blob: 2da49a9901935f3e02fca69c272a4231f80e3985 [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='+',
Jerome Forissier1d8c2a42018-09-07 17:05:21 +020063 help='Search for ELF file in DIR. tee.elf is needed '
64 'to decode a TEE Core or pseudo-TA abort, while '
65 '<TA_uuid>.elf is required if a user-mode TA has '
66 'crashed. For convenience, ELF files may also be '
67 'given.')
Jerome Forissier5f7df502018-02-15 16:57:55 +010068 parser.add_argument('-s', '--strip_path', nargs='?',
Jerome Forissier1d8c2a42018-09-07 17:05:21 +020069 help='Strip STRIP_PATH from file paths (default: '
70 'current directory, use -s with no argument to show '
71 'full paths)', default=os.getcwd())
Jerome Forissier733a15f2017-05-19 17:40:17 +020072
73 return parser.parse_args()
74
Jerome Forissierae252462018-05-25 15:07:28 +020075
Jerome Forissier733a15f2017-05-19 17:40:17 +020076class Symbolizer(object):
77 def __init__(self, out, dirs, strip_path):
78 self._out = out
79 self._dirs = dirs
80 self._strip_path = strip_path
81 self._addr2line = None
Jerome Forissier733a15f2017-05-19 17:40:17 +020082 self.reset()
83
84 def get_elf(self, elf_or_uuid):
85 if not elf_or_uuid.endswith('.elf'):
86 elf_or_uuid += '.elf'
87 for d in self._dirs:
Jerome Forissier157e6212017-08-24 15:49:16 +020088 if d.endswith(elf_or_uuid) and os.path.isfile(d):
89 return d
Jerome Forissier733a15f2017-05-19 17:40:17 +020090 elf = glob.glob(d + '/' + elf_or_uuid)
91 if elf:
92 return elf[0]
93
Jerome Forissierd7204312017-09-04 17:58:52 +020094 def set_arch(self):
95 if self._arch:
96 return
Jerome Forissierae252462018-05-25 15:07:28 +020097 elf = self.get_elf(self._elfs[0][0])
98 if elf is None:
99 return
100 p = subprocess.Popen(['file', self.get_elf(self._elfs[0][0])],
101 stdout=subprocess.PIPE)
102 output = p.stdout.readlines()
103 p.terminate()
104 if 'ARM aarch64,' in output[0]:
105 self._arch = 'aarch64-linux-gnu-'
106 elif 'ARM,' in output[0]:
107 self._arch = 'arm-linux-gnueabihf-'
Jerome Forissierd7204312017-09-04 17:58:52 +0200108
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200109 def arch_prefix(self, cmd):
Jerome Forissierd7204312017-09-04 17:58:52 +0200110 self.set_arch()
Jerome Forissierae252462018-05-25 15:07:28 +0200111 if self._arch is None:
112 return ''
Jerome Forissierd7204312017-09-04 17:58:52 +0200113 return self._arch + cmd
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200114
Jerome Forissierae252462018-05-25 15:07:28 +0200115 def spawn_addr2line(self, elf_name):
116 if elf_name is None:
117 return
118 if self._addr2line_elf_name is elf_name:
119 return
120 if self._addr2line:
121 self._addr2line.terminate
122 self._addr2line = None
123 elf = self.get_elf(elf_name)
124 if not elf:
125 return
126 cmd = self.arch_prefix('addr2line')
127 if not cmd:
128 return
129 self._addr2line = subprocess.Popen([cmd, '-f', '-p', '-e', elf],
130 stdin=subprocess.PIPE,
131 stdout=subprocess.PIPE)
132 self._addr2line_elf_name = elf_name
133
134 # If addr falls into a region that maps a TA ELF file, return the load
135 # address of that file.
136 def elf_load_addr(self, addr):
137 if self._regions:
138 for r in self._regions:
139 r_addr = int(r[0], 16)
140 r_size = int(r[1], 16)
141 i_addr = int(addr, 16)
142 if (i_addr >= r_addr and i_addr < (r_addr + r_size)):
143 # Found region
144 elf_idx = r[2]
145 if elf_idx is not None:
146 return self._elfs[int(elf_idx)][1]
147 return None
148 else:
149 # tee.elf
150 return '0x0'
151
152 def elf_for_addr(self, addr):
153 l_addr = self.elf_load_addr(addr)
154 if l_addr is None:
155 return None
156 if l_addr is '0x0':
157 return 'tee.elf'
158 for k in self._elfs:
159 e = self._elfs[k]
160 if int(e[1], 16) == int(l_addr, 16):
161 return e[0]
162 return None
Jerome Forissier733a15f2017-05-19 17:40:17 +0200163
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200164 def subtract_load_addr(self, addr):
Jerome Forissierae252462018-05-25 15:07:28 +0200165 l_addr = self.elf_load_addr(addr)
166 if l_addr is None:
167 return None
168 if int(l_addr, 16) > int(addr, 16):
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200169 return ''
Jerome Forissierae252462018-05-25 15:07:28 +0200170 return '0x{:x}'.format(int(addr, 16) - int(l_addr, 16))
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200171
172 def resolve(self, addr):
173 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200174 self.spawn_addr2line(self.elf_for_addr(addr))
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200175 if not reladdr or not self._addr2line:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200176 return '???'
177 try:
178 print >> self._addr2line.stdin, reladdr
179 ret = self._addr2line.stdout.readline().rstrip('\n')
180 except IOError:
181 ret = '!!!'
182 return ret
183
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200184 def symbol_plus_offset(self, addr):
185 ret = ''
186 prevsize = 0
187 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200188 elf_name = self.elf_for_addr(addr)
189 if elf_name is None:
190 return ''
191 elf = self.get_elf(elf_name)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200192 cmd = self.arch_prefix('nm')
193 if not reladdr or not elf or not cmd:
194 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200195 ireladdr = int(reladdr, 16)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200196 nm = subprocess.Popen([cmd, '--numeric-sort', '--print-size', elf],
Jerome Forissierae252462018-05-25 15:07:28 +0200197 stdin=subprocess.PIPE,
198 stdout=subprocess.PIPE)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200199 for line in iter(nm.stdout.readline, ''):
200 try:
201 addr, size, _, name = line.split()
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200202 except ValueError:
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200203 # Size is missing
Jerome Forissierb4815422018-06-20 09:43:33 +0200204 try:
205 addr, _, name = line.split()
206 size = '0'
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200207 except ValueError:
Jerome Forissierb4815422018-06-20 09:43:33 +0200208 # E.g., undefined (external) symbols (line = "U symbol")
209 continue
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200210 iaddr = int(addr, 16)
211 isize = int(size, 16)
212 if iaddr == ireladdr:
213 ret = name
214 break
215 if iaddr < ireladdr and iaddr + isize >= ireladdr:
216 offs = ireladdr - iaddr
217 ret = name + '+' + str(offs)
218 break
219 if iaddr > ireladdr and prevsize == 0:
220 offs = iaddr + ireladdr
221 ret = prevname + '+' + str(offs)
222 break
223 prevsize = size
224 prevname = name
225 nm.terminate()
226 return ret
227
228 def section_plus_offset(self, addr):
229 ret = ''
230 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200231 elf_name = self.elf_for_addr(addr)
232 if elf_name is None:
233 return ''
234 elf = self.get_elf(elf_name)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200235 cmd = self.arch_prefix('objdump')
236 if not reladdr or not elf or not cmd:
237 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200238 iaddr = int(reladdr, 16)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200239 objdump = subprocess.Popen([cmd, '--section-headers', elf],
Jerome Forissierae252462018-05-25 15:07:28 +0200240 stdin=subprocess.PIPE,
241 stdout=subprocess.PIPE)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200242 for line in iter(objdump.stdout.readline, ''):
243 try:
244 idx, name, size, vma, lma, offs, algn = line.split()
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200245 except ValueError:
Jerome Forissierae252462018-05-25 15:07:28 +0200246 continue
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200247 ivma = int(vma, 16)
248 isize = int(size, 16)
249 if ivma == iaddr:
250 ret = name
251 break
252 if ivma < iaddr and ivma + isize >= iaddr:
253 offs = iaddr - ivma
254 ret = name + '+' + str(offs)
255 break
256 objdump.terminate()
257 return ret
258
259 def process_abort(self, line):
260 ret = ''
261 match = re.search(ABORT_ADDR_RE, line)
262 addr = match.group('addr')
263 pre = match.start('addr')
264 post = match.end('addr')
265 sym = self.symbol_plus_offset(addr)
266 sec = self.section_plus_offset(addr)
267 if sym or sec:
268 ret += line[:pre]
269 ret += addr
270 if sym:
271 ret += ' ' + sym
272 if sec:
273 ret += ' ' + sec
274 ret += line[post:]
275 return ret
276
Jerome Forissier30999122017-08-25 18:42:58 +0200277 # Return all ELF sections with the ALLOC flag
Jerome Forissierae252462018-05-25 15:07:28 +0200278 def read_sections(self, elf_name):
279 if elf_name is None:
Jerome Forissier30999122017-08-25 18:42:58 +0200280 return
Jerome Forissierae252462018-05-25 15:07:28 +0200281 if elf_name in self._sections:
282 return
283 elf = self.get_elf(elf_name)
Jerome Forissier30999122017-08-25 18:42:58 +0200284 cmd = self.arch_prefix('objdump')
285 if not elf or not cmd:
286 return
Jerome Forissierae252462018-05-25 15:07:28 +0200287 self._sections[elf_name] = []
Jerome Forissier30999122017-08-25 18:42:58 +0200288 objdump = subprocess.Popen([cmd, '--section-headers', elf],
Jerome Forissierae252462018-05-25 15:07:28 +0200289 stdin=subprocess.PIPE,
290 stdout=subprocess.PIPE)
Jerome Forissier30999122017-08-25 18:42:58 +0200291 for line in iter(objdump.stdout.readline, ''):
292 try:
293 _, name, size, vma, _, _, _ = line.split()
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200294 except ValueError:
Jerome Forissier30999122017-08-25 18:42:58 +0200295 if 'ALLOC' in line:
Jerome Forissierae252462018-05-25 15:07:28 +0200296 self._sections[elf_name].append([name, int(vma, 16),
297 int(size, 16)])
Jerome Forissier30999122017-08-25 18:42:58 +0200298
299 def overlaps(self, section, addr, size):
300 sec_addr = section[1]
301 sec_size = section[2]
302 if not size or not sec_size:
303 return False
Jerome Forissierae252462018-05-25 15:07:28 +0200304 return ((addr <= (sec_addr + sec_size - 1)) and
305 ((addr + size - 1) >= sec_addr))
Jerome Forissier30999122017-08-25 18:42:58 +0200306
Jerome Forissierae252462018-05-25 15:07:28 +0200307 def sections_in_region(self, addr, size, elf_idx):
Jerome Forissier30999122017-08-25 18:42:58 +0200308 ret = ''
309 addr = self.subtract_load_addr(addr)
310 if not addr:
311 return ''
312 iaddr = int(addr, 16)
313 isize = int(size, 16)
Jerome Forissierae252462018-05-25 15:07:28 +0200314 elf = self._elfs[int(elf_idx)][0]
315 if elf is None:
316 return ''
317 self.read_sections(elf)
318 if elf not in self._sections:
319 return ''
320 for s in self._sections[elf]:
Jerome Forissier30999122017-08-25 18:42:58 +0200321 if self.overlaps(s, iaddr, isize):
322 ret += ' ' + s[0]
323 return ret
324
Jerome Forissier733a15f2017-05-19 17:40:17 +0200325 def reset(self):
326 self._call_stack_found = False
Jerome Forissier733a15f2017-05-19 17:40:17 +0200327 if self._addr2line:
328 self._addr2line.terminate()
329 self._addr2line = None
Jerome Forissierae252462018-05-25 15:07:28 +0200330 self._addr2line_elf_name = None
Jerome Forissierd7204312017-09-04 17:58:52 +0200331 self._arch = None
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200332 self._saved_abort_line = ''
Jerome Forissierae252462018-05-25 15:07:28 +0200333 self._sections = {} # {elf_name: [[name, addr, size], ...], ...}
334 self._regions = [] # [[addr, size, elf_idx, saved line], ...]
335 self._elfs = {0: ["tee.elf", 0]} # {idx: [uuid, load_addr], ...}
Jerome Forissier733a15f2017-05-19 17:40:17 +0200336
Jerome Forissier095567e2018-05-29 17:42:34 +0200337 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
Jerome Forissier733a15f2017-05-19 17:40:17 +0200342 def write(self, line):
343 if self._call_stack_found:
344 match = re.search(STACK_ADDR_RE, line)
345 if match:
346 addr = match.group('addr')
347 pre = match.start('addr')
348 post = match.end('addr')
349 self._out.write(line[:pre])
350 self._out.write(addr)
351 res = self.resolve(addr)
Jerome Forissier095567e2018-05-29 17:42:34 +0200352 res = self.pretty_print_path(res)
Jerome Forissier733a15f2017-05-19 17:40:17 +0200353 self._out.write(' ' + res)
354 self._out.write(line[post:])
355 return
356 else:
357 self.reset()
Jerome Forissier30999122017-08-25 18:42:58 +0200358 match = re.search(REGION_RE, line)
359 if match:
Jerome Forissierae252462018-05-25 15:07:28 +0200360 # Region table: save info for later processing once
361 # we know which UUID corresponds to which ELF index
Jerome Forissier30999122017-08-25 18:42:58 +0200362 addr = match.group('addr')
363 size = match.group('size')
Jerome Forissierae252462018-05-25 15:07:28 +0200364 elf_idx = match.group('elf_idx')
365 self._regions.append([addr, size, elf_idx, line])
366 return
367 match = re.search(ELF_LIST_RE, line)
368 if match:
369 # ELF list: save info for later. Region table and ELF list
370 # will be displayed when the call stack is reached
371 i = int(match.group('idx'))
372 self._elfs[i] = [match.group('uuid'), match.group('load_addr'),
373 line]
Jerome Forissier30999122017-08-25 18:42:58 +0200374 return
Jerome Forissier733a15f2017-05-19 17:40:17 +0200375 match = re.search(CALL_STACK_RE, line)
376 if match:
377 self._call_stack_found = True
Jerome Forissierae252462018-05-25 15:07:28 +0200378 if self._regions:
379 for r in self._regions:
380 r_addr = r[0]
381 r_size = r[1]
382 elf_idx = r[2]
383 saved_line = r[3]
384 if elf_idx is None:
385 self._out.write(saved_line)
386 else:
387 self._out.write(saved_line.strip() +
388 self.sections_in_region(r_addr,
389 r_size,
390 elf_idx) +
391 '\n')
392 if self._elfs:
393 for k in self._elfs:
394 e = self._elfs[k]
395 if (len(e) >= 3):
Jerome Forissier1e6f2ea2018-06-27 09:29:24 +0200396 # TA executable or library
Jerome Forissier095567e2018-05-29 17:42:34 +0200397 self._out.write(e[2].strip())
Jerome Forissier1e6f2ea2018-06-27 09:29:24 +0200398 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
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200434
Jerome Forissier733a15f2017-05-19 17:40:17 +0200435if __name__ == "__main__":
436 main()