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