blob: aad7c2d1908922cadd8efca6f218421fecb5a7d2 [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
Jerome Forissier1cbf7772018-09-07 17:08:23 +020084 def my_Popen(self, cmd):
85 try:
86 return subprocess.Popen(cmd, stdin=subprocess.PIPE,
87 stdout=subprocess.PIPE)
88 except OSError as e:
89 if e.errno == os.errno.ENOENT:
90 print >> sys.stderr, "*** Error:", cmd[0] + \
91 ": command not found"
92 sys.exit(1)
93
Jerome Forissier733a15f2017-05-19 17:40:17 +020094 def get_elf(self, elf_or_uuid):
95 if not elf_or_uuid.endswith('.elf'):
96 elf_or_uuid += '.elf'
97 for d in self._dirs:
Jerome Forissier157e6212017-08-24 15:49:16 +020098 if d.endswith(elf_or_uuid) and os.path.isfile(d):
99 return d
Jerome Forissier733a15f2017-05-19 17:40:17 +0200100 elf = glob.glob(d + '/' + elf_or_uuid)
101 if elf:
102 return elf[0]
103
Jerome Forissierd7204312017-09-04 17:58:52 +0200104 def set_arch(self):
105 if self._arch:
106 return
Jerome Forissierae252462018-05-25 15:07:28 +0200107 elf = self.get_elf(self._elfs[0][0])
108 if elf is None:
109 return
110 p = subprocess.Popen(['file', self.get_elf(self._elfs[0][0])],
111 stdout=subprocess.PIPE)
112 output = p.stdout.readlines()
113 p.terminate()
114 if 'ARM aarch64,' in output[0]:
115 self._arch = 'aarch64-linux-gnu-'
116 elif 'ARM,' in output[0]:
117 self._arch = 'arm-linux-gnueabihf-'
Jerome Forissierd7204312017-09-04 17:58:52 +0200118
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200119 def arch_prefix(self, cmd):
Jerome Forissierd7204312017-09-04 17:58:52 +0200120 self.set_arch()
Jerome Forissierae252462018-05-25 15:07:28 +0200121 if self._arch is None:
122 return ''
Jerome Forissierd7204312017-09-04 17:58:52 +0200123 return self._arch + cmd
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200124
Jerome Forissierae252462018-05-25 15:07:28 +0200125 def spawn_addr2line(self, elf_name):
126 if elf_name is None:
127 return
128 if self._addr2line_elf_name is elf_name:
129 return
130 if self._addr2line:
131 self._addr2line.terminate
132 self._addr2line = None
133 elf = self.get_elf(elf_name)
134 if not elf:
135 return
136 cmd = self.arch_prefix('addr2line')
137 if not cmd:
138 return
Jerome Forissier1cbf7772018-09-07 17:08:23 +0200139 self._addr2line = self.my_Popen([cmd, '-f', '-p', '-e', elf])
Jerome Forissierae252462018-05-25 15:07:28 +0200140 self._addr2line_elf_name = elf_name
141
142 # If addr falls into a region that maps a TA ELF file, return the load
143 # address of that file.
144 def elf_load_addr(self, addr):
145 if self._regions:
146 for r in self._regions:
147 r_addr = int(r[0], 16)
148 r_size = int(r[1], 16)
149 i_addr = int(addr, 16)
150 if (i_addr >= r_addr and i_addr < (r_addr + r_size)):
151 # Found region
152 elf_idx = r[2]
153 if elf_idx is not None:
154 return self._elfs[int(elf_idx)][1]
155 return None
156 else:
157 # tee.elf
158 return '0x0'
159
160 def elf_for_addr(self, addr):
161 l_addr = self.elf_load_addr(addr)
162 if l_addr is None:
163 return None
164 if l_addr is '0x0':
165 return 'tee.elf'
166 for k in self._elfs:
167 e = self._elfs[k]
168 if int(e[1], 16) == int(l_addr, 16):
169 return e[0]
170 return None
Jerome Forissier733a15f2017-05-19 17:40:17 +0200171
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200172 def subtract_load_addr(self, addr):
Jerome Forissierae252462018-05-25 15:07:28 +0200173 l_addr = self.elf_load_addr(addr)
174 if l_addr is None:
175 return None
176 if int(l_addr, 16) > int(addr, 16):
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200177 return ''
Jerome Forissierae252462018-05-25 15:07:28 +0200178 return '0x{:x}'.format(int(addr, 16) - int(l_addr, 16))
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200179
180 def resolve(self, addr):
181 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200182 self.spawn_addr2line(self.elf_for_addr(addr))
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200183 if not reladdr or not self._addr2line:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200184 return '???'
185 try:
186 print >> self._addr2line.stdin, reladdr
187 ret = self._addr2line.stdout.readline().rstrip('\n')
188 except IOError:
189 ret = '!!!'
190 return ret
191
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200192 def symbol_plus_offset(self, addr):
193 ret = ''
194 prevsize = 0
195 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200196 elf_name = self.elf_for_addr(addr)
197 if elf_name is None:
198 return ''
199 elf = self.get_elf(elf_name)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200200 cmd = self.arch_prefix('nm')
201 if not reladdr or not elf or not cmd:
202 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200203 ireladdr = int(reladdr, 16)
Jerome Forissier1cbf7772018-09-07 17:08:23 +0200204 nm = self.my_Popen([cmd, '--numeric-sort', '--print-size', elf])
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200205 for line in iter(nm.stdout.readline, ''):
206 try:
207 addr, size, _, name = line.split()
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200208 except ValueError:
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200209 # Size is missing
Jerome Forissierb4815422018-06-20 09:43:33 +0200210 try:
211 addr, _, name = line.split()
212 size = '0'
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200213 except ValueError:
Jerome Forissierb4815422018-06-20 09:43:33 +0200214 # E.g., undefined (external) symbols (line = "U symbol")
215 continue
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200216 iaddr = int(addr, 16)
217 isize = int(size, 16)
218 if iaddr == ireladdr:
219 ret = name
220 break
221 if iaddr < ireladdr and iaddr + isize >= ireladdr:
222 offs = ireladdr - iaddr
223 ret = name + '+' + str(offs)
224 break
225 if iaddr > ireladdr and prevsize == 0:
226 offs = iaddr + ireladdr
227 ret = prevname + '+' + str(offs)
228 break
229 prevsize = size
230 prevname = name
231 nm.terminate()
232 return ret
233
234 def section_plus_offset(self, addr):
235 ret = ''
236 reladdr = self.subtract_load_addr(addr)
Jerome Forissierae252462018-05-25 15:07:28 +0200237 elf_name = self.elf_for_addr(addr)
238 if elf_name is None:
239 return ''
240 elf = self.get_elf(elf_name)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200241 cmd = self.arch_prefix('objdump')
242 if not reladdr or not elf or not cmd:
243 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200244 iaddr = int(reladdr, 16)
Jerome Forissier1cbf7772018-09-07 17:08:23 +0200245 objdump = self.my_Popen([cmd, '--section-headers', elf])
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200246 for line in iter(objdump.stdout.readline, ''):
247 try:
248 idx, name, size, vma, lma, offs, algn = line.split()
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200249 except ValueError:
Jerome Forissierae252462018-05-25 15:07:28 +0200250 continue
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200251 ivma = int(vma, 16)
252 isize = int(size, 16)
253 if ivma == iaddr:
254 ret = name
255 break
256 if ivma < iaddr and ivma + isize >= iaddr:
257 offs = iaddr - ivma
258 ret = name + '+' + str(offs)
259 break
260 objdump.terminate()
261 return ret
262
263 def process_abort(self, line):
264 ret = ''
265 match = re.search(ABORT_ADDR_RE, line)
266 addr = match.group('addr')
267 pre = match.start('addr')
268 post = match.end('addr')
269 sym = self.symbol_plus_offset(addr)
270 sec = self.section_plus_offset(addr)
271 if sym or sec:
272 ret += line[:pre]
273 ret += addr
274 if sym:
275 ret += ' ' + sym
276 if sec:
277 ret += ' ' + sec
278 ret += line[post:]
279 return ret
280
Jerome Forissier30999122017-08-25 18:42:58 +0200281 # Return all ELF sections with the ALLOC flag
Jerome Forissierae252462018-05-25 15:07:28 +0200282 def read_sections(self, elf_name):
283 if elf_name is None:
Jerome Forissier30999122017-08-25 18:42:58 +0200284 return
Jerome Forissierae252462018-05-25 15:07:28 +0200285 if elf_name in self._sections:
286 return
287 elf = self.get_elf(elf_name)
Jerome Forissier30999122017-08-25 18:42:58 +0200288 cmd = self.arch_prefix('objdump')
289 if not elf or not cmd:
290 return
Jerome Forissierae252462018-05-25 15:07:28 +0200291 self._sections[elf_name] = []
Jerome Forissier1cbf7772018-09-07 17:08:23 +0200292 objdump = self.my_Popen([cmd, '--section-headers', elf])
Jerome Forissier30999122017-08-25 18:42:58 +0200293 for line in iter(objdump.stdout.readline, ''):
294 try:
295 _, name, size, vma, _, _, _ = line.split()
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200296 except ValueError:
Jerome Forissier30999122017-08-25 18:42:58 +0200297 if 'ALLOC' in line:
Jerome Forissierae252462018-05-25 15:07:28 +0200298 self._sections[elf_name].append([name, int(vma, 16),
299 int(size, 16)])
Jerome Forissier30999122017-08-25 18:42:58 +0200300
301 def overlaps(self, section, addr, size):
302 sec_addr = section[1]
303 sec_size = section[2]
304 if not size or not sec_size:
305 return False
Jerome Forissierae252462018-05-25 15:07:28 +0200306 return ((addr <= (sec_addr + sec_size - 1)) and
307 ((addr + size - 1) >= sec_addr))
Jerome Forissier30999122017-08-25 18:42:58 +0200308
Jerome Forissierae252462018-05-25 15:07:28 +0200309 def sections_in_region(self, addr, size, elf_idx):
Jerome Forissier30999122017-08-25 18:42:58 +0200310 ret = ''
311 addr = self.subtract_load_addr(addr)
312 if not addr:
313 return ''
314 iaddr = int(addr, 16)
315 isize = int(size, 16)
Jerome Forissierae252462018-05-25 15:07:28 +0200316 elf = self._elfs[int(elf_idx)][0]
317 if elf is None:
318 return ''
319 self.read_sections(elf)
320 if elf not in self._sections:
321 return ''
322 for s in self._sections[elf]:
Jerome Forissier30999122017-08-25 18:42:58 +0200323 if self.overlaps(s, iaddr, isize):
324 ret += ' ' + s[0]
325 return ret
326
Jerome Forissier733a15f2017-05-19 17:40:17 +0200327 def reset(self):
328 self._call_stack_found = False
Jerome Forissier733a15f2017-05-19 17:40:17 +0200329 if self._addr2line:
330 self._addr2line.terminate()
331 self._addr2line = None
Jerome Forissierae252462018-05-25 15:07:28 +0200332 self._addr2line_elf_name = None
Jerome Forissierd7204312017-09-04 17:58:52 +0200333 self._arch = None
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200334 self._saved_abort_line = ''
Jerome Forissierae252462018-05-25 15:07:28 +0200335 self._sections = {} # {elf_name: [[name, addr, size], ...], ...}
336 self._regions = [] # [[addr, size, elf_idx, saved line], ...]
337 self._elfs = {0: ["tee.elf", 0]} # {idx: [uuid, load_addr], ...}
Jerome Forissier733a15f2017-05-19 17:40:17 +0200338
Jerome Forissier095567e2018-05-29 17:42:34 +0200339 def pretty_print_path(self, path):
340 if self._strip_path:
341 return re.sub(re.escape(self._strip_path) + '/*', '', path)
342 return path
343
Jerome Forissier733a15f2017-05-19 17:40:17 +0200344 def write(self, line):
345 if self._call_stack_found:
346 match = re.search(STACK_ADDR_RE, line)
347 if match:
348 addr = match.group('addr')
349 pre = match.start('addr')
350 post = match.end('addr')
351 self._out.write(line[:pre])
352 self._out.write(addr)
353 res = self.resolve(addr)
Jerome Forissier095567e2018-05-29 17:42:34 +0200354 res = self.pretty_print_path(res)
Jerome Forissier733a15f2017-05-19 17:40:17 +0200355 self._out.write(' ' + res)
356 self._out.write(line[post:])
357 return
358 else:
359 self.reset()
Jerome Forissier30999122017-08-25 18:42:58 +0200360 match = re.search(REGION_RE, line)
361 if match:
Jerome Forissierae252462018-05-25 15:07:28 +0200362 # Region table: save info for later processing once
363 # we know which UUID corresponds to which ELF index
Jerome Forissier30999122017-08-25 18:42:58 +0200364 addr = match.group('addr')
365 size = match.group('size')
Jerome Forissierae252462018-05-25 15:07:28 +0200366 elf_idx = match.group('elf_idx')
367 self._regions.append([addr, size, elf_idx, line])
368 return
369 match = re.search(ELF_LIST_RE, line)
370 if match:
371 # ELF list: save info for later. Region table and ELF list
372 # will be displayed when the call stack is reached
373 i = int(match.group('idx'))
374 self._elfs[i] = [match.group('uuid'), match.group('load_addr'),
375 line]
Jerome Forissier30999122017-08-25 18:42:58 +0200376 return
Jerome Forissier733a15f2017-05-19 17:40:17 +0200377 match = re.search(CALL_STACK_RE, line)
378 if match:
379 self._call_stack_found = True
Jerome Forissierae252462018-05-25 15:07:28 +0200380 if self._regions:
381 for r in self._regions:
382 r_addr = r[0]
383 r_size = r[1]
384 elf_idx = r[2]
385 saved_line = r[3]
386 if elf_idx is None:
387 self._out.write(saved_line)
388 else:
389 self._out.write(saved_line.strip() +
390 self.sections_in_region(r_addr,
391 r_size,
392 elf_idx) +
393 '\n')
394 if self._elfs:
395 for k in self._elfs:
396 e = self._elfs[k]
397 if (len(e) >= 3):
Jerome Forissier1e6f2ea2018-06-27 09:29:24 +0200398 # TA executable or library
Jerome Forissier095567e2018-05-29 17:42:34 +0200399 self._out.write(e[2].strip())
Jerome Forissier1e6f2ea2018-06-27 09:29:24 +0200400 elf = self.get_elf(e[0])
401 if elf:
402 rpath = os.path.realpath(elf)
403 path = self.pretty_print_path(rpath)
404 self._out.write(' (' + path + ')')
405 self._out.write('\n')
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200406 # Here is a good place to resolve the abort address because we
407 # have all the information we need
408 if self._saved_abort_line:
409 self._out.write(self.process_abort(self._saved_abort_line))
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200410 match = re.search(ABORT_ADDR_RE, line)
411 if match:
Jerome Forissier27b83ad2017-10-06 13:37:35 +0200412 self.reset()
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200413 # At this point the arch and TA load address are unknown.
414 # Save the line so We can translate the abort address later.
415 self._saved_abort_line = line
Jerome Forissier733a15f2017-05-19 17:40:17 +0200416 self._out.write(line)
417
418 def flush(self):
419 self._out.flush()
420
Jerome Forissierae252462018-05-25 15:07:28 +0200421
Jerome Forissier733a15f2017-05-19 17:40:17 +0200422def main():
423 args = get_args()
424 if args.dir:
425 # Flatten list in case -d is used several times *and* with multiple
426 # arguments
427 args.dirs = [item for sublist in args.dir for item in sublist]
428 else:
429 args.dirs = []
430 symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path)
431
432 for line in sys.stdin:
433 symbolizer.write(line)
434 symbolizer.flush()
435
Jerome Forissier1d8c2a42018-09-07 17:05:21 +0200436
Jerome Forissier733a15f2017-05-19 17:40:17 +0200437if __name__ == "__main__":
438 main()