blob: e565835844a8b9e3d4103089f0174999d5bb8eee [file] [log] [blame]
Andrew Boie945af952017-08-22 13:15:23 -07001#!/usr/bin/env python3
2#
3# Copyright (c) 2017 Intel Corporation
4#
5# SPDX-License-Identifier: Apache-2.0
Andrew Boiec78c5e62019-03-11 14:45:43 -07006"""
7Script to generate gperf tables of kernel object metadata
8
9User mode threads making system calls reference kernel objects by memory
10address, as the kernel/driver APIs in Zephyr are the same for both user
11and supervisor contexts. It is necessary for the kernel to be able to
12validate accesses to kernel objects to make the following assertions:
13
14 - That the memory address points to a kernel object
15
16 - The kernel object is of the expected type for the API being invoked
17
18 - The kernel object is of the expected initialization state
19
20 - The calling thread has sufficient permissions on the object
21
David B. Kinder17299f02019-05-31 15:39:39 -070022For more details see the :ref:`kernelobjects` section in the documentation.
Marc Herbert4a10eea2019-04-16 15:39:45 -070023
Andrew Boiec78c5e62019-03-11 14:45:43 -070024The zephyr build generates an intermediate ELF binary, zephyr_prebuilt.elf,
25which this script scans looking for kernel objects by examining the DWARF
26debug information to look for instances of data structures that are considered
27kernel objects. For device drivers, the API struct pointer populated at build
28time is also examined to disambiguate between various device driver instances
29since they are all 'struct device'.
30
Marc Herbert4a10eea2019-04-16 15:39:45 -070031This script can generate five different output files:
Andrew Boiec78c5e62019-03-11 14:45:43 -070032
33 - A gperf script to generate the hash table mapping kernel object memory
34 addresses to kernel object metadata, used to track permissions,
35 object type, initialization state, and any object-specific data.
36
37 - A header file containing generated macros for validating driver instances
38 inside the system call handlers for the driver subsystem APIs.
39
Marc Herbert4a10eea2019-04-16 15:39:45 -070040 - A code fragment included by kernel.h with one enum constant for
41 each kernel object type and each driver instance.
Andrew Boiec78c5e62019-03-11 14:45:43 -070042
Marc Herbert4a10eea2019-04-16 15:39:45 -070043 - The inner cases of a switch/case C statement, included by
44 kernel/userspace.c, mapping the kernel object types and driver
45 instances to their human-readable representation in the
Andrew Boiec78c5e62019-03-11 14:45:43 -070046 otype_to_str() function.
47
Marc Herbert4a10eea2019-04-16 15:39:45 -070048 - The inner cases of a switch/case C statement, included by
49 kernel/userspace.c, mapping kernel object types to their sizes.
50 This is used for allocating instances of them at runtime
51 (CONFIG_DYNAMIC_OBJECTS) in the obj_size_get() function.
Andrew Boiec78c5e62019-03-11 14:45:43 -070052"""
Andrew Boie945af952017-08-22 13:15:23 -070053
54import sys
55import argparse
Daniel Leunge58b6542018-08-08 11:23:16 -070056import math
Andrew Boie945af952017-08-22 13:15:23 -070057import os
58import struct
Corey Whartonccd15df2020-02-29 14:51:42 -080059import json
Andrew Boief20efcf2018-05-23 10:57:39 -070060from elf_helper import ElfHelper, kobject_to_enum
Andrew Boie945af952017-08-22 13:15:23 -070061
Marc Herbertf78288b2019-03-05 14:31:44 -080062from collections import OrderedDict
63
Andrew Boie09c22cc2018-06-27 10:25:45 -070064# Keys in this dictionary are structs which should be recognized as kernel
Andrew Boiec235e162019-03-27 14:27:24 -070065# objects. Values are a tuple:
66#
67# - The first item is None, or the name of a Kconfig that
68# indicates the presence of this object's definition in case it is not
69# available in all configurations.
70#
71# - The second item is a boolean indicating whether it is permissible for
72# the object to be located in user-accessible memory.
Andrew Boie09c22cc2018-06-27 10:25:45 -070073
Marc Herbertf78288b2019-03-05 14:31:44 -080074# Regular dictionaries are ordered only with Python 3.6 and
75# above. Good summary and pointers to official documents at:
76# https://stackoverflow.com/questions/39980323/are-dictionaries-ordered-in-python-3-6
Ulf Magnusson0d39a102019-09-06 11:13:19 +020077kobjects = OrderedDict([
Andrew Boiec235e162019-03-27 14:27:24 -070078 ("k_mem_slab", (None, False)),
79 ("k_msgq", (None, False)),
80 ("k_mutex", (None, False)),
81 ("k_pipe", (None, False)),
82 ("k_queue", (None, False)),
83 ("k_poll_signal", (None, False)),
84 ("k_sem", (None, False)),
85 ("k_stack", (None, False)),
86 ("k_thread", (None, False)),
87 ("k_timer", (None, False)),
88 ("_k_thread_stack_element", (None, False)),
Andrew Boiec235e162019-03-27 14:27:24 -070089 ("device", (None, False)),
Wentong Wu5611e922019-06-20 23:51:27 +080090 ("sys_mutex", (None, True)),
91 ("k_futex", (None, True))
Marc Herbertf78288b2019-03-05 14:31:44 -080092])
93
Andrew Boie5bd891d2017-09-27 12:59:28 -070094subsystems = [
Corey Wharton86bfc482020-03-04 13:32:01 -080095 # Editing the list is deprecated, add the __subsystem sentinal to your driver
96 # api declaration instead. e.x.
97 #
98 # __subsystem struct my_driver_api {
99 # ....
100 #};
Andrew Boie6f928092019-04-23 13:06:59 -0700101]
Andrew Boie5bd891d2017-09-27 12:59:28 -0700102
Andrew Boie945af952017-08-22 13:15:23 -0700103header = """%compare-lengths
Patrik Flykt4344e272019-03-08 14:19:05 -0700104%define lookup-function-name z_object_lookup
Andrew Boie945af952017-08-22 13:15:23 -0700105%language=ANSI-C
Andrew Boie47f8fd12017-10-05 11:11:02 -0700106%global-table
Andrew Boie945af952017-08-22 13:15:23 -0700107%struct-type
108%{
109#include <kernel.h>
Andrew Boie31bdfc02017-11-08 16:38:03 -0800110#include <toolchain.h>
Andrew Boie47f8fd12017-10-05 11:11:02 -0700111#include <syscall_handler.h>
Andrew Boie945af952017-08-22 13:15:23 -0700112#include <string.h>
113%}
114struct _k_object;
Andrew Boie945af952017-08-22 13:15:23 -0700115"""
116
Andy Gross6042ae92018-01-22 14:26:49 -0600117# Different versions of gperf have different prototypes for the lookup
118# function, best to implement the wrapper here. The pointer value itself is
119# turned into a string, we told gperf to expect binary strings that are not
Anas Nashif72565532017-12-12 08:19:25 -0500120# NULL-terminated.
Andrew Boie945af952017-08-22 13:15:23 -0700121footer = """%%
Patrik Flykt4344e272019-03-08 14:19:05 -0700122struct _k_object *z_object_gperf_find(void *obj)
Andrew Boie945af952017-08-22 13:15:23 -0700123{
Patrik Flykt4344e272019-03-08 14:19:05 -0700124 return z_object_lookup((const char *)obj, sizeof(void *));
Andrew Boie945af952017-08-22 13:15:23 -0700125}
Andrew Boie47f8fd12017-10-05 11:11:02 -0700126
Patrik Flykt4344e272019-03-08 14:19:05 -0700127void z_object_gperf_wordlist_foreach(_wordlist_cb_func_t func, void *context)
Andrew Boie47f8fd12017-10-05 11:11:02 -0700128{
129 int i;
130
131 for (i = MIN_HASH_VALUE; i <= MAX_HASH_VALUE; i++) {
Flavio Ceolin4218d5f2018-09-17 09:39:51 -0700132 if (wordlist[i].name != NULL) {
Andrew Boie47f8fd12017-10-05 11:11:02 -0700133 func(&wordlist[i], context);
134 }
135 }
136}
Andrew Boie31bdfc02017-11-08 16:38:03 -0800137
138#ifndef CONFIG_DYNAMIC_OBJECTS
Patrik Flykt4344e272019-03-08 14:19:05 -0700139struct _k_object *z_object_find(void *obj)
140 ALIAS_OF(z_object_gperf_find);
Andrew Boie31bdfc02017-11-08 16:38:03 -0800141
Patrik Flykt4344e272019-03-08 14:19:05 -0700142void z_object_wordlist_foreach(_wordlist_cb_func_t func, void *context)
143 ALIAS_OF(z_object_gperf_wordlist_foreach);
Andrew Boie31bdfc02017-11-08 16:38:03 -0800144#endif
Andrew Boie945af952017-08-22 13:15:23 -0700145"""
146
147
Andy Gross6042ae92018-01-22 14:26:49 -0600148def write_gperf_table(fp, eh, objs, static_begin, static_end):
Andrew Boie945af952017-08-22 13:15:23 -0700149 fp.write(header)
Andrew Boief0835672019-03-27 15:44:52 -0700150 num_mutexes = eh.get_sys_mutex_counter()
Ulf Magnusson9d343562019-05-07 12:06:35 +0200151 if num_mutexes != 0:
Andrew Boief0835672019-03-27 15:44:52 -0700152 fp.write("static struct k_mutex kernel_mutexes[%d] = {\n" % num_mutexes)
153 for i in range(num_mutexes):
154 fp.write("_K_MUTEX_INITIALIZER(kernel_mutexes[%d])" % i)
Ulf Magnusson9d343562019-05-07 12:06:35 +0200155 if i != num_mutexes - 1:
Andrew Boief0835672019-03-27 15:44:52 -0700156 fp.write(", ")
157 fp.write("};\n")
Andrew Boie945af952017-08-22 13:15:23 -0700158
Wentong Wu5611e922019-06-20 23:51:27 +0800159 num_futex = eh.get_futex_counter()
Ulf Magnussond4c851c2019-09-02 11:11:22 +0200160 if num_futex != 0:
Wentong Wu5611e922019-06-20 23:51:27 +0800161 fp.write("static struct z_futex_data futex_data[%d] = {\n" % num_futex)
162 for i in range(num_futex):
163 fp.write("Z_FUTEX_DATA_INITIALIZER(futex_data[%d])" % i)
Ulf Magnussond4c851c2019-09-02 11:11:22 +0200164 if i != num_futex - 1:
Wentong Wu5611e922019-06-20 23:51:27 +0800165 fp.write(", ")
166 fp.write("};\n")
167
Andrew Boief0835672019-03-27 15:44:52 -0700168 fp.write("%%\n")
Daniel Leunge58b6542018-08-08 11:23:16 -0700169 # Setup variables for mapping thread indexes
170 syms = eh.get_symbols()
171 thread_max_bytes = syms["CONFIG_MAX_THREAD_BYTES"]
172 thread_idx_map = {}
173
174 for i in range(0, thread_max_bytes):
175 thread_idx_map[i] = 0xFF
176
Andrew Boiebca15da2017-10-15 14:17:48 -0700177 for obj_addr, ko in objs.items():
178 obj_type = ko.type_name
Andrew Boie945af952017-08-22 13:15:23 -0700179 # pre-initialized objects fall within this memory range, they are
180 # either completely initialized at build time, or done automatically
181 # at boot during some PRE_KERNEL_* phase
Ulf Magnusson3206e422019-09-03 15:05:01 +0200182 initialized = static_begin <= obj_addr < static_end
Andrew Boie78757072019-07-23 13:29:30 -0700183 is_driver = obj_type.startswith("K_OBJ_DRIVER_")
Andrew Boie945af952017-08-22 13:15:23 -0700184
Andrew Boief290ab52019-11-18 17:06:13 -0800185 if "CONFIG_64BIT" in syms:
186 format_code = "Q"
187 else:
188 format_code = "I"
189
190 if eh.little_endian:
191 endian = "<"
192 else:
193 endian = ">"
194
195 byte_str = struct.pack(endian + format_code, obj_addr)
Andrew Boie945af952017-08-22 13:15:23 -0700196 fp.write("\"")
197 for byte in byte_str:
198 val = "\\x%02x" % byte
199 fp.write(val)
200
Andrew Boie78757072019-07-23 13:29:30 -0700201 flags = "0"
Ulf Magnussond4c851c2019-09-02 11:11:22 +0200202 if initialized:
Andrew Boie78757072019-07-23 13:29:30 -0700203 flags += " | K_OBJ_FLAG_INITIALIZED"
Ulf Magnussond4c851c2019-09-02 11:11:22 +0200204 if is_driver:
Andrew Boie78757072019-07-23 13:29:30 -0700205 flags += " | K_OBJ_FLAG_DRIVER"
206
207 fp.write("\", {}, %s, %s, %s\n" % (obj_type, flags, str(ko.data)))
Andrew Boie945af952017-08-22 13:15:23 -0700208
Daniel Leunge58b6542018-08-08 11:23:16 -0700209 if obj_type == "K_OBJ_THREAD":
210 idx = math.floor(ko.data / 8)
211 bit = ko.data % 8
212 thread_idx_map[idx] = thread_idx_map[idx] & ~(2**bit)
213
Andrew Boie945af952017-08-22 13:15:23 -0700214 fp.write(footer)
215
Daniel Leunge58b6542018-08-08 11:23:16 -0700216 # Generate the array of already mapped thread indexes
217 fp.write('\n')
Andrew Boie9b34ecd2020-01-08 17:37:11 -0800218 fp.write('Z_GENERIC_SECTION(.kobject_data.data) ')
Daniel Leunge58b6542018-08-08 11:23:16 -0700219 fp.write('u8_t _thread_idx_map[%d] = {' % (thread_max_bytes))
220
221 for i in range(0, thread_max_bytes):
222 fp.write(' 0x%x, ' % (thread_idx_map[i]))
223
224 fp.write('};\n')
225
Andrew Boie945af952017-08-22 13:15:23 -0700226
Leandro Pereirac2003672018-04-04 13:50:32 -0700227driver_macro_tpl = """
Andrew Boie8345e5e2018-05-04 15:57:57 -0700228#define Z_SYSCALL_DRIVER_%(driver_upper)s(ptr, op) Z_SYSCALL_DRIVER_GEN(ptr, op, %(driver_lower)s, %(driver_upper)s)
Leandro Pereirac2003672018-04-04 13:50:32 -0700229"""
230
Anas Nashif7a08b2b2018-09-16 14:54:44 -0500231
Leandro Pereirac2003672018-04-04 13:50:32 -0700232def write_validation_output(fp):
Flavio Ceolina7fffa92018-09-13 15:06:35 -0700233 fp.write("#ifndef DRIVER_VALIDATION_GEN_H\n")
234 fp.write("#define DRIVER_VALIDATION_GEN_H\n")
Leandro Pereirac2003672018-04-04 13:50:32 -0700235
Andrew Boie8345e5e2018-05-04 15:57:57 -0700236 fp.write("""#define Z_SYSCALL_DRIVER_GEN(ptr, op, driver_lower_case, driver_upper_case) \\
237 (Z_SYSCALL_OBJ(ptr, K_OBJ_DRIVER_##driver_upper_case) || \\
238 Z_SYSCALL_DRIVER_OP(ptr, driver_lower_case##_driver_api, op))
239 """)
Leandro Pereirac2003672018-04-04 13:50:32 -0700240
241 for subsystem in subsystems:
242 subsystem = subsystem.replace("_driver_api", "")
243
244 fp.write(driver_macro_tpl % {
245 "driver_lower": subsystem.lower(),
246 "driver_upper": subsystem.upper(),
247 })
248
Flavio Ceolina7fffa92018-09-13 15:06:35 -0700249 fp.write("#endif /* DRIVER_VALIDATION_GEN_H */\n")
Leandro Pereirac2003672018-04-04 13:50:32 -0700250
Leandro Pereira39dc7d02018-04-05 13:59:33 -0700251
252def write_kobj_types_output(fp):
253 fp.write("/* Core kernel objects */\n")
Andrew Boiec235e162019-03-27 14:27:24 -0700254 for kobj, obj_info in kobjects.items():
255 dep, _ = obj_info
Leandro Pereira39dc7d02018-04-05 13:59:33 -0700256 if kobj == "device":
257 continue
258
Andrew Boie09c22cc2018-06-27 10:25:45 -0700259 if dep:
260 fp.write("#ifdef %s\n" % dep)
261
Andrew Boief20efcf2018-05-23 10:57:39 -0700262 fp.write("%s,\n" % kobject_to_enum(kobj))
Leandro Pereira39dc7d02018-04-05 13:59:33 -0700263
Andrew Boie09c22cc2018-06-27 10:25:45 -0700264 if dep:
265 fp.write("#endif\n")
266
Leandro Pereira39dc7d02018-04-05 13:59:33 -0700267 fp.write("/* Driver subsystems */\n")
268 for subsystem in subsystems:
269 subsystem = subsystem.replace("_driver_api", "").upper()
270 fp.write("K_OBJ_DRIVER_%s,\n" % subsystem)
271
272
273def write_kobj_otype_output(fp):
274 fp.write("/* Core kernel objects */\n")
Andrew Boiec235e162019-03-27 14:27:24 -0700275 for kobj, obj_info in kobjects.items():
276 dep, _ = obj_info
Leandro Pereira39dc7d02018-04-05 13:59:33 -0700277 if kobj == "device":
278 continue
279
Andrew Boie09c22cc2018-06-27 10:25:45 -0700280 if dep:
281 fp.write("#ifdef %s\n" % dep)
282
Anas Nashif7a08b2b2018-09-16 14:54:44 -0500283 fp.write('case %s: ret = "%s"; break;\n' %
284 (kobject_to_enum(kobj), kobj))
Andrew Boie09c22cc2018-06-27 10:25:45 -0700285 if dep:
286 fp.write("#endif\n")
Leandro Pereira39dc7d02018-04-05 13:59:33 -0700287
288 fp.write("/* Driver subsystems */\n")
289 for subsystem in subsystems:
290 subsystem = subsystem.replace("_driver_api", "")
Flavio Ceolin3259ac02018-09-11 13:14:21 -0700291 fp.write('case K_OBJ_DRIVER_%s: ret = "%s driver"; break;\n' % (
Leandro Pereira39dc7d02018-04-05 13:59:33 -0700292 subsystem.upper(),
293 subsystem
294 ))
295
Anas Nashif7a08b2b2018-09-16 14:54:44 -0500296
Andrew Boie47fa8eb2018-05-16 10:11:17 -0700297def write_kobj_size_output(fp):
298 fp.write("/* Non device/stack objects */\n")
Andrew Boiec235e162019-03-27 14:27:24 -0700299 for kobj, obj_info in kobjects.items():
300 dep, _ = obj_info
Andrew Boie47fa8eb2018-05-16 10:11:17 -0700301 # device handled by default case. Stacks are not currently handled,
302 # if they eventually are it will be a special case.
Ulf Magnusson3feb8f92019-09-03 14:25:26 +0200303 if kobj in {"device", "_k_thread_stack_element"}:
Andrew Boie47fa8eb2018-05-16 10:11:17 -0700304 continue
305
Andrew Boie09c22cc2018-06-27 10:25:45 -0700306 if dep:
307 fp.write("#ifdef %s\n" % dep)
308
Flavio Ceolin3259ac02018-09-11 13:14:21 -0700309 fp.write('case %s: ret = sizeof(struct %s); break;\n' %
Anas Nashif7a08b2b2018-09-16 14:54:44 -0500310 (kobject_to_enum(kobj), kobj))
Andrew Boie09c22cc2018-06-27 10:25:45 -0700311 if dep:
312 fp.write("#endif\n")
313
Leandro Pereira39dc7d02018-04-05 13:59:33 -0700314
Corey Whartonccd15df2020-02-29 14:51:42 -0800315def parse_subsystems_list_file(path):
316 with open(path, "r") as fp:
317 subsys_list = json.load(fp)
318 subsystems.extend(subsys_list)
319
Andrew Boie945af952017-08-22 13:15:23 -0700320def parse_args():
321 global args
322
Anas Nashif72565532017-12-12 08:19:25 -0500323 parser = argparse.ArgumentParser(
324 description=__doc__,
325 formatter_class=argparse.RawDescriptionHelpFormatter)
Andrew Boie945af952017-08-22 13:15:23 -0700326
Leandro Pereirac2003672018-04-04 13:50:32 -0700327 parser.add_argument("-k", "--kernel", required=False,
Anas Nashif72565532017-12-12 08:19:25 -0500328 help="Input zephyr ELF binary")
329 parser.add_argument(
Leandro Pereirac2003672018-04-04 13:50:32 -0700330 "-g", "--gperf-output", required=False,
Anas Nashif72565532017-12-12 08:19:25 -0500331 help="Output list of kernel object addresses for gperf use")
Leandro Pereirac2003672018-04-04 13:50:32 -0700332 parser.add_argument(
333 "-V", "--validation-output", required=False,
334 help="Output driver validation macros")
Leandro Pereira39dc7d02018-04-05 13:59:33 -0700335 parser.add_argument(
336 "-K", "--kobj-types-output", required=False,
Marc Herbert4a10eea2019-04-16 15:39:45 -0700337 help="Output k_object enum constants")
Leandro Pereira39dc7d02018-04-05 13:59:33 -0700338 parser.add_argument(
339 "-S", "--kobj-otype-output", required=False,
340 help="Output case statements for otype_to_str()")
Andrew Boie47fa8eb2018-05-16 10:11:17 -0700341 parser.add_argument(
342 "-Z", "--kobj-size-output", required=False,
343 help="Output case statements for obj_size_get()")
Corey Whartonccd15df2020-02-29 14:51:42 -0800344 parser.add_argument("-i", "--include-subsystem-list", required=False, action='append',
345 help='''Specifies a file with a JSON encoded list of subsystem names to append to
346 the driver subsystems list. Can be specified multiple times:
347 -i file1 -i file2 ...''')
348
Andrew Boie945af952017-08-22 13:15:23 -0700349 parser.add_argument("-v", "--verbose", action="store_true",
Anas Nashif72565532017-12-12 08:19:25 -0500350 help="Print extra debugging information")
Andrew Boie945af952017-08-22 13:15:23 -0700351 args = parser.parse_args()
Sebastian Bøe4971d2a2017-12-28 17:34:50 +0100352 if "VERBOSE" in os.environ:
353 args.verbose = 1
Andrew Boie945af952017-08-22 13:15:23 -0700354
355
356def main():
357 parse_args()
358
Corey Whartonccd15df2020-02-29 14:51:42 -0800359 if args.include_subsystem_list is not None:
360 for list_file in args.include_subsystem_list:
361 parse_subsystems_list_file(list_file)
362
Leandro Pereirac2003672018-04-04 13:50:32 -0700363 if args.gperf_output:
Marc Herbertf78288b2019-03-05 14:31:44 -0800364 assert args.kernel, "--kernel ELF required for --gperf-output"
Leandro Pereirac2003672018-04-04 13:50:32 -0700365 eh = ElfHelper(args.kernel, args.verbose, kobjects, subsystems)
366 syms = eh.get_symbols()
367 max_threads = syms["CONFIG_MAX_THREAD_BYTES"] * 8
368 objs = eh.find_kobjects(syms)
Marc Herbertf78288b2019-03-05 14:31:44 -0800369 if not objs:
370 sys.stderr.write("WARNING: zero kobject found in %s\n"
371 % args.kernel)
Andrew Boie945af952017-08-22 13:15:23 -0700372
Anas Nashif7a08b2b2018-09-16 14:54:44 -0500373 thread_counter = eh.get_thread_counter()
374 if thread_counter > max_threads:
Ulf Magnusson50b9b122019-09-07 14:41:01 +0200375 sys.exit("Too many thread objects ({})\n"
376 "Increase CONFIG_MAX_THREAD_BYTES to {}"
377 .format(thread_counter, -(-thread_counter // 8)))
Andrew Boie818a96d2017-11-03 09:00:35 -0700378
Leandro Pereirac2003672018-04-04 13:50:32 -0700379 with open(args.gperf_output, "w") as fp:
380 write_gperf_table(fp, eh, objs,
381 syms["_static_kernel_objects_begin"],
382 syms["_static_kernel_objects_end"])
383
384 if args.validation_output:
385 with open(args.validation_output, "w") as fp:
386 write_validation_output(fp)
Anas Nashif72565532017-12-12 08:19:25 -0500387
Leandro Pereira39dc7d02018-04-05 13:59:33 -0700388 if args.kobj_types_output:
389 with open(args.kobj_types_output, "w") as fp:
390 write_kobj_types_output(fp)
391
392 if args.kobj_otype_output:
393 with open(args.kobj_otype_output, "w") as fp:
394 write_kobj_otype_output(fp)
Andrew Boie945af952017-08-22 13:15:23 -0700395
Andrew Boie47fa8eb2018-05-16 10:11:17 -0700396 if args.kobj_size_output:
397 with open(args.kobj_size_output, "w") as fp:
398 write_kobj_size_output(fp)
399
Anas Nashif7a08b2b2018-09-16 14:54:44 -0500400
Andrew Boie945af952017-08-22 13:15:23 -0700401if __name__ == "__main__":
402 main()