blob: 0e554ebd52baf0d6915796344f947e237413854c [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,
Jerome Forissierf9089762018-10-02 10:12:32 +020034nm) are used to extract the debug info. If the CROSS_COMPILE environment
35variable is set, it is used as a prefix to the binutils tools. That is, the
36script will invoke $(CROSS_COMPILE)addr2line etc. If it is not set however,
37the prefix will be determined automatically for each ELF file based on its
38architecture (arm-linux-gnueabihf-, aarch64-linux-gnu-). The resulting command
39is then expected to be found in the user's PATH.
Jerome Forissier733a15f2017-05-19 17:40:17 +020040
Jerome Forissier0c5bedb2018-02-15 17:20:36 +010041OP-TEE abort and panic messages are sent to the secure console. They look like
42the following:
Jerome Forissier733a15f2017-05-19 17:40:17 +020043
Jerome Forissier0c5bedb2018-02-15 17:20:36 +010044 E/TC:0 User TA data-abort at address 0xffffdecd (alignment fault)
Jerome Forissier733a15f2017-05-19 17:40:17 +020045 ...
Jerome Forissier0c5bedb2018-02-15 17:20:36 +010046 E/TC:0 Call stack:
47 E/TC:0 0x4000549e
48 E/TC:0 0x40001f4b
49 E/TC:0 0x4000273f
50 E/TC:0 0x40005da7
Jerome Forissier733a15f2017-05-19 17:40:17 +020051
52Inspired by a script of the same name by the Chromium project.
53
54Sample usage:
55
56 $ scripts/symbolize.py -d out/arm-plat-hikey/core -d ../optee_test/out/ta/*
57 <paste whole dump here>
58 ^D
59'''
60
Jerome Forissierae252462018-05-25 15:07:28 +020061
Jerome Forissier733a15f2017-05-19 17:40:17 +020062def get_args():
63 parser = argparse.ArgumentParser(
64 formatter_class=argparse.RawDescriptionHelpFormatter,
65 description='Symbolizes OP-TEE abort dumps',
66 epilog=epilog)
67 parser.add_argument('-d', '--dir', action='append', nargs='+',
Jerome Forissier1d8c2a42018-09-07 17:05:21 +020068 help='Search for ELF file in DIR. tee.elf is needed '
69 'to decode a TEE Core or pseudo-TA abort, while '
70 '<TA_uuid>.elf is required if a user-mode TA has '
71 'crashed. For convenience, ELF files may also be '
72 'given.')
Jerome Forissier5f7df502018-02-15 16:57:55 +010073 parser.add_argument('-s', '--strip_path', nargs='?',
Jerome Forissier1d8c2a42018-09-07 17:05:21 +020074 help='Strip STRIP_PATH from file paths (default: '
75 'current directory, use -s with no argument to show '
76 'full paths)', default=os.getcwd())
Jerome Forissier733a15f2017-05-19 17:40:17 +020077
78 return parser.parse_args()
79
Jerome Forissierae252462018-05-25 15:07:28 +020080
Jerome Forissier733a15f2017-05-19 17:40:17 +020081class Symbolizer(object):
82 def __init__(self, out, dirs, strip_path):
83 self._out = out
84 self._dirs = dirs
85 self._strip_path = strip_path
86 self._addr2line = None
Jerome Forissier733a15f2017-05-19 17:40:17 +020087 self.reset()
88
Jerome Forissier1cbf7772018-09-07 17:08:23 +020089 def my_Popen(self, cmd):
90 try:
91 return subprocess.Popen(cmd, stdin=subprocess.PIPE,
92 stdout=subprocess.PIPE)
93 except OSError as e:
94 if e.errno == os.errno.ENOENT:
95 print >> sys.stderr, "*** Error:", cmd[0] + \
96 ": command not found"
97 sys.exit(1)
98
Jerome Forissier733a15f2017-05-19 17:40:17 +020099 def get_elf(self, elf_or_uuid):
100 if not elf_or_uuid.endswith('.elf'):
101 elf_or_uuid += '.elf'
102 for d in self._dirs:
Jerome Forissier157e6212017-08-24 15:49:16 +0200103 if d.endswith(elf_or_uuid) and os.path.isfile(d):
104 return d
Jerome Forissier733a15f2017-05-19 17:40:17 +0200105 elf = glob.glob(d + '/' + elf_or_uuid)
106 if elf:
107 return elf[0]
108
Jerome Forissierd7204312017-09-04 17:58:52 +0200109 def set_arch(self):
110 if self._arch:
111 return
Etienne Carriere8a6d4a82018-10-01 09:58:31 +0200112 self._arch = os.getenv('CROSS_COMPILE');
113 if self._arch:
114 return
Jerome Forissierae252462018-05-25 15:07:28 +0200115 elf = self.get_elf(self._elfs[0][0])
116 if elf is None:
117 return
118 p = subprocess.Popen(['file', self.get_elf(self._elfs[0][0])],
119 stdout=subprocess.PIPE)
120 output = p.stdout.readlines()
121 p.terminate()
122 if 'ARM aarch64,' in output[0]:
123 self._arch = 'aarch64-linux-gnu-'
124 elif 'ARM,' in output[0]:
125 self._arch = 'arm-linux-gnueabihf-'
Jerome Forissierd7204312017-09-04 17:58:52 +0200126
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200127 def arch_prefix(self, cmd):
Jerome Forissierd7204312017-09-04 17:58:52 +0200128 self.set_arch()
Jerome Forissierae252462018-05-25 15:07:28 +0200129 if self._arch is None:
130 return ''
Jerome Forissierd7204312017-09-04 17:58:52 +0200131 return self._arch + cmd
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200132
Jerome Forissierae252462018-05-25 15:07:28 +0200133 def spawn_addr2line(self, elf_name):
134 if elf_name is None:
135 return
136 if self._addr2line_elf_name is elf_name:
137 return
138 if self._addr2line:
139 self._addr2line.terminate
140 self._addr2line = None
141 elf = self.get_elf(elf_name)
142 if not elf:
143 return
144 cmd = self.arch_prefix('addr2line')
145 if not cmd:
146 return
Jerome Forissier1cbf7772018-09-07 17:08:23 +0200147 self._addr2line = self.my_Popen([cmd, '-f', '-p', '-e', elf])
Jerome Forissierae252462018-05-25 15:07:28 +0200148 self._addr2line_elf_name = elf_name
149
150 # If addr falls into a region that maps a TA ELF file, return the load
151 # address of that file.
152 def elf_load_addr(self, addr):
153 if self._regions:
154 for r in self._regions:
155 r_addr = int(r[0], 16)
156 r_size = int(r[1], 16)
157 i_addr = int(addr, 16)
158 if (i_addr >= r_addr and i_addr < (r_addr + r_size)):
159 # Found region
160 elf_idx = r[2]
161 if elf_idx is not None:
162 return self._elfs[int(elf_idx)][1]
163 return None
164 else:
165 # tee.elf
166 return '0x0'
167
168 def elf_for_addr(self, addr):
169 l_addr = self.elf_load_addr(addr)
170 if l_addr is None:
171 return None
172 if l_addr is '0x0':
173 return 'tee.elf'
174 for k in self._elfs:
175 e = self._elfs[k]
176 if int(e[1], 16) == int(l_addr, 16):
177 return e[0]
178 return None
Jerome Forissier733a15f2017-05-19 17:40:17 +0200179
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200180 def subtract_load_addr(self, addr):
Jerome Forissierae252462018-05-25 15:07:28 +0200181 l_addr = self.elf_load_addr(addr)
182 if l_addr is None:
183 return None
184 if int(l_addr, 16) > int(addr, 16):
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200185 return ''
Jerome Forissierae252462018-05-25 15:07:28 +0200186 return '0x{:x}'.format(int(addr, 16) - int(l_addr, 16))
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200187
188 def resolve(self, addr):
189 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200190 self.spawn_addr2line(self.elf_for_addr(addr))
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200191 if not reladdr or not self._addr2line:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200192 return '???'
193 try:
194 print >> self._addr2line.stdin, reladdr
195 ret = self._addr2line.stdout.readline().rstrip('\n')
196 except IOError:
197 ret = '!!!'
198 return ret
199
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200200 def symbol_plus_offset(self, addr):
201 ret = ''
202 prevsize = 0
203 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200204 elf_name = self.elf_for_addr(addr)
205 if elf_name is None:
206 return ''
207 elf = self.get_elf(elf_name)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200208 cmd = self.arch_prefix('nm')
209 if not reladdr or not elf or not cmd:
210 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200211 ireladdr = int(reladdr, 16)
Jerome Forissier1cbf7772018-09-07 17:08:23 +0200212 nm = self.my_Popen([cmd, '--numeric-sort', '--print-size', elf])
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200213 for line in iter(nm.stdout.readline, ''):
214 try:
215 addr, size, _, name = line.split()
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200216 except ValueError:
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200217 # Size is missing
Jerome Forissierb4815422018-06-20 09:43:33 +0200218 try:
219 addr, _, name = line.split()
220 size = '0'
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200221 except ValueError:
Jerome Forissierb4815422018-06-20 09:43:33 +0200222 # E.g., undefined (external) symbols (line = "U symbol")
223 continue
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200224 iaddr = int(addr, 16)
225 isize = int(size, 16)
226 if iaddr == ireladdr:
227 ret = name
228 break
229 if iaddr < ireladdr and iaddr + isize >= ireladdr:
230 offs = ireladdr - iaddr
231 ret = name + '+' + str(offs)
232 break
233 if iaddr > ireladdr and prevsize == 0:
234 offs = iaddr + ireladdr
235 ret = prevname + '+' + str(offs)
236 break
237 prevsize = size
238 prevname = name
239 nm.terminate()
240 return ret
241
242 def section_plus_offset(self, addr):
243 ret = ''
244 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200245 elf_name = self.elf_for_addr(addr)
246 if elf_name is None:
247 return ''
248 elf = self.get_elf(elf_name)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200249 cmd = self.arch_prefix('objdump')
250 if not reladdr or not elf or not cmd:
251 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200252 iaddr = int(reladdr, 16)
Jerome Forissier1cbf7772018-09-07 17:08:23 +0200253 objdump = self.my_Popen([cmd, '--section-headers', elf])
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200254 for line in iter(objdump.stdout.readline, ''):
255 try:
256 idx, name, size, vma, lma, offs, algn = line.split()
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200257 except ValueError:
Jerome Forissierae252462018-05-25 15:07:28 +0200258 continue
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200259 ivma = int(vma, 16)
260 isize = int(size, 16)
261 if ivma == iaddr:
262 ret = name
263 break
264 if ivma < iaddr and ivma + isize >= iaddr:
265 offs = iaddr - ivma
266 ret = name + '+' + str(offs)
267 break
268 objdump.terminate()
269 return ret
270
271 def process_abort(self, line):
272 ret = ''
273 match = re.search(ABORT_ADDR_RE, line)
274 addr = match.group('addr')
275 pre = match.start('addr')
276 post = match.end('addr')
277 sym = self.symbol_plus_offset(addr)
278 sec = self.section_plus_offset(addr)
279 if sym or sec:
280 ret += line[:pre]
281 ret += addr
282 if sym:
283 ret += ' ' + sym
284 if sec:
285 ret += ' ' + sec
286 ret += line[post:]
287 return ret
288
Jerome Forissier30999122017-08-25 18:42:58 +0200289 # Return all ELF sections with the ALLOC flag
Jerome Forissierae252462018-05-25 15:07:28 +0200290 def read_sections(self, elf_name):
291 if elf_name is None:
Jerome Forissier30999122017-08-25 18:42:58 +0200292 return
Jerome Forissierae252462018-05-25 15:07:28 +0200293 if elf_name in self._sections:
294 return
295 elf = self.get_elf(elf_name)
Jerome Forissier30999122017-08-25 18:42:58 +0200296 cmd = self.arch_prefix('objdump')
297 if not elf or not cmd:
298 return
Jerome Forissierae252462018-05-25 15:07:28 +0200299 self._sections[elf_name] = []
Jerome Forissier1cbf7772018-09-07 17:08:23 +0200300 objdump = self.my_Popen([cmd, '--section-headers', elf])
Jerome Forissier30999122017-08-25 18:42:58 +0200301 for line in iter(objdump.stdout.readline, ''):
302 try:
303 _, name, size, vma, _, _, _ = line.split()
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200304 except ValueError:
Jerome Forissier30999122017-08-25 18:42:58 +0200305 if 'ALLOC' in line:
Jerome Forissierae252462018-05-25 15:07:28 +0200306 self._sections[elf_name].append([name, int(vma, 16),
307 int(size, 16)])
Jerome Forissier30999122017-08-25 18:42:58 +0200308
309 def overlaps(self, section, addr, size):
310 sec_addr = section[1]
311 sec_size = section[2]
312 if not size or not sec_size:
313 return False
Jerome Forissierae252462018-05-25 15:07:28 +0200314 return ((addr <= (sec_addr + sec_size - 1)) and
315 ((addr + size - 1) >= sec_addr))
Jerome Forissier30999122017-08-25 18:42:58 +0200316
Jerome Forissierae252462018-05-25 15:07:28 +0200317 def sections_in_region(self, addr, size, elf_idx):
Jerome Forissier30999122017-08-25 18:42:58 +0200318 ret = ''
319 addr = self.subtract_load_addr(addr)
320 if not addr:
321 return ''
322 iaddr = int(addr, 16)
323 isize = int(size, 16)
Jerome Forissierae252462018-05-25 15:07:28 +0200324 elf = self._elfs[int(elf_idx)][0]
325 if elf is None:
326 return ''
327 self.read_sections(elf)
328 if elf not in self._sections:
329 return ''
330 for s in self._sections[elf]:
Jerome Forissier30999122017-08-25 18:42:58 +0200331 if self.overlaps(s, iaddr, isize):
332 ret += ' ' + s[0]
333 return ret
334
Jerome Forissier733a15f2017-05-19 17:40:17 +0200335 def reset(self):
336 self._call_stack_found = False
Jerome Forissier733a15f2017-05-19 17:40:17 +0200337 if self._addr2line:
338 self._addr2line.terminate()
339 self._addr2line = None
Jerome Forissierae252462018-05-25 15:07:28 +0200340 self._addr2line_elf_name = None
Jerome Forissierd7204312017-09-04 17:58:52 +0200341 self._arch = None
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200342 self._saved_abort_line = ''
Jerome Forissierae252462018-05-25 15:07:28 +0200343 self._sections = {} # {elf_name: [[name, addr, size], ...], ...}
344 self._regions = [] # [[addr, size, elf_idx, saved line], ...]
345 self._elfs = {0: ["tee.elf", 0]} # {idx: [uuid, load_addr], ...}
Jerome Forissier733a15f2017-05-19 17:40:17 +0200346
Jerome Forissier095567e2018-05-29 17:42:34 +0200347 def pretty_print_path(self, path):
348 if self._strip_path:
349 return re.sub(re.escape(self._strip_path) + '/*', '', path)
350 return path
351
Jerome Forissier733a15f2017-05-19 17:40:17 +0200352 def write(self, line):
353 if self._call_stack_found:
354 match = re.search(STACK_ADDR_RE, line)
355 if match:
356 addr = match.group('addr')
357 pre = match.start('addr')
358 post = match.end('addr')
359 self._out.write(line[:pre])
360 self._out.write(addr)
361 res = self.resolve(addr)
Jerome Forissier095567e2018-05-29 17:42:34 +0200362 res = self.pretty_print_path(res)
Jerome Forissier733a15f2017-05-19 17:40:17 +0200363 self._out.write(' ' + res)
364 self._out.write(line[post:])
365 return
366 else:
367 self.reset()
Jerome Forissier30999122017-08-25 18:42:58 +0200368 match = re.search(REGION_RE, line)
369 if match:
Jerome Forissierae252462018-05-25 15:07:28 +0200370 # Region table: save info for later processing once
371 # we know which UUID corresponds to which ELF index
Jerome Forissier30999122017-08-25 18:42:58 +0200372 addr = match.group('addr')
373 size = match.group('size')
Jerome Forissierae252462018-05-25 15:07:28 +0200374 elf_idx = match.group('elf_idx')
375 self._regions.append([addr, size, elf_idx, line])
376 return
377 match = re.search(ELF_LIST_RE, line)
378 if match:
379 # ELF list: save info for later. Region table and ELF list
380 # will be displayed when the call stack is reached
381 i = int(match.group('idx'))
382 self._elfs[i] = [match.group('uuid'), match.group('load_addr'),
383 line]
Jerome Forissier30999122017-08-25 18:42:58 +0200384 return
Jerome Forissier733a15f2017-05-19 17:40:17 +0200385 match = re.search(CALL_STACK_RE, line)
386 if match:
387 self._call_stack_found = True
Jerome Forissierae252462018-05-25 15:07:28 +0200388 if self._regions:
389 for r in self._regions:
390 r_addr = r[0]
391 r_size = r[1]
392 elf_idx = r[2]
393 saved_line = r[3]
394 if elf_idx is None:
395 self._out.write(saved_line)
396 else:
397 self._out.write(saved_line.strip() +
398 self.sections_in_region(r_addr,
399 r_size,
400 elf_idx) +
401 '\n')
402 if self._elfs:
403 for k in self._elfs:
404 e = self._elfs[k]
405 if (len(e) >= 3):
Jerome Forissier1e6f2ea2018-06-27 09:29:24 +0200406 # TA executable or library
Jerome Forissier095567e2018-05-29 17:42:34 +0200407 self._out.write(e[2].strip())
Jerome Forissier1e6f2ea2018-06-27 09:29:24 +0200408 elf = self.get_elf(e[0])
409 if elf:
410 rpath = os.path.realpath(elf)
411 path = self.pretty_print_path(rpath)
412 self._out.write(' (' + path + ')')
413 self._out.write('\n')
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200414 # Here is a good place to resolve the abort address because we
415 # have all the information we need
416 if self._saved_abort_line:
417 self._out.write(self.process_abort(self._saved_abort_line))
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200418 match = re.search(ABORT_ADDR_RE, line)
419 if match:
Jerome Forissier27b83ad2017-10-06 13:37:35 +0200420 self.reset()
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200421 # At this point the arch and TA load address are unknown.
422 # Save the line so We can translate the abort address later.
423 self._saved_abort_line = line
Jerome Forissier733a15f2017-05-19 17:40:17 +0200424 self._out.write(line)
425
426 def flush(self):
427 self._out.flush()
428
Jerome Forissierae252462018-05-25 15:07:28 +0200429
Jerome Forissier733a15f2017-05-19 17:40:17 +0200430def main():
431 args = get_args()
432 if args.dir:
433 # Flatten list in case -d is used several times *and* with multiple
434 # arguments
435 args.dirs = [item for sublist in args.dir for item in sublist]
436 else:
437 args.dirs = []
438 symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path)
439
440 for line in sys.stdin:
441 symbolizer.write(line)
442 symbolizer.flush()
443
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200444
Jerome Forissier733a15f2017-05-19 17:40:17 +0200445if __name__ == "__main__":
446 main()