blob: cb0c33835a7285388d92c4a456730c6d3d16ac94 [file] [log] [blame]
Andy Gross826389a2018-01-22 14:09:48 -06001#!/usr/bin/env python3
2#
3# Copyright (c) 2017-2018 Linaro
4#
5# SPDX-License-Identifier: Apache-2.0
6
7import sys
Andy Gross826389a2018-01-22 14:09:48 -06008import os
9import struct
10from distutils.version import LooseVersion
11
Marc Herbertf78288b2019-03-05 14:31:44 -080012from collections import OrderedDict
13
Andy Gross826389a2018-01-22 14:09:48 -060014import elftools
15from elftools.elf.elffile import ELFFile
Andy Gross826389a2018-01-22 14:09:48 -060016from elftools.elf.sections import SymbolTableSection
17
18if LooseVersion(elftools.__version__) < LooseVersion('0.24'):
19 sys.stderr.write("pyelftools is out of date, need version 0.24 or later\n")
20 sys.exit(1)
21
22
23def subsystem_to_enum(subsys):
24 return "K_OBJ_DRIVER_" + subsys[:-11].upper()
25
26
Andrew Boief20efcf2018-05-23 10:57:39 -070027def kobject_to_enum(kobj):
28 if kobj.startswith("k_") or kobj.startswith("_k_"):
29 name = kobj[2:]
30 else:
31 name = kobj
32
33 return "K_OBJ_%s" % name.upper()
Andy Gross826389a2018-01-22 14:09:48 -060034
35
36DW_OP_addr = 0x3
37DW_OP_fbreg = 0x91
38STACK_TYPE = "_k_thread_stack_element"
39thread_counter = 0
Andrew Boief0835672019-03-27 15:44:52 -070040sys_mutex_counter = 0
Wentong Wu5611e922019-06-20 23:51:27 +080041futex_counter = 0
Andy Gross826389a2018-01-22 14:09:48 -060042
43# Global type environment. Populated by pass 1.
44type_env = {}
Andrew Boie577d5dd2018-05-16 15:52:37 -070045extern_env = {}
Andy Gross826389a2018-01-22 14:09:48 -060046kobjects = {}
47subsystems = {}
48
49# --- debug stuff ---
50
51scr = os.path.basename(sys.argv[0])
52
53# --- type classes ----
54
55
56class KobjectInstance:
57 def __init__(self, type_obj, addr):
58 global thread_counter
Andrew Boief0835672019-03-27 15:44:52 -070059 global sys_mutex_counter
Wentong Wu5611e922019-06-20 23:51:27 +080060 global futex_counter
Andy Gross826389a2018-01-22 14:09:48 -060061
62 self.addr = addr
63 self.type_obj = type_obj
64
65 # Type name determined later since drivers needs to look at the
66 # API struct address
67 self.type_name = None
68
69 if self.type_obj.name == "k_thread":
70 # Assign an ID for this thread object, used to track its
71 # permissions to other kernel objects
72 self.data = thread_counter
73 thread_counter = thread_counter + 1
Andrew Boief0835672019-03-27 15:44:52 -070074 elif self.type_obj.name == "sys_mutex":
75 self.data = "(u32_t)(&kernel_mutexes[%d])" % sys_mutex_counter
76 sys_mutex_counter += 1
Wentong Wu5611e922019-06-20 23:51:27 +080077 elif self.type_obj.name == "k_futex":
78 self.data = "(u32_t)(&futex_data[%d])" % futex_counter
79 futex_counter += 1
Andy Gross826389a2018-01-22 14:09:48 -060080 else:
81 self.data = 0
82
83
84class KobjectType:
85 def __init__(self, offset, name, size, api=False):
86 self.name = name
87 self.size = size
88 self.offset = offset
89 self.api = api
90
91 def __repr__(self):
92 return "<kobject %s>" % self.name
93
94 def has_kobject(self):
95 return True
96
97 def get_kobjects(self, addr):
98 return {addr: KobjectInstance(self, addr)}
99
100
101class ArrayType:
102 def __init__(self, offset, elements, member_type):
103 self.elements = elements
104 self.member_type = member_type
105 self.offset = offset
106
107 def __repr__(self):
Ulf Magnusson7ffd6282019-03-20 21:56:09 +0100108 return "<array of %d>" % self.member_type
Andy Gross826389a2018-01-22 14:09:48 -0600109
110 def has_kobject(self):
111 if self.member_type not in type_env:
112 return False
113
114 return type_env[self.member_type].has_kobject()
115
116 def get_kobjects(self, addr):
117 mt = type_env[self.member_type]
118
119 # Stacks are arrays of _k_stack_element_t but we want to treat
120 # the whole array as one kernel object (a thread stack)
121 # Data value gets set to size of entire region
122 if isinstance(mt, KobjectType) and mt.name == STACK_TYPE:
123 # An array of stacks appears as a multi-dimensional array.
124 # The last size is the size of each stack. We need to track
125 # each stack within the array, not as one huge stack object.
126 *dimensions, stacksize = self.elements
127 num_members = 1
128 for e in dimensions:
129 num_members = num_members * e
130
131 ret = {}
132 for i in range(num_members):
133 a = addr + (i * stacksize)
134 o = mt.get_kobjects(a)
135 o[a].data = stacksize
136 ret.update(o)
137 return ret
138
139 objs = {}
140
141 # Multidimensional array flattened out
142 num_members = 1
143 for e in self.elements:
144 num_members = num_members * e
145
146 for i in range(num_members):
147 objs.update(mt.get_kobjects(addr + (i * mt.size)))
148 return objs
149
150
151class AggregateTypeMember:
152 def __init__(self, offset, member_name, member_type, member_offset):
153 self.member_name = member_name
154 self.member_type = member_type
Andrew Boiee144a682018-05-21 15:51:31 -0700155 if isinstance(member_offset, list):
ruuddw8364b222018-05-25 15:53:27 +0200156 # DWARF v2, location encoded as set of operations
157 # only "DW_OP_plus_uconst" with ULEB128 argument supported
Anas Nashif6af68b32018-09-16 14:52:34 -0500158 if member_offset[0] == 0x23:
ruuddw8364b222018-05-25 15:53:27 +0200159 self.member_offset = member_offset[1] & 0x7f
160 for i in range(1, len(member_offset)-1):
Ulf Magnussonba312fe2019-03-20 19:30:29 +0100161 if member_offset[i] & 0x80:
Anas Nashif6af68b32018-09-16 14:52:34 -0500162 self.member_offset += (
163 member_offset[i+1] & 0x7f) << i*7
ruuddw8364b222018-05-25 15:53:27 +0200164 else:
Andrew Boiecf91f8c2019-03-27 13:40:51 -0700165 raise Exception("not yet supported location operation (%s:%d:%d)" %
166 (self.member_name, self.member_type, member_offset[0]))
Andrew Boiee144a682018-05-21 15:51:31 -0700167 else:
168 self.member_offset = member_offset
Andy Gross826389a2018-01-22 14:09:48 -0600169
170 def __repr__(self):
171 return "<member %s, type %d, offset %d>" % (
172 self.member_name, self.member_type, self.member_offset)
173
174 def has_kobject(self):
175 if self.member_type not in type_env:
176 return False
177
178 return type_env[self.member_type].has_kobject()
179
180 def get_kobjects(self, addr):
181 mt = type_env[self.member_type]
182 return mt.get_kobjects(addr + self.member_offset)
183
184
185class ConstType:
186 def __init__(self, child_type):
187 self.child_type = child_type
188
189 def __repr__(self):
190 return "<const %d>" % self.child_type
191
192 def has_kobject(self):
193 if self.child_type not in type_env:
194 return False
195
196 return type_env[self.child_type].has_kobject()
197
198 def get_kobjects(self, addr):
199 return type_env[self.child_type].get_kobjects(addr)
200
201
202class AggregateType:
203 def __init__(self, offset, name, size):
204 self.name = name
205 self.size = size
206 self.offset = offset
207 self.members = []
208
209 def add_member(self, member):
210 self.members.append(member)
211
212 def __repr__(self):
213 return "<struct %s, with %s>" % (self.name, self.members)
214
215 def has_kobject(self):
216 result = False
217
218 bad_members = []
219
220 for member in self.members:
221 if member.has_kobject():
222 result = True
223 else:
224 bad_members.append(member)
225 # Don't need to consider this again, just remove it
226
227 for bad_member in bad_members:
228 self.members.remove(bad_member)
229
230 return result
231
232 def get_kobjects(self, addr):
233 objs = {}
234 for member in self.members:
235 objs.update(member.get_kobjects(addr))
236 return objs
237
238
239# --- helper functions for getting data from DIEs ---
240
Andrew Boie577d5dd2018-05-16 15:52:37 -0700241def die_get_spec(die):
242 if 'DW_AT_specification' not in die.attributes:
243 return None
244
245 spec_val = die.attributes["DW_AT_specification"].value
246
247 # offset of the DW_TAG_variable for the extern declaration
248 offset = spec_val + die.cu.cu_offset
249
250 return extern_env.get(offset)
251
252
Andy Gross826389a2018-01-22 14:09:48 -0600253def die_get_name(die):
254 if 'DW_AT_name' not in die.attributes:
Andrew Boie577d5dd2018-05-16 15:52:37 -0700255 die = die_get_spec(die)
256 if not die:
257 return None
258
Andy Gross826389a2018-01-22 14:09:48 -0600259 return die.attributes["DW_AT_name"].value.decode("utf-8")
260
261
262def die_get_type_offset(die):
263 if 'DW_AT_type' not in die.attributes:
Andrew Boie577d5dd2018-05-16 15:52:37 -0700264 die = die_get_spec(die)
265 if not die:
266 return None
Andy Gross826389a2018-01-22 14:09:48 -0600267
268 return die.attributes["DW_AT_type"].value + die.cu.cu_offset
269
270
271def die_get_byte_size(die):
272 if 'DW_AT_byte_size' not in die.attributes:
273 return 0
274
275 return die.attributes["DW_AT_byte_size"].value
276
277
278def analyze_die_struct(die):
279 name = die_get_name(die) or "<anon>"
280 offset = die.offset
281 size = die_get_byte_size(die)
282
283 # Incomplete type
284 if not size:
285 return
286
287 if name in kobjects:
288 type_env[offset] = KobjectType(offset, name, size)
289 elif name in subsystems:
290 type_env[offset] = KobjectType(offset, name, size, api=True)
291 else:
292 at = AggregateType(offset, name, size)
293 type_env[offset] = at
294
295 for child in die.iter_children():
296 if child.tag != "DW_TAG_member":
297 continue
298 child_type = die_get_type_offset(child)
299 member_offset = \
300 child.attributes["DW_AT_data_member_location"].value
301 cname = die_get_name(child) or "<anon>"
302 m = AggregateTypeMember(child.offset, cname, child_type,
303 member_offset)
304 at.add_member(m)
305
306 return
307
308
309def analyze_die_const(die):
310 type_offset = die_get_type_offset(die)
311 if not type_offset:
312 return
313
314 type_env[die.offset] = ConstType(type_offset)
315
316
317def analyze_die_array(die):
318 type_offset = die_get_type_offset(die)
319 elements = []
320
321 for child in die.iter_children():
322 if child.tag != "DW_TAG_subrange_type":
323 continue
324 if "DW_AT_upper_bound" not in child.attributes:
325 continue
326
327 ub = child.attributes["DW_AT_upper_bound"]
328 if not ub.form.startswith("DW_FORM_data"):
329 continue
330
331 elements.append(ub.value + 1)
332
333 if not elements:
334 return
335
336 type_env[die.offset] = ArrayType(die.offset, elements, type_offset)
337
338
339def addr_deref(elf, addr):
340 for section in elf.iter_sections():
341 start = section['sh_addr']
342 end = start + section['sh_size']
343
344 if addr >= start and addr < end:
345 data = section.data()
346 offset = addr - start
347 return struct.unpack("<I" if elf.little_endian else ">I",
348 data[offset:offset + 4])[0]
349
350 return 0
351
352
353def device_get_api_addr(elf, addr):
354 return addr_deref(elf, addr + 4)
355
356
357def get_filename_lineno(die):
358 lp_header = die.dwarfinfo.line_program_for_CU(die.cu).header
359 files = lp_header["file_entry"]
360 includes = lp_header["include_directory"]
361
362 fileinfo = files[die.attributes["DW_AT_decl_file"].value - 1]
363 filename = fileinfo.name.decode("utf-8")
364 filedir = includes[fileinfo.dir_index - 1].decode("utf-8")
365
366 path = os.path.join(filedir, filename)
367 lineno = die.attributes["DW_AT_decl_line"].value
368 return (path, lineno)
369
370
371class ElfHelper:
372
373 def __init__(self, filename, verbose, kobjs, subs):
374 self.verbose = verbose
375 self.fp = open(filename, "rb")
376 self.elf = ELFFile(self.fp)
377 self.little_endian = self.elf.little_endian
378 global kobjects
379 global subsystems
380 kobjects = kobjs
381 subsystems = subs
382
383 def find_kobjects(self, syms):
384 if not self.elf.has_dwarf_info():
385 sys.stderr.write("ELF file has no DWARF information\n")
386 sys.exit(1)
387
Wentong Wu859ca422019-07-05 17:49:02 +0800388 app_smem_start = syms["_app_smem_start"]
389 app_smem_end = syms["_app_smem_end"]
Andy Gross826389a2018-01-22 14:09:48 -0600390
391 di = self.elf.get_dwarf_info()
392
393 variables = []
394
395 # Step 1: collect all type information.
396 for CU in di.iter_CUs():
Ulf Magnusson12ba9df2019-03-19 19:28:24 +0100397 for die in CU.iter_DIEs():
Andy Gross826389a2018-01-22 14:09:48 -0600398 # Unions are disregarded, kernel objects should never be union
399 # members since the memory is not dedicated to that object and
400 # could be something else
401 if die.tag == "DW_TAG_structure_type":
402 analyze_die_struct(die)
403 elif die.tag == "DW_TAG_const_type":
404 analyze_die_const(die)
405 elif die.tag == "DW_TAG_array_type":
406 analyze_die_array(die)
407 elif die.tag == "DW_TAG_variable":
408 variables.append(die)
409
410 # Step 2: filter type_env to only contain kernel objects, or structs
411 # and arrays of kernel objects
412 bad_offsets = []
413 for offset, type_object in type_env.items():
414 if not type_object.has_kobject():
415 bad_offsets.append(offset)
416
417 for offset in bad_offsets:
418 del type_env[offset]
419
420 # Step 3: Now that we know all the types we are looking for, examine
421 # all variables
422 all_objs = {}
423
Andy Gross826389a2018-01-22 14:09:48 -0600424 for die in variables:
425 name = die_get_name(die)
426 if not name:
427 continue
428
Andrew Boiee48bc562018-12-03 10:13:39 -0800429 if name.startswith("__device_sys_init"):
430 # Boot-time initialization function; not an actual device
431 continue
432
Andy Gross826389a2018-01-22 14:09:48 -0600433 type_offset = die_get_type_offset(die)
434
435 # Is this a kernel object, or a structure containing kernel
436 # objects?
437 if type_offset not in type_env:
438 continue
439
440 if "DW_AT_declaration" in die.attributes:
Andrew Boie577d5dd2018-05-16 15:52:37 -0700441 # Extern declaration, only used indirectly
442 extern_env[die.offset] = die
443 continue
444
445 if "DW_AT_location" not in die.attributes:
446 self.debug_die(
447 die,
448 "No location information for object '%s'; possibly"
449 " stack allocated" % name)
450 continue
451
452 loc = die.attributes["DW_AT_location"]
453 if loc.form != "DW_FORM_exprloc" and \
454 loc.form != "DW_FORM_block1":
455 self.debug_die(
456 die,
457 "kernel object '%s' unexpected location format" %
458 name)
459 continue
460
461 opcode = loc.value[0]
462 if opcode != DW_OP_addr:
463
464 # Check if frame pointer offset DW_OP_fbreg
465 if opcode == DW_OP_fbreg:
466 self.debug_die(die, "kernel object '%s' found on stack" %
Anas Nashif6af68b32018-09-16 14:52:34 -0500467 name)
Andy Gross826389a2018-01-22 14:09:48 -0600468 else:
Andy Gross826389a2018-01-22 14:09:48 -0600469 self.debug_die(
470 die,
Andrew Boie577d5dd2018-05-16 15:52:37 -0700471 "kernel object '%s' unexpected exprloc opcode %s" %
472 (name, hex(opcode)))
473 continue
Andy Gross826389a2018-01-22 14:09:48 -0600474
Andrew Boie577d5dd2018-05-16 15:52:37 -0700475 addr = (loc.value[1] | (loc.value[2] << 8) |
476 (loc.value[3] << 16) | (loc.value[4] << 24))
Andy Gross826389a2018-01-22 14:09:48 -0600477
478 if addr == 0:
479 # Never linked; gc-sections deleted it
480 continue
481
Andy Gross826389a2018-01-22 14:09:48 -0600482 type_obj = type_env[type_offset]
483 objs = type_obj.get_kobjects(addr)
484 all_objs.update(objs)
485
Anas Nashif6af68b32018-09-16 14:52:34 -0500486 self.debug("symbol '%s' at %s contains %d object(s)"
487 % (name, hex(addr), len(objs)))
Andy Gross826389a2018-01-22 14:09:48 -0600488
489 # Step 4: objs is a dictionary mapping variable memory addresses to
490 # their associated type objects. Now that we have seen all variables
491 # and can properly look up API structs, convert this into a dictionary
492 # mapping variables to the C enumeration of what kernel object type it
493 # is.
494 ret = {}
495 for addr, ko in all_objs.items():
496 # API structs don't get into the gperf table
497 if ko.type_obj.api:
498 continue
499
Andrew Boiec235e162019-03-27 14:27:24 -0700500 _, user_ram_allowed = kobjects[ko.type_obj.name]
501 if (not user_ram_allowed and
Wentong Wu859ca422019-07-05 17:49:02 +0800502 (addr >= app_smem_start and addr < app_smem_end)):
Andrew Boiec235e162019-03-27 14:27:24 -0700503
504 self.debug_die(die,
505 "object '%s' found in invalid location %s"
506 % (name, hex(addr)))
507 continue
508
Andy Gross826389a2018-01-22 14:09:48 -0600509 if ko.type_obj.name != "device":
510 # Not a device struct so we immediately know its type
511 ko.type_name = kobject_to_enum(ko.type_obj.name)
512 ret[addr] = ko
513 continue
514
515 # Device struct. Need to get the address of its API struct,
516 # if it has one.
517 apiaddr = device_get_api_addr(self.elf, addr)
518 if apiaddr not in all_objs:
Andrew Boiec7b73632018-12-03 08:37:27 -0800519 if apiaddr == 0:
520 self.debug("device instance at 0x%x has no associated subsystem"
Ulf Magnussond8314152019-03-22 00:14:40 +0100521 % addr)
Andrew Boiec7b73632018-12-03 08:37:27 -0800522 else:
523 self.debug("device instance at 0x%x has unknown API 0x%x"
Ulf Magnussond8314152019-03-22 00:14:40 +0100524 % (addr, apiaddr))
Andy Gross826389a2018-01-22 14:09:48 -0600525 # API struct does not correspond to a known subsystem, skip it
526 continue
527
528 apiobj = all_objs[apiaddr]
529 ko.type_name = subsystem_to_enum(apiobj.type_obj.name)
530 ret[addr] = ko
531
532 self.debug("found %d kernel object instances total" % len(ret))
Marc Herbertf78288b2019-03-05 14:31:44 -0800533
534 # 1. Before python 3.7 dict order is not guaranteed. With Python
535 # 3.5 it doesn't seem random with *integer* keys but can't
536 # rely on that.
537 # 2. OrderedDict means _insertion_ order, so not enough because
538 # built from other (random!) dicts: need to _sort_ first.
539 # 3. Sorting memory address looks good.
540 return OrderedDict(sorted(ret.items()))
Andy Gross826389a2018-01-22 14:09:48 -0600541
542 def get_symbols(self):
543 for section in self.elf.iter_sections():
544 if isinstance(section, SymbolTableSection):
Ulf Magnusson425998b2019-03-20 20:24:21 +0100545 return {sym.name: sym.entry.st_value
546 for sym in section.iter_symbols()}
Andy Gross826389a2018-01-22 14:09:48 -0600547
548 raise LookupError("Could not find symbol table")
549
550 def debug(self, text):
551 if not self.verbose:
552 return
553 sys.stdout.write(scr + ": " + text + "\n")
554
555 def error(self, text):
556 sys.stderr.write("%s ERROR: %s\n" % (scr, text))
557 sys.exit(1)
558
559 def debug_die(self, die, text):
560 fn, ln = get_filename_lineno(die)
561
562 self.debug(str(die))
563 self.debug("File '%s', line %d:" % (fn, ln))
564 self.debug(" %s" % text)
565
566 def get_thread_counter(self):
567 return thread_counter
Andrew Boief0835672019-03-27 15:44:52 -0700568
569 def get_sys_mutex_counter(self):
570 return sys_mutex_counter
Wentong Wu5611e922019-06-20 23:51:27 +0800571
572 def get_futex_counter(self):
573 return futex_counter