blob: cdb4cdf4d3d4af0bf57a74ddfeeacf76c7cff2c3 [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
Jerome Forissier6e7c2e92018-11-14 11:02:49 +010018STACK_ADDR_RE = re.compile(
Sumit Gargb6bc49c2019-01-17 17:42:14 +053019 r'[UEIDFM]/T[AC]:(\?+|[0-9]+) [0-9]* +(?P<addr>0x[0-9a-f]+)')
Jerome Forissier6e7c2e92018-11-14 11:02:49 +010020ABORT_ADDR_RE = re.compile(r'-abort at address (?P<addr>0x[0-9a-f]+)')
Jerome Forissier444c2032019-03-13 18:00:56 +010021REGION_RE = re.compile(r'region +[0-9]+: va (?P<addr>0x[0-9a-f]+) '
Jerome Forissier6e7c2e92018-11-14 11:02:49 +010022 r'pa 0x[0-9a-f]+ size (?P<size>0x[0-9a-f]+)'
23 r'( flags .{6} (\[(?P<elf_idx>[0-9]+)\])?)?')
Jerome Forissierae252462018-05-25 15:07:28 +020024ELF_LIST_RE = re.compile(r'\[(?P<idx>[0-9]+)\] (?P<uuid>[0-9a-f\-]+)'
Jerome Forissier6e7c2e92018-11-14 11:02:49 +010025 r' @ (?P<load_addr>0x[0-9a-f\-]+)')
Jerome Forissier733a15f2017-05-19 17:40:17 +020026
27epilog = '''
Jerome Forissier0c5bedb2018-02-15 17:20:36 +010028This scripts reads an OP-TEE abort or panic message from stdin and adds debug
29information to the output, such as '<function> at <file>:<line>' next to each
30address in the call stack. Any message generated by OP-TEE and containing a
31call stack can in principle be processed by this script. This currently
32includes aborts and panics from the TEE core as well as from any TA.
33The paths provided on the command line are used to locate the appropriate ELF
34binary (tee.elf or Trusted Application). The GNU binutils (addr2line, objdump,
Jerome Forissierf9089762018-10-02 10:12:32 +020035nm) are used to extract the debug info. If the CROSS_COMPILE environment
36variable is set, it is used as a prefix to the binutils tools. That is, the
37script will invoke $(CROSS_COMPILE)addr2line etc. If it is not set however,
38the prefix will be determined automatically for each ELF file based on its
39architecture (arm-linux-gnueabihf-, aarch64-linux-gnu-). The resulting command
40is then expected to be found in the user's PATH.
Jerome Forissier733a15f2017-05-19 17:40:17 +020041
Jerome Forissier0c5bedb2018-02-15 17:20:36 +010042OP-TEE abort and panic messages are sent to the secure console. They look like
43the following:
Jerome Forissier733a15f2017-05-19 17:40:17 +020044
Jerome Forissier0c5bedb2018-02-15 17:20:36 +010045 E/TC:0 User TA data-abort at address 0xffffdecd (alignment fault)
Jerome Forissier733a15f2017-05-19 17:40:17 +020046 ...
Jerome Forissier0c5bedb2018-02-15 17:20:36 +010047 E/TC:0 Call stack:
48 E/TC:0 0x4000549e
49 E/TC:0 0x40001f4b
50 E/TC:0 0x4000273f
51 E/TC:0 0x40005da7
Jerome Forissier733a15f2017-05-19 17:40:17 +020052
53Inspired by a script of the same name by the Chromium project.
54
55Sample usage:
56
57 $ scripts/symbolize.py -d out/arm-plat-hikey/core -d ../optee_test/out/ta/*
58 <paste whole dump here>
59 ^D
60'''
61
Jerome Forissierae252462018-05-25 15:07:28 +020062
Jerome Forissier733a15f2017-05-19 17:40:17 +020063def get_args():
64 parser = argparse.ArgumentParser(
Jerome Forissier6e7c2e92018-11-14 11:02:49 +010065 formatter_class=argparse.RawDescriptionHelpFormatter,
66 description='Symbolizes OP-TEE abort dumps',
67 epilog=epilog)
Jerome Forissier733a15f2017-05-19 17:40:17 +020068 parser.add_argument('-d', '--dir', action='append', nargs='+',
Jerome Forissier1d8c2a42018-09-07 17:05:21 +020069 help='Search for ELF file in DIR. tee.elf is needed '
70 'to decode a TEE Core or pseudo-TA abort, while '
71 '<TA_uuid>.elf is required if a user-mode TA has '
72 'crashed. For convenience, ELF files may also be '
73 'given.')
Jerome Forissier5f7df502018-02-15 16:57:55 +010074 parser.add_argument('-s', '--strip_path', nargs='?',
Jerome Forissier1d8c2a42018-09-07 17:05:21 +020075 help='Strip STRIP_PATH from file paths (default: '
76 'current directory, use -s with no argument to show '
77 'full paths)', default=os.getcwd())
Jerome Forissier733a15f2017-05-19 17:40:17 +020078
79 return parser.parse_args()
80
Jerome Forissierae252462018-05-25 15:07:28 +020081
Jerome Forissier733a15f2017-05-19 17:40:17 +020082class Symbolizer(object):
83 def __init__(self, out, dirs, strip_path):
84 self._out = out
85 self._dirs = dirs
86 self._strip_path = strip_path
87 self._addr2line = None
Jerome Forissier733a15f2017-05-19 17:40:17 +020088 self.reset()
89
Jerome Forissier1cbf7772018-09-07 17:08:23 +020090 def my_Popen(self, cmd):
91 try:
92 return subprocess.Popen(cmd, stdin=subprocess.PIPE,
93 stdout=subprocess.PIPE)
94 except OSError as e:
95 if e.errno == os.errno.ENOENT:
96 print >> sys.stderr, "*** Error:", cmd[0] + \
97 ": command not found"
98 sys.exit(1)
99
Jerome Forissier733a15f2017-05-19 17:40:17 +0200100 def get_elf(self, elf_or_uuid):
101 if not elf_or_uuid.endswith('.elf'):
102 elf_or_uuid += '.elf'
103 for d in self._dirs:
Jerome Forissier157e6212017-08-24 15:49:16 +0200104 if d.endswith(elf_or_uuid) and os.path.isfile(d):
105 return d
Jerome Forissier733a15f2017-05-19 17:40:17 +0200106 elf = glob.glob(d + '/' + elf_or_uuid)
107 if elf:
108 return elf[0]
109
Jerome Forissierd7204312017-09-04 17:58:52 +0200110 def set_arch(self):
111 if self._arch:
112 return
Jerome Forissier6e7c2e92018-11-14 11:02:49 +0100113 self._arch = os.getenv('CROSS_COMPILE')
Etienne Carriere8a6d4a82018-10-01 09:58:31 +0200114 if self._arch:
115 return
Jerome Forissierae252462018-05-25 15:07:28 +0200116 elf = self.get_elf(self._elfs[0][0])
117 if elf is None:
118 return
119 p = subprocess.Popen(['file', self.get_elf(self._elfs[0][0])],
120 stdout=subprocess.PIPE)
121 output = p.stdout.readlines()
122 p.terminate()
123 if 'ARM aarch64,' in output[0]:
124 self._arch = 'aarch64-linux-gnu-'
125 elif 'ARM,' in output[0]:
126 self._arch = 'arm-linux-gnueabihf-'
Jerome Forissierd7204312017-09-04 17:58:52 +0200127
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200128 def arch_prefix(self, cmd):
Jerome Forissierd7204312017-09-04 17:58:52 +0200129 self.set_arch()
Jerome Forissierae252462018-05-25 15:07:28 +0200130 if self._arch is None:
131 return ''
Jerome Forissierd7204312017-09-04 17:58:52 +0200132 return self._arch + cmd
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200133
Jerome Forissierae252462018-05-25 15:07:28 +0200134 def spawn_addr2line(self, elf_name):
135 if elf_name is None:
136 return
137 if self._addr2line_elf_name is elf_name:
138 return
139 if self._addr2line:
140 self._addr2line.terminate
141 self._addr2line = None
142 elf = self.get_elf(elf_name)
143 if not elf:
144 return
145 cmd = self.arch_prefix('addr2line')
146 if not cmd:
147 return
Jerome Forissier1cbf7772018-09-07 17:08:23 +0200148 self._addr2line = self.my_Popen([cmd, '-f', '-p', '-e', elf])
Jerome Forissierae252462018-05-25 15:07:28 +0200149 self._addr2line_elf_name = elf_name
150
151 # If addr falls into a region that maps a TA ELF file, return the load
152 # address of that file.
153 def elf_load_addr(self, addr):
154 if self._regions:
155 for r in self._regions:
156 r_addr = int(r[0], 16)
157 r_size = int(r[1], 16)
158 i_addr = int(addr, 16)
159 if (i_addr >= r_addr and i_addr < (r_addr + r_size)):
160 # Found region
161 elf_idx = r[2]
162 if elf_idx is not None:
163 return self._elfs[int(elf_idx)][1]
164 return None
165 else:
166 # tee.elf
167 return '0x0'
168
169 def elf_for_addr(self, addr):
170 l_addr = self.elf_load_addr(addr)
171 if l_addr is None:
172 return None
173 if l_addr is '0x0':
174 return 'tee.elf'
175 for k in self._elfs:
176 e = self._elfs[k]
177 if int(e[1], 16) == int(l_addr, 16):
178 return e[0]
179 return None
Jerome Forissier733a15f2017-05-19 17:40:17 +0200180
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200181 def subtract_load_addr(self, addr):
Jerome Forissierae252462018-05-25 15:07:28 +0200182 l_addr = self.elf_load_addr(addr)
183 if l_addr is None:
184 return None
185 if int(l_addr, 16) > int(addr, 16):
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200186 return ''
Jerome Forissierae252462018-05-25 15:07:28 +0200187 return '0x{:x}'.format(int(addr, 16) - int(l_addr, 16))
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200188
189 def resolve(self, addr):
190 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200191 self.spawn_addr2line(self.elf_for_addr(addr))
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200192 if not reladdr or not self._addr2line:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200193 return '???'
194 try:
195 print >> self._addr2line.stdin, reladdr
196 ret = self._addr2line.stdout.readline().rstrip('\n')
197 except IOError:
198 ret = '!!!'
199 return ret
200
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200201 def symbol_plus_offset(self, addr):
202 ret = ''
203 prevsize = 0
204 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200205 elf_name = self.elf_for_addr(addr)
206 if elf_name is None:
207 return ''
208 elf = self.get_elf(elf_name)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200209 cmd = self.arch_prefix('nm')
210 if not reladdr or not elf or not cmd:
211 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200212 ireladdr = int(reladdr, 16)
Jerome Forissier1cbf7772018-09-07 17:08:23 +0200213 nm = self.my_Popen([cmd, '--numeric-sort', '--print-size', elf])
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200214 for line in iter(nm.stdout.readline, ''):
215 try:
216 addr, size, _, name = line.split()
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200217 except ValueError:
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200218 # Size is missing
Jerome Forissierb4815422018-06-20 09:43:33 +0200219 try:
220 addr, _, name = line.split()
221 size = '0'
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200222 except ValueError:
Jerome Forissierb4815422018-06-20 09:43:33 +0200223 # E.g., undefined (external) symbols (line = "U symbol")
224 continue
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200225 iaddr = int(addr, 16)
226 isize = int(size, 16)
227 if iaddr == ireladdr:
228 ret = name
229 break
230 if iaddr < ireladdr and iaddr + isize >= ireladdr:
231 offs = ireladdr - iaddr
232 ret = name + '+' + str(offs)
233 break
234 if iaddr > ireladdr and prevsize == 0:
235 offs = iaddr + ireladdr
236 ret = prevname + '+' + str(offs)
237 break
238 prevsize = size
239 prevname = name
240 nm.terminate()
241 return ret
242
243 def section_plus_offset(self, addr):
244 ret = ''
245 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200246 elf_name = self.elf_for_addr(addr)
247 if elf_name is None:
248 return ''
249 elf = self.get_elf(elf_name)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200250 cmd = self.arch_prefix('objdump')
251 if not reladdr or not elf or not cmd:
252 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200253 iaddr = int(reladdr, 16)
Jerome Forissier1cbf7772018-09-07 17:08:23 +0200254 objdump = self.my_Popen([cmd, '--section-headers', elf])
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200255 for line in iter(objdump.stdout.readline, ''):
256 try:
257 idx, name, size, vma, lma, offs, algn = line.split()
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200258 except ValueError:
Jerome Forissierae252462018-05-25 15:07:28 +0200259 continue
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200260 ivma = int(vma, 16)
261 isize = int(size, 16)
262 if ivma == iaddr:
263 ret = name
264 break
265 if ivma < iaddr and ivma + isize >= iaddr:
266 offs = iaddr - ivma
267 ret = name + '+' + str(offs)
268 break
269 objdump.terminate()
270 return ret
271
272 def process_abort(self, line):
273 ret = ''
274 match = re.search(ABORT_ADDR_RE, line)
275 addr = match.group('addr')
276 pre = match.start('addr')
277 post = match.end('addr')
278 sym = self.symbol_plus_offset(addr)
279 sec = self.section_plus_offset(addr)
280 if sym or sec:
281 ret += line[:pre]
282 ret += addr
283 if sym:
284 ret += ' ' + sym
285 if sec:
286 ret += ' ' + sec
287 ret += line[post:]
288 return ret
289
Jerome Forissier30999122017-08-25 18:42:58 +0200290 # Return all ELF sections with the ALLOC flag
Jerome Forissierae252462018-05-25 15:07:28 +0200291 def read_sections(self, elf_name):
292 if elf_name is None:
Jerome Forissier30999122017-08-25 18:42:58 +0200293 return
Jerome Forissierae252462018-05-25 15:07:28 +0200294 if elf_name in self._sections:
295 return
296 elf = self.get_elf(elf_name)
Jerome Forissier30999122017-08-25 18:42:58 +0200297 cmd = self.arch_prefix('objdump')
298 if not elf or not cmd:
299 return
Jerome Forissierae252462018-05-25 15:07:28 +0200300 self._sections[elf_name] = []
Jerome Forissier1cbf7772018-09-07 17:08:23 +0200301 objdump = self.my_Popen([cmd, '--section-headers', elf])
Jerome Forissier30999122017-08-25 18:42:58 +0200302 for line in iter(objdump.stdout.readline, ''):
303 try:
304 _, name, size, vma, _, _, _ = line.split()
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200305 except ValueError:
Jerome Forissier30999122017-08-25 18:42:58 +0200306 if 'ALLOC' in line:
Jerome Forissierae252462018-05-25 15:07:28 +0200307 self._sections[elf_name].append([name, int(vma, 16),
308 int(size, 16)])
Jerome Forissier30999122017-08-25 18:42:58 +0200309
310 def overlaps(self, section, addr, size):
311 sec_addr = section[1]
312 sec_size = section[2]
313 if not size or not sec_size:
314 return False
Jerome Forissierae252462018-05-25 15:07:28 +0200315 return ((addr <= (sec_addr + sec_size - 1)) and
316 ((addr + size - 1) >= sec_addr))
Jerome Forissier30999122017-08-25 18:42:58 +0200317
Jerome Forissierae252462018-05-25 15:07:28 +0200318 def sections_in_region(self, addr, size, elf_idx):
Jerome Forissier30999122017-08-25 18:42:58 +0200319 ret = ''
320 addr = self.subtract_load_addr(addr)
321 if not addr:
322 return ''
323 iaddr = int(addr, 16)
324 isize = int(size, 16)
Jerome Forissierae252462018-05-25 15:07:28 +0200325 elf = self._elfs[int(elf_idx)][0]
326 if elf is None:
327 return ''
328 self.read_sections(elf)
329 if elf not in self._sections:
330 return ''
331 for s in self._sections[elf]:
Jerome Forissier30999122017-08-25 18:42:58 +0200332 if self.overlaps(s, iaddr, isize):
333 ret += ' ' + s[0]
334 return ret
335
Jerome Forissier733a15f2017-05-19 17:40:17 +0200336 def reset(self):
337 self._call_stack_found = False
Jerome Forissier733a15f2017-05-19 17:40:17 +0200338 if self._addr2line:
339 self._addr2line.terminate()
340 self._addr2line = None
Jerome Forissierae252462018-05-25 15:07:28 +0200341 self._addr2line_elf_name = None
Jerome Forissierd7204312017-09-04 17:58:52 +0200342 self._arch = None
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200343 self._saved_abort_line = ''
Jerome Forissierae252462018-05-25 15:07:28 +0200344 self._sections = {} # {elf_name: [[name, addr, size], ...], ...}
345 self._regions = [] # [[addr, size, elf_idx, saved line], ...]
346 self._elfs = {0: ["tee.elf", 0]} # {idx: [uuid, load_addr], ...}
Jerome Forissier733a15f2017-05-19 17:40:17 +0200347
Jerome Forissier095567e2018-05-29 17:42:34 +0200348 def pretty_print_path(self, path):
349 if self._strip_path:
350 return re.sub(re.escape(self._strip_path) + '/*', '', path)
351 return path
352
Jerome Forissier733a15f2017-05-19 17:40:17 +0200353 def write(self, line):
Jerome Forissier6e7c2e92018-11-14 11:02:49 +0100354 if self._call_stack_found:
355 match = re.search(STACK_ADDR_RE, line)
Jerome Forissier30999122017-08-25 18:42:58 +0200356 if match:
357 addr = match.group('addr')
Jerome Forissier6e7c2e92018-11-14 11:02:49 +0100358 pre = match.start('addr')
359 post = match.end('addr')
360 self._out.write(line[:pre])
361 self._out.write(addr)
362 res = self.resolve(addr)
363 res = self.pretty_print_path(res)
364 self._out.write(' ' + res)
365 self._out.write(line[post:])
Jerome Forissierae252462018-05-25 15:07:28 +0200366 return
Jerome Forissier6e7c2e92018-11-14 11:02:49 +0100367 else:
Jerome Forissier27b83ad2017-10-06 13:37:35 +0200368 self.reset()
Jerome Forissier6e7c2e92018-11-14 11:02:49 +0100369 match = re.search(REGION_RE, line)
370 if match:
371 # Region table: save info for later processing once
372 # we know which UUID corresponds to which ELF index
373 addr = match.group('addr')
374 size = match.group('size')
375 elf_idx = match.group('elf_idx')
376 self._regions.append([addr, size, elf_idx, line])
377 return
378 match = re.search(ELF_LIST_RE, line)
379 if match:
380 # ELF list: save info for later. Region table and ELF list
381 # will be displayed when the call stack is reached
382 i = int(match.group('idx'))
383 self._elfs[i] = [match.group('uuid'), match.group('load_addr'),
384 line]
385 return
386 match = re.search(CALL_STACK_RE, line)
387 if match:
388 self._call_stack_found = True
389 if self._regions:
390 for r in self._regions:
391 r_addr = r[0]
392 r_size = r[1]
393 elf_idx = r[2]
394 saved_line = r[3]
395 if elf_idx is None:
396 self._out.write(saved_line)
397 else:
398 self._out.write(saved_line.strip() +
399 self.sections_in_region(r_addr,
400 r_size,
401 elf_idx) +
402 '\n')
403 if self._elfs:
404 for k in self._elfs:
405 e = self._elfs[k]
406 if (len(e) >= 3):
407 # TA executable or library
408 self._out.write(e[2].strip())
409 elf = self.get_elf(e[0])
410 if elf:
411 rpath = os.path.realpath(elf)
412 path = self.pretty_print_path(rpath)
413 self._out.write(' (' + path + ')')
414 self._out.write('\n')
415 # Here is a good place to resolve the abort address because we
416 # have all the information we need
417 if self._saved_abort_line:
418 self._out.write(self.process_abort(self._saved_abort_line))
419 match = re.search(ABORT_ADDR_RE, line)
420 if match:
421 self.reset()
422 # At this point the arch and TA load address are unknown.
423 # Save the line so We can translate the abort address later.
424 self._saved_abort_line = line
425 self._out.write(line)
Jerome Forissier733a15f2017-05-19 17:40:17 +0200426
427 def flush(self):
428 self._out.flush()
429
Jerome Forissierae252462018-05-25 15:07:28 +0200430
Jerome Forissier733a15f2017-05-19 17:40:17 +0200431def main():
432 args = get_args()
433 if args.dir:
434 # Flatten list in case -d is used several times *and* with multiple
435 # arguments
436 args.dirs = [item for sublist in args.dir for item in sublist]
437 else:
438 args.dirs = []
439 symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path)
440
441 for line in sys.stdin:
442 symbolizer.write(line)
443 symbolizer.flush()
444
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200445
Jerome Forissier733a15f2017-05-19 17:40:17 +0200446if __name__ == "__main__":
447 main()