blob: abed7ba6f58a0548f811154b3dc4f8a67d90cb1b [file] [log] [blame]
Jerome Forissier733a15f2017-05-19 17:40:17 +02001#!/usr/bin/env python
Jerome Forissier1bb92982017-12-15 14:27:02 +01002# SPDX-License-Identifier: BSD-2-Clause
Jerome Forissier733a15f2017-05-19 17:40:17 +02003#
4# Copyright (c) 2017, Linaro Limited
Jerome Forissier733a15f2017-05-19 17:40:17 +02005#
Jerome Forissier733a15f2017-05-19 17:40:17 +02006
7
8import argparse
9import glob
Jerome Forissier157e6212017-08-24 15:49:16 +020010import os
Jerome Forissier733a15f2017-05-19 17:40:17 +020011import re
12import subprocess
13import sys
14
Jerome Forissier733a15f2017-05-19 17:40:17 +020015CALL_STACK_RE = re.compile('Call stack:')
Joakim Becha2b984b2017-12-15 14:34:56 +010016# This gets the address from lines looking like this:
17# E/TC:0 0x001044a8
18STACK_ADDR_RE = re.compile(r'[UEIDFM]/T[AC]:.*(?P<addr>0x[0-9a-f]+)')
Jerome Forissier142c5cc2017-08-24 15:07:17 +020019ABORT_ADDR_RE = re.compile('-abort at address (?P<addr>0x[0-9a-f]+)')
Jerome Forissier30999122017-08-25 18:42:58 +020020REGION_RE = re.compile('region [0-9]+: va (?P<addr>0x[0-9a-f]+) '
Jerome Forissierae252462018-05-25 15:07:28 +020021 'pa 0x[0-9a-f]+ size (?P<size>0x[0-9a-f]+)'
22 '( flags .{6} (\[(?P<elf_idx>[0-9]+)\])?)?')
23ELF_LIST_RE = re.compile(r'\[(?P<idx>[0-9]+)\] (?P<uuid>[0-9a-f\-]+)'
24 ' @ (?P<load_addr>0x[0-9a-f\-]+)')
Jerome Forissier733a15f2017-05-19 17:40:17 +020025
26epilog = '''
Jerome Forissier0c5bedb2018-02-15 17:20:36 +010027This scripts reads an OP-TEE abort or panic message from stdin and adds debug
28information to the output, such as '<function> at <file>:<line>' next to each
29address in the call stack. Any message generated by OP-TEE and containing a
30call stack can in principle be processed by this script. This currently
31includes aborts and panics from the TEE core as well as from any TA.
32The paths provided on the command line are used to locate the appropriate ELF
33binary (tee.elf or Trusted Application). The GNU binutils (addr2line, objdump,
34nm) are used to extract the debug info.
Jerome Forissier733a15f2017-05-19 17:40:17 +020035
Jerome Forissier0c5bedb2018-02-15 17:20:36 +010036OP-TEE abort and panic messages are sent to the secure console. They look like
37the following:
Jerome Forissier733a15f2017-05-19 17:40:17 +020038
Jerome Forissier0c5bedb2018-02-15 17:20:36 +010039 E/TC:0 User TA data-abort at address 0xffffdecd (alignment fault)
Jerome Forissier733a15f2017-05-19 17:40:17 +020040 ...
Jerome Forissier0c5bedb2018-02-15 17:20:36 +010041 E/TC:0 Call stack:
42 E/TC:0 0x4000549e
43 E/TC:0 0x40001f4b
44 E/TC:0 0x4000273f
45 E/TC:0 0x40005da7
Jerome Forissier733a15f2017-05-19 17:40:17 +020046
47Inspired by a script of the same name by the Chromium project.
48
49Sample usage:
50
51 $ scripts/symbolize.py -d out/arm-plat-hikey/core -d ../optee_test/out/ta/*
52 <paste whole dump here>
53 ^D
54'''
55
Jerome Forissierae252462018-05-25 15:07:28 +020056
Jerome Forissier733a15f2017-05-19 17:40:17 +020057def get_args():
58 parser = argparse.ArgumentParser(
59 formatter_class=argparse.RawDescriptionHelpFormatter,
60 description='Symbolizes OP-TEE abort dumps',
61 epilog=epilog)
62 parser.add_argument('-d', '--dir', action='append', nargs='+',
63 help='Search for ELF file in DIR. tee.elf is needed to decode '
64 'a TEE Core or pseudo-TA abort, while <TA_uuid>.elf is required '
Jerome Forissier157e6212017-08-24 15:49:16 +020065 'if a user-mode TA has crashed. For convenience, ELF files '
66 'may also be given.')
Jerome Forissier5f7df502018-02-15 16:57:55 +010067 parser.add_argument('-s', '--strip_path', nargs='?',
68 help='Strip STRIP_PATH from file paths (default: current directory, '
69 'use -s with no argument to show full paths)',
70 default=os.getcwd())
Jerome Forissier733a15f2017-05-19 17:40:17 +020071
72 return parser.parse_args()
73
Jerome Forissierae252462018-05-25 15:07:28 +020074
Jerome Forissier733a15f2017-05-19 17:40:17 +020075class Symbolizer(object):
76 def __init__(self, out, dirs, strip_path):
77 self._out = out
78 self._dirs = dirs
79 self._strip_path = strip_path
80 self._addr2line = None
Jerome Forissier733a15f2017-05-19 17:40:17 +020081 self.reset()
82
83 def get_elf(self, elf_or_uuid):
84 if not elf_or_uuid.endswith('.elf'):
85 elf_or_uuid += '.elf'
86 for d in self._dirs:
Jerome Forissier157e6212017-08-24 15:49:16 +020087 if d.endswith(elf_or_uuid) and os.path.isfile(d):
88 return d
Jerome Forissier733a15f2017-05-19 17:40:17 +020089 elf = glob.glob(d + '/' + elf_or_uuid)
90 if elf:
91 return elf[0]
92
Jerome Forissierd7204312017-09-04 17:58:52 +020093 def set_arch(self):
94 if self._arch:
95 return
Jerome Forissierae252462018-05-25 15:07:28 +020096 elf = self.get_elf(self._elfs[0][0])
97 if elf is None:
98 return
99 p = subprocess.Popen(['file', self.get_elf(self._elfs[0][0])],
100 stdout=subprocess.PIPE)
101 output = p.stdout.readlines()
102 p.terminate()
103 if 'ARM aarch64,' in output[0]:
104 self._arch = 'aarch64-linux-gnu-'
105 elif 'ARM,' in output[0]:
106 self._arch = 'arm-linux-gnueabihf-'
Jerome Forissierd7204312017-09-04 17:58:52 +0200107
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200108 def arch_prefix(self, cmd):
Jerome Forissierd7204312017-09-04 17:58:52 +0200109 self.set_arch()
Jerome Forissierae252462018-05-25 15:07:28 +0200110 if self._arch is None:
111 return ''
Jerome Forissierd7204312017-09-04 17:58:52 +0200112 return self._arch + cmd
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200113
Jerome Forissierae252462018-05-25 15:07:28 +0200114 def spawn_addr2line(self, elf_name):
115 if elf_name is None:
116 return
117 if self._addr2line_elf_name is elf_name:
118 return
119 if self._addr2line:
120 self._addr2line.terminate
121 self._addr2line = None
122 elf = self.get_elf(elf_name)
123 if not elf:
124 return
125 cmd = self.arch_prefix('addr2line')
126 if not cmd:
127 return
128 self._addr2line = subprocess.Popen([cmd, '-f', '-p', '-e', elf],
129 stdin=subprocess.PIPE,
130 stdout=subprocess.PIPE)
131 self._addr2line_elf_name = elf_name
132
133 # If addr falls into a region that maps a TA ELF file, return the load
134 # address of that file.
135 def elf_load_addr(self, addr):
136 if self._regions:
137 for r in self._regions:
138 r_addr = int(r[0], 16)
139 r_size = int(r[1], 16)
140 i_addr = int(addr, 16)
141 if (i_addr >= r_addr and i_addr < (r_addr + r_size)):
142 # Found region
143 elf_idx = r[2]
144 if elf_idx is not None:
145 return self._elfs[int(elf_idx)][1]
146 return None
147 else:
148 # tee.elf
149 return '0x0'
150
151 def elf_for_addr(self, addr):
152 l_addr = self.elf_load_addr(addr)
153 if l_addr is None:
154 return None
155 if l_addr is '0x0':
156 return 'tee.elf'
157 for k in self._elfs:
158 e = self._elfs[k]
159 if int(e[1], 16) == int(l_addr, 16):
160 return e[0]
161 return None
Jerome Forissier733a15f2017-05-19 17:40:17 +0200162
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200163 def subtract_load_addr(self, addr):
Jerome Forissierae252462018-05-25 15:07:28 +0200164 l_addr = self.elf_load_addr(addr)
165 if l_addr is None:
166 return None
167 if int(l_addr, 16) > int(addr, 16):
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200168 return ''
Jerome Forissierae252462018-05-25 15:07:28 +0200169 return '0x{:x}'.format(int(addr, 16) - int(l_addr, 16))
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200170
171 def resolve(self, addr):
172 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200173 self.spawn_addr2line(self.elf_for_addr(addr))
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200174 if not reladdr or not self._addr2line:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200175 return '???'
176 try:
177 print >> self._addr2line.stdin, reladdr
178 ret = self._addr2line.stdout.readline().rstrip('\n')
179 except IOError:
180 ret = '!!!'
181 return ret
182
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200183 def symbol_plus_offset(self, addr):
184 ret = ''
185 prevsize = 0
186 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200187 elf_name = self.elf_for_addr(addr)
188 if elf_name is None:
189 return ''
190 elf = self.get_elf(elf_name)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200191 cmd = self.arch_prefix('nm')
192 if not reladdr or not elf or not cmd:
193 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200194 ireladdr = int(reladdr, 16)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200195 nm = subprocess.Popen([cmd, '--numeric-sort', '--print-size', elf],
Jerome Forissierae252462018-05-25 15:07:28 +0200196 stdin=subprocess.PIPE,
197 stdout=subprocess.PIPE)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200198 for line in iter(nm.stdout.readline, ''):
199 try:
200 addr, size, _, name = line.split()
201 except:
202 # Size is missing
Jerome Forissierb4815422018-06-20 09:43:33 +0200203 try:
204 addr, _, name = line.split()
205 size = '0'
206 except:
207 # E.g., undefined (external) symbols (line = "U symbol")
208 continue
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200209 iaddr = int(addr, 16)
210 isize = int(size, 16)
211 if iaddr == ireladdr:
212 ret = name
213 break
214 if iaddr < ireladdr and iaddr + isize >= ireladdr:
215 offs = ireladdr - iaddr
216 ret = name + '+' + str(offs)
217 break
218 if iaddr > ireladdr and prevsize == 0:
219 offs = iaddr + ireladdr
220 ret = prevname + '+' + str(offs)
221 break
222 prevsize = size
223 prevname = name
224 nm.terminate()
225 return ret
226
227 def section_plus_offset(self, addr):
228 ret = ''
229 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200230 elf_name = self.elf_for_addr(addr)
231 if elf_name is None:
232 return ''
233 elf = self.get_elf(elf_name)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200234 cmd = self.arch_prefix('objdump')
235 if not reladdr or not elf or not cmd:
236 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200237 iaddr = int(reladdr, 16)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200238 objdump = subprocess.Popen([cmd, '--section-headers', elf],
Jerome Forissierae252462018-05-25 15:07:28 +0200239 stdin=subprocess.PIPE,
240 stdout=subprocess.PIPE)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200241 for line in iter(objdump.stdout.readline, ''):
242 try:
243 idx, name, size, vma, lma, offs, algn = line.split()
244 except:
Jerome Forissierae252462018-05-25 15:07:28 +0200245 continue
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200246 ivma = int(vma, 16)
247 isize = int(size, 16)
248 if ivma == iaddr:
249 ret = name
250 break
251 if ivma < iaddr and ivma + isize >= iaddr:
252 offs = iaddr - ivma
253 ret = name + '+' + str(offs)
254 break
255 objdump.terminate()
256 return ret
257
258 def process_abort(self, line):
259 ret = ''
260 match = re.search(ABORT_ADDR_RE, line)
261 addr = match.group('addr')
262 pre = match.start('addr')
263 post = match.end('addr')
264 sym = self.symbol_plus_offset(addr)
265 sec = self.section_plus_offset(addr)
266 if sym or sec:
267 ret += line[:pre]
268 ret += addr
269 if sym:
270 ret += ' ' + sym
271 if sec:
272 ret += ' ' + sec
273 ret += line[post:]
274 return ret
275
Jerome Forissier30999122017-08-25 18:42:58 +0200276 # Return all ELF sections with the ALLOC flag
Jerome Forissierae252462018-05-25 15:07:28 +0200277 def read_sections(self, elf_name):
278 if elf_name is None:
Jerome Forissier30999122017-08-25 18:42:58 +0200279 return
Jerome Forissierae252462018-05-25 15:07:28 +0200280 if elf_name in self._sections:
281 return
282 elf = self.get_elf(elf_name)
Jerome Forissier30999122017-08-25 18:42:58 +0200283 cmd = self.arch_prefix('objdump')
284 if not elf or not cmd:
285 return
Jerome Forissierae252462018-05-25 15:07:28 +0200286 self._sections[elf_name] = []
Jerome Forissier30999122017-08-25 18:42:58 +0200287 objdump = subprocess.Popen([cmd, '--section-headers', elf],
Jerome Forissierae252462018-05-25 15:07:28 +0200288 stdin=subprocess.PIPE,
289 stdout=subprocess.PIPE)
Jerome Forissier30999122017-08-25 18:42:58 +0200290 for line in iter(objdump.stdout.readline, ''):
291 try:
292 _, name, size, vma, _, _, _ = line.split()
293 except:
294 if 'ALLOC' in line:
Jerome Forissierae252462018-05-25 15:07:28 +0200295 self._sections[elf_name].append([name, int(vma, 16),
296 int(size, 16)])
Jerome Forissier30999122017-08-25 18:42:58 +0200297
298 def overlaps(self, section, addr, size):
299 sec_addr = section[1]
300 sec_size = section[2]
301 if not size or not sec_size:
302 return False
Jerome Forissierae252462018-05-25 15:07:28 +0200303 return ((addr <= (sec_addr + sec_size - 1)) and
304 ((addr + size - 1) >= sec_addr))
Jerome Forissier30999122017-08-25 18:42:58 +0200305
Jerome Forissierae252462018-05-25 15:07:28 +0200306 def sections_in_region(self, addr, size, elf_idx):
Jerome Forissier30999122017-08-25 18:42:58 +0200307 ret = ''
308 addr = self.subtract_load_addr(addr)
309 if not addr:
310 return ''
311 iaddr = int(addr, 16)
312 isize = int(size, 16)
Jerome Forissierae252462018-05-25 15:07:28 +0200313 elf = self._elfs[int(elf_idx)][0]
314 if elf is None:
315 return ''
316 self.read_sections(elf)
317 if elf not in self._sections:
318 return ''
319 for s in self._sections[elf]:
Jerome Forissier30999122017-08-25 18:42:58 +0200320 if self.overlaps(s, iaddr, isize):
321 ret += ' ' + s[0]
322 return ret
323
Jerome Forissier733a15f2017-05-19 17:40:17 +0200324 def reset(self):
325 self._call_stack_found = False
Jerome Forissier733a15f2017-05-19 17:40:17 +0200326 if self._addr2line:
327 self._addr2line.terminate()
328 self._addr2line = None
Jerome Forissierae252462018-05-25 15:07:28 +0200329 self._addr2line_elf_name = None
Jerome Forissierd7204312017-09-04 17:58:52 +0200330 self._arch = None
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200331 self._saved_abort_line = ''
Jerome Forissierae252462018-05-25 15:07:28 +0200332 self._sections = {} # {elf_name: [[name, addr, size], ...], ...}
333 self._regions = [] # [[addr, size, elf_idx, saved line], ...]
334 self._elfs = {0: ["tee.elf", 0]} # {idx: [uuid, load_addr], ...}
Jerome Forissier733a15f2017-05-19 17:40:17 +0200335
Jerome Forissier095567e2018-05-29 17:42:34 +0200336
337 def pretty_print_path(self, path):
338 if self._strip_path:
339 return re.sub(re.escape(self._strip_path) + '/*', '', path)
340 return path
341
342
Jerome Forissier733a15f2017-05-19 17:40:17 +0200343 def write(self, line):
344 if self._call_stack_found:
345 match = re.search(STACK_ADDR_RE, line)
346 if match:
347 addr = match.group('addr')
348 pre = match.start('addr')
349 post = match.end('addr')
350 self._out.write(line[:pre])
351 self._out.write(addr)
352 res = self.resolve(addr)
Jerome Forissier095567e2018-05-29 17:42:34 +0200353 res = self.pretty_print_path(res)
Jerome Forissier733a15f2017-05-19 17:40:17 +0200354 self._out.write(' ' + res)
355 self._out.write(line[post:])
356 return
357 else:
358 self.reset()
Jerome Forissier30999122017-08-25 18:42:58 +0200359 match = re.search(REGION_RE, line)
360 if match:
Jerome Forissierae252462018-05-25 15:07:28 +0200361 # Region table: save info for later processing once
362 # we know which UUID corresponds to which ELF index
Jerome Forissier30999122017-08-25 18:42:58 +0200363 addr = match.group('addr')
364 size = match.group('size')
Jerome Forissierae252462018-05-25 15:07:28 +0200365 elf_idx = match.group('elf_idx')
366 self._regions.append([addr, size, elf_idx, line])
367 return
368 match = re.search(ELF_LIST_RE, line)
369 if match:
370 # ELF list: save info for later. Region table and ELF list
371 # will be displayed when the call stack is reached
372 i = int(match.group('idx'))
373 self._elfs[i] = [match.group('uuid'), match.group('load_addr'),
374 line]
Jerome Forissier30999122017-08-25 18:42:58 +0200375 return
Jerome Forissier733a15f2017-05-19 17:40:17 +0200376 match = re.search(CALL_STACK_RE, line)
377 if match:
378 self._call_stack_found = True
Jerome Forissierae252462018-05-25 15:07:28 +0200379 if self._regions:
380 for r in self._regions:
381 r_addr = r[0]
382 r_size = r[1]
383 elf_idx = r[2]
384 saved_line = r[3]
385 if elf_idx is None:
386 self._out.write(saved_line)
387 else:
388 self._out.write(saved_line.strip() +
389 self.sections_in_region(r_addr,
390 r_size,
391 elf_idx) +
392 '\n')
393 if self._elfs:
394 for k in self._elfs:
395 e = self._elfs[k]
396 if (len(e) >= 3):
Jerome Forissier095567e2018-05-29 17:42:34 +0200397 self._out.write(e[2].strip())
398 elf = self.get_elf(e[0])
399 if elf:
400 rpath = os.path.realpath(elf)
401 path = self.pretty_print_path(rpath)
402 self._out.write(' (' + path + ')')
403 self._out.write('\n')
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200404 # Here is a good place to resolve the abort address because we
405 # have all the information we need
406 if self._saved_abort_line:
407 self._out.write(self.process_abort(self._saved_abort_line))
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200408 match = re.search(ABORT_ADDR_RE, line)
409 if match:
Jerome Forissier27b83ad2017-10-06 13:37:35 +0200410 self.reset()
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200411 # At this point the arch and TA load address are unknown.
412 # Save the line so We can translate the abort address later.
413 self._saved_abort_line = line
Jerome Forissier733a15f2017-05-19 17:40:17 +0200414 self._out.write(line)
415
416 def flush(self):
417 self._out.flush()
418
Jerome Forissierae252462018-05-25 15:07:28 +0200419
Jerome Forissier733a15f2017-05-19 17:40:17 +0200420def main():
421 args = get_args()
422 if args.dir:
423 # Flatten list in case -d is used several times *and* with multiple
424 # arguments
425 args.dirs = [item for sublist in args.dir for item in sublist]
426 else:
427 args.dirs = []
428 symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path)
429
430 for line in sys.stdin:
431 symbolizer.write(line)
432 symbolizer.flush()
433
434if __name__ == "__main__":
435 main()