blob: 005d9a21c72cc9e465c45db7b17a8fb2acf4e6ef [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
Andy Gross826389a2018-01-22 14:09:48 -060041
42# Global type environment. Populated by pass 1.
43type_env = {}
Andrew Boie577d5dd2018-05-16 15:52:37 -070044extern_env = {}
Andy Gross826389a2018-01-22 14:09:48 -060045kobjects = {}
46subsystems = {}
47
48# --- debug stuff ---
49
50scr = os.path.basename(sys.argv[0])
51
52# --- type classes ----
53
54
55class KobjectInstance:
56 def __init__(self, type_obj, addr):
57 global thread_counter
Andrew Boief0835672019-03-27 15:44:52 -070058 global sys_mutex_counter
Andy Gross826389a2018-01-22 14:09:48 -060059
60 self.addr = addr
61 self.type_obj = type_obj
62
63 # Type name determined later since drivers needs to look at the
64 # API struct address
65 self.type_name = None
66
67 if self.type_obj.name == "k_thread":
68 # Assign an ID for this thread object, used to track its
69 # permissions to other kernel objects
70 self.data = thread_counter
71 thread_counter = thread_counter + 1
Andrew Boief0835672019-03-27 15:44:52 -070072 elif self.type_obj.name == "sys_mutex":
73 self.data = "(u32_t)(&kernel_mutexes[%d])" % sys_mutex_counter
74 sys_mutex_counter += 1
Andy Gross826389a2018-01-22 14:09:48 -060075 else:
76 self.data = 0
77
78
79class KobjectType:
80 def __init__(self, offset, name, size, api=False):
81 self.name = name
82 self.size = size
83 self.offset = offset
84 self.api = api
85
86 def __repr__(self):
87 return "<kobject %s>" % self.name
88
89 def has_kobject(self):
90 return True
91
92 def get_kobjects(self, addr):
93 return {addr: KobjectInstance(self, addr)}
94
95
96class ArrayType:
97 def __init__(self, offset, elements, member_type):
98 self.elements = elements
99 self.member_type = member_type
100 self.offset = offset
101
102 def __repr__(self):
Ulf Magnusson7ffd6282019-03-20 21:56:09 +0100103 return "<array of %d>" % self.member_type
Andy Gross826389a2018-01-22 14:09:48 -0600104
105 def has_kobject(self):
106 if self.member_type not in type_env:
107 return False
108
109 return type_env[self.member_type].has_kobject()
110
111 def get_kobjects(self, addr):
112 mt = type_env[self.member_type]
113
114 # Stacks are arrays of _k_stack_element_t but we want to treat
115 # the whole array as one kernel object (a thread stack)
116 # Data value gets set to size of entire region
117 if isinstance(mt, KobjectType) and mt.name == STACK_TYPE:
118 # An array of stacks appears as a multi-dimensional array.
119 # The last size is the size of each stack. We need to track
120 # each stack within the array, not as one huge stack object.
121 *dimensions, stacksize = self.elements
122 num_members = 1
123 for e in dimensions:
124 num_members = num_members * e
125
126 ret = {}
127 for i in range(num_members):
128 a = addr + (i * stacksize)
129 o = mt.get_kobjects(a)
130 o[a].data = stacksize
131 ret.update(o)
132 return ret
133
134 objs = {}
135
136 # Multidimensional array flattened out
137 num_members = 1
138 for e in self.elements:
139 num_members = num_members * e
140
141 for i in range(num_members):
142 objs.update(mt.get_kobjects(addr + (i * mt.size)))
143 return objs
144
145
146class AggregateTypeMember:
147 def __init__(self, offset, member_name, member_type, member_offset):
148 self.member_name = member_name
149 self.member_type = member_type
Andrew Boiee144a682018-05-21 15:51:31 -0700150 if isinstance(member_offset, list):
ruuddw8364b222018-05-25 15:53:27 +0200151 # DWARF v2, location encoded as set of operations
152 # only "DW_OP_plus_uconst" with ULEB128 argument supported
Anas Nashif6af68b32018-09-16 14:52:34 -0500153 if member_offset[0] == 0x23:
ruuddw8364b222018-05-25 15:53:27 +0200154 self.member_offset = member_offset[1] & 0x7f
155 for i in range(1, len(member_offset)-1):
Ulf Magnussonba312fe2019-03-20 19:30:29 +0100156 if member_offset[i] & 0x80:
Anas Nashif6af68b32018-09-16 14:52:34 -0500157 self.member_offset += (
158 member_offset[i+1] & 0x7f) << i*7
ruuddw8364b222018-05-25 15:53:27 +0200159 else:
Andrew Boiecf91f8c2019-03-27 13:40:51 -0700160 raise Exception("not yet supported location operation (%s:%d:%d)" %
161 (self.member_name, self.member_type, member_offset[0]))
Andrew Boiee144a682018-05-21 15:51:31 -0700162 else:
163 self.member_offset = member_offset
Andy Gross826389a2018-01-22 14:09:48 -0600164
165 def __repr__(self):
166 return "<member %s, type %d, offset %d>" % (
167 self.member_name, self.member_type, self.member_offset)
168
169 def has_kobject(self):
170 if self.member_type not in type_env:
171 return False
172
173 return type_env[self.member_type].has_kobject()
174
175 def get_kobjects(self, addr):
176 mt = type_env[self.member_type]
177 return mt.get_kobjects(addr + self.member_offset)
178
179
180class ConstType:
181 def __init__(self, child_type):
182 self.child_type = child_type
183
184 def __repr__(self):
185 return "<const %d>" % self.child_type
186
187 def has_kobject(self):
188 if self.child_type not in type_env:
189 return False
190
191 return type_env[self.child_type].has_kobject()
192
193 def get_kobjects(self, addr):
194 return type_env[self.child_type].get_kobjects(addr)
195
196
197class AggregateType:
198 def __init__(self, offset, name, size):
199 self.name = name
200 self.size = size
201 self.offset = offset
202 self.members = []
203
204 def add_member(self, member):
205 self.members.append(member)
206
207 def __repr__(self):
208 return "<struct %s, with %s>" % (self.name, self.members)
209
210 def has_kobject(self):
211 result = False
212
213 bad_members = []
214
215 for member in self.members:
216 if member.has_kobject():
217 result = True
218 else:
219 bad_members.append(member)
220 # Don't need to consider this again, just remove it
221
222 for bad_member in bad_members:
223 self.members.remove(bad_member)
224
225 return result
226
227 def get_kobjects(self, addr):
228 objs = {}
229 for member in self.members:
230 objs.update(member.get_kobjects(addr))
231 return objs
232
233
234# --- helper functions for getting data from DIEs ---
235
Andrew Boie577d5dd2018-05-16 15:52:37 -0700236def die_get_spec(die):
237 if 'DW_AT_specification' not in die.attributes:
238 return None
239
240 spec_val = die.attributes["DW_AT_specification"].value
241
242 # offset of the DW_TAG_variable for the extern declaration
243 offset = spec_val + die.cu.cu_offset
244
245 return extern_env.get(offset)
246
247
Andy Gross826389a2018-01-22 14:09:48 -0600248def die_get_name(die):
249 if 'DW_AT_name' not in die.attributes:
Andrew Boie577d5dd2018-05-16 15:52:37 -0700250 die = die_get_spec(die)
251 if not die:
252 return None
253
Andy Gross826389a2018-01-22 14:09:48 -0600254 return die.attributes["DW_AT_name"].value.decode("utf-8")
255
256
257def die_get_type_offset(die):
258 if 'DW_AT_type' not in die.attributes:
Andrew Boie577d5dd2018-05-16 15:52:37 -0700259 die = die_get_spec(die)
260 if not die:
261 return None
Andy Gross826389a2018-01-22 14:09:48 -0600262
263 return die.attributes["DW_AT_type"].value + die.cu.cu_offset
264
265
266def die_get_byte_size(die):
267 if 'DW_AT_byte_size' not in die.attributes:
268 return 0
269
270 return die.attributes["DW_AT_byte_size"].value
271
272
273def analyze_die_struct(die):
274 name = die_get_name(die) or "<anon>"
275 offset = die.offset
276 size = die_get_byte_size(die)
277
278 # Incomplete type
279 if not size:
280 return
281
282 if name in kobjects:
283 type_env[offset] = KobjectType(offset, name, size)
284 elif name in subsystems:
285 type_env[offset] = KobjectType(offset, name, size, api=True)
286 else:
287 at = AggregateType(offset, name, size)
288 type_env[offset] = at
289
290 for child in die.iter_children():
291 if child.tag != "DW_TAG_member":
292 continue
293 child_type = die_get_type_offset(child)
294 member_offset = \
295 child.attributes["DW_AT_data_member_location"].value
296 cname = die_get_name(child) or "<anon>"
297 m = AggregateTypeMember(child.offset, cname, child_type,
298 member_offset)
299 at.add_member(m)
300
301 return
302
303
304def analyze_die_const(die):
305 type_offset = die_get_type_offset(die)
306 if not type_offset:
307 return
308
309 type_env[die.offset] = ConstType(type_offset)
310
311
312def analyze_die_array(die):
313 type_offset = die_get_type_offset(die)
314 elements = []
315
316 for child in die.iter_children():
317 if child.tag != "DW_TAG_subrange_type":
318 continue
319 if "DW_AT_upper_bound" not in child.attributes:
320 continue
321
322 ub = child.attributes["DW_AT_upper_bound"]
323 if not ub.form.startswith("DW_FORM_data"):
324 continue
325
326 elements.append(ub.value + 1)
327
328 if not elements:
329 return
330
331 type_env[die.offset] = ArrayType(die.offset, elements, type_offset)
332
333
334def addr_deref(elf, addr):
335 for section in elf.iter_sections():
336 start = section['sh_addr']
337 end = start + section['sh_size']
338
339 if addr >= start and addr < end:
340 data = section.data()
341 offset = addr - start
342 return struct.unpack("<I" if elf.little_endian else ">I",
343 data[offset:offset + 4])[0]
344
345 return 0
346
347
348def device_get_api_addr(elf, addr):
349 return addr_deref(elf, addr + 4)
350
351
352def get_filename_lineno(die):
353 lp_header = die.dwarfinfo.line_program_for_CU(die.cu).header
354 files = lp_header["file_entry"]
355 includes = lp_header["include_directory"]
356
357 fileinfo = files[die.attributes["DW_AT_decl_file"].value - 1]
358 filename = fileinfo.name.decode("utf-8")
359 filedir = includes[fileinfo.dir_index - 1].decode("utf-8")
360
361 path = os.path.join(filedir, filename)
362 lineno = die.attributes["DW_AT_decl_line"].value
363 return (path, lineno)
364
365
366class ElfHelper:
367
368 def __init__(self, filename, verbose, kobjs, subs):
369 self.verbose = verbose
370 self.fp = open(filename, "rb")
371 self.elf = ELFFile(self.fp)
372 self.little_endian = self.elf.little_endian
373 global kobjects
374 global subsystems
375 kobjects = kobjs
376 subsystems = subs
377
378 def find_kobjects(self, syms):
379 if not self.elf.has_dwarf_info():
380 sys.stderr.write("ELF file has no DWARF information\n")
381 sys.exit(1)
382
383 kram_start = syms["__kernel_ram_start"]
384 kram_end = syms["__kernel_ram_end"]
385 krom_start = syms["_image_rom_start"]
386 krom_end = syms["_image_rom_end"]
387
388 di = self.elf.get_dwarf_info()
389
390 variables = []
391
392 # Step 1: collect all type information.
393 for CU in di.iter_CUs():
Ulf Magnusson12ba9df2019-03-19 19:28:24 +0100394 for die in CU.iter_DIEs():
Andy Gross826389a2018-01-22 14:09:48 -0600395 # Unions are disregarded, kernel objects should never be union
396 # members since the memory is not dedicated to that object and
397 # could be something else
398 if die.tag == "DW_TAG_structure_type":
399 analyze_die_struct(die)
400 elif die.tag == "DW_TAG_const_type":
401 analyze_die_const(die)
402 elif die.tag == "DW_TAG_array_type":
403 analyze_die_array(die)
404 elif die.tag == "DW_TAG_variable":
405 variables.append(die)
406
407 # Step 2: filter type_env to only contain kernel objects, or structs
408 # and arrays of kernel objects
409 bad_offsets = []
410 for offset, type_object in type_env.items():
411 if not type_object.has_kobject():
412 bad_offsets.append(offset)
413
414 for offset in bad_offsets:
415 del type_env[offset]
416
417 # Step 3: Now that we know all the types we are looking for, examine
418 # all variables
419 all_objs = {}
420
Andy Gross826389a2018-01-22 14:09:48 -0600421 for die in variables:
422 name = die_get_name(die)
423 if not name:
424 continue
425
Andrew Boiee48bc562018-12-03 10:13:39 -0800426 if name.startswith("__device_sys_init"):
427 # Boot-time initialization function; not an actual device
428 continue
429
Andy Gross826389a2018-01-22 14:09:48 -0600430 type_offset = die_get_type_offset(die)
431
432 # Is this a kernel object, or a structure containing kernel
433 # objects?
434 if type_offset not in type_env:
435 continue
436
437 if "DW_AT_declaration" in die.attributes:
Andrew Boie577d5dd2018-05-16 15:52:37 -0700438 # Extern declaration, only used indirectly
439 extern_env[die.offset] = die
440 continue
441
442 if "DW_AT_location" not in die.attributes:
443 self.debug_die(
444 die,
445 "No location information for object '%s'; possibly"
446 " stack allocated" % name)
447 continue
448
449 loc = die.attributes["DW_AT_location"]
450 if loc.form != "DW_FORM_exprloc" and \
451 loc.form != "DW_FORM_block1":
452 self.debug_die(
453 die,
454 "kernel object '%s' unexpected location format" %
455 name)
456 continue
457
458 opcode = loc.value[0]
459 if opcode != DW_OP_addr:
460
461 # Check if frame pointer offset DW_OP_fbreg
462 if opcode == DW_OP_fbreg:
463 self.debug_die(die, "kernel object '%s' found on stack" %
Anas Nashif6af68b32018-09-16 14:52:34 -0500464 name)
Andy Gross826389a2018-01-22 14:09:48 -0600465 else:
Andy Gross826389a2018-01-22 14:09:48 -0600466 self.debug_die(
467 die,
Andrew Boie577d5dd2018-05-16 15:52:37 -0700468 "kernel object '%s' unexpected exprloc opcode %s" %
469 (name, hex(opcode)))
470 continue
Andy Gross826389a2018-01-22 14:09:48 -0600471
Andrew Boie577d5dd2018-05-16 15:52:37 -0700472 addr = (loc.value[1] | (loc.value[2] << 8) |
473 (loc.value[3] << 16) | (loc.value[4] << 24))
Andy Gross826389a2018-01-22 14:09:48 -0600474
475 if addr == 0:
476 # Never linked; gc-sections deleted it
477 continue
478
Andy Gross826389a2018-01-22 14:09:48 -0600479 type_obj = type_env[type_offset]
480 objs = type_obj.get_kobjects(addr)
481 all_objs.update(objs)
482
Anas Nashif6af68b32018-09-16 14:52:34 -0500483 self.debug("symbol '%s' at %s contains %d object(s)"
484 % (name, hex(addr), len(objs)))
Andy Gross826389a2018-01-22 14:09:48 -0600485
486 # Step 4: objs is a dictionary mapping variable memory addresses to
487 # their associated type objects. Now that we have seen all variables
488 # and can properly look up API structs, convert this into a dictionary
489 # mapping variables to the C enumeration of what kernel object type it
490 # is.
491 ret = {}
492 for addr, ko in all_objs.items():
493 # API structs don't get into the gperf table
494 if ko.type_obj.api:
495 continue
496
Andrew Boiec235e162019-03-27 14:27:24 -0700497 _, user_ram_allowed = kobjects[ko.type_obj.name]
498 if (not user_ram_allowed and
499 (addr < kram_start or addr >= kram_end) and
500 (addr < krom_start or addr >= krom_end)):
501
502 self.debug_die(die,
503 "object '%s' found in invalid location %s"
504 % (name, hex(addr)))
505 continue
506
Andy Gross826389a2018-01-22 14:09:48 -0600507 if ko.type_obj.name != "device":
508 # Not a device struct so we immediately know its type
509 ko.type_name = kobject_to_enum(ko.type_obj.name)
510 ret[addr] = ko
511 continue
512
513 # Device struct. Need to get the address of its API struct,
514 # if it has one.
515 apiaddr = device_get_api_addr(self.elf, addr)
516 if apiaddr not in all_objs:
Andrew Boiec7b73632018-12-03 08:37:27 -0800517 if apiaddr == 0:
518 self.debug("device instance at 0x%x has no associated subsystem"
Ulf Magnussond8314152019-03-22 00:14:40 +0100519 % addr)
Andrew Boiec7b73632018-12-03 08:37:27 -0800520 else:
521 self.debug("device instance at 0x%x has unknown API 0x%x"
Ulf Magnussond8314152019-03-22 00:14:40 +0100522 % (addr, apiaddr))
Andy Gross826389a2018-01-22 14:09:48 -0600523 # API struct does not correspond to a known subsystem, skip it
524 continue
525
526 apiobj = all_objs[apiaddr]
527 ko.type_name = subsystem_to_enum(apiobj.type_obj.name)
528 ret[addr] = ko
529
530 self.debug("found %d kernel object instances total" % len(ret))
Marc Herbertf78288b2019-03-05 14:31:44 -0800531
532 # 1. Before python 3.7 dict order is not guaranteed. With Python
533 # 3.5 it doesn't seem random with *integer* keys but can't
534 # rely on that.
535 # 2. OrderedDict means _insertion_ order, so not enough because
536 # built from other (random!) dicts: need to _sort_ first.
537 # 3. Sorting memory address looks good.
538 return OrderedDict(sorted(ret.items()))
Andy Gross826389a2018-01-22 14:09:48 -0600539
540 def get_symbols(self):
541 for section in self.elf.iter_sections():
542 if isinstance(section, SymbolTableSection):
Ulf Magnusson425998b2019-03-20 20:24:21 +0100543 return {sym.name: sym.entry.st_value
544 for sym in section.iter_symbols()}
Andy Gross826389a2018-01-22 14:09:48 -0600545
546 raise LookupError("Could not find symbol table")
547
548 def debug(self, text):
549 if not self.verbose:
550 return
551 sys.stdout.write(scr + ": " + text + "\n")
552
553 def error(self, text):
554 sys.stderr.write("%s ERROR: %s\n" % (scr, text))
555 sys.exit(1)
556
557 def debug_die(self, die, text):
558 fn, ln = get_filename_lineno(die)
559
560 self.debug(str(die))
561 self.debug("File '%s', line %d:" % (fn, ln))
562 self.debug(" %s" % text)
563
564 def get_thread_counter(self):
565 return thread_counter
Andrew Boief0835672019-03-27 15:44:52 -0700566
567 def get_sys_mutex_counter(self):
568 return sys_mutex_counter