blob: 68d98940523c621581d0cbf97eec80668952e24b [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
15TA_UUID_RE = re.compile(r'Status of TA (?P<uuid>[0-9a-f\-]+)')
Joakim Becha2b984b2017-12-15 14:34:56 +010016TA_INFO_RE = re.compile(' arch: (?P<arch>\w+) '
Jerome Forissier733a15f2017-05-19 17:40:17 +020017 'load address: (?P<load_addr>0x[0-9a-f]+)')
18CALL_STACK_RE = re.compile('Call stack:')
Joakim Becha2b984b2017-12-15 14:34:56 +010019
20# This gets the address from lines looking like this:
21# E/TC:0 0x001044a8
22STACK_ADDR_RE = re.compile(r'[UEIDFM]/T[AC]:.*(?P<addr>0x[0-9a-f]+)')
Jerome Forissier142c5cc2017-08-24 15:07:17 +020023ABORT_ADDR_RE = re.compile('-abort at address (?P<addr>0x[0-9a-f]+)')
Jerome Forissier30999122017-08-25 18:42:58 +020024REGION_RE = re.compile('region [0-9]+: va (?P<addr>0x[0-9a-f]+) '
25 'pa 0x[0-9a-f]+ size (?P<size>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,
35nm) are used to extract the debug info.
Jerome Forissier733a15f2017-05-19 17:40:17 +020036
Jerome Forissier0c5bedb2018-02-15 17:20:36 +010037OP-TEE abort and panic messages are sent to the secure console. They look like
38the following:
Jerome Forissier733a15f2017-05-19 17:40:17 +020039
Jerome Forissier0c5bedb2018-02-15 17:20:36 +010040 E/TC:0 User TA data-abort at address 0xffffdecd (alignment fault)
Jerome Forissier733a15f2017-05-19 17:40:17 +020041 ...
Jerome Forissier0c5bedb2018-02-15 17:20:36 +010042 E/TC:0 Call stack:
43 E/TC:0 0x4000549e
44 E/TC:0 0x40001f4b
45 E/TC:0 0x4000273f
46 E/TC:0 0x40005da7
Jerome Forissier733a15f2017-05-19 17:40:17 +020047
48Inspired by a script of the same name by the Chromium project.
49
50Sample usage:
51
52 $ scripts/symbolize.py -d out/arm-plat-hikey/core -d ../optee_test/out/ta/*
53 <paste whole dump here>
54 ^D
55'''
56
57def 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
74class Symbolizer(object):
75 def __init__(self, out, dirs, strip_path):
76 self._out = out
77 self._dirs = dirs
78 self._strip_path = strip_path
79 self._addr2line = None
80 self._bin = 'tee.elf'
81 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
96 if self._bin:
97 p = subprocess.Popen([ 'file', self.get_elf(self._bin) ],
98 stdout=subprocess.PIPE)
99 output = p.stdout.readlines()
100 p.terminate()
101 if 'ARM aarch64,' in output[0]:
102 self._arch = 'aarch64-linux-gnu-'
103 elif 'ARM,' in output[0]:
104 self._arch = 'arm-linux-gnueabihf-'
105
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200106 def arch_prefix(self, cmd):
Jerome Forissierd7204312017-09-04 17:58:52 +0200107 self.set_arch()
108 return self._arch + cmd
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200109
Jerome Forissier733a15f2017-05-19 17:40:17 +0200110 def spawn_addr2line(self):
111 if not self._addr2line:
112 elf = self.get_elf(self._bin)
113 if not elf:
114 return
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200115 cmd = self.arch_prefix('addr2line')
116 if not cmd:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200117 return
118 self._addr2line = subprocess.Popen([cmd, '-f', '-p', '-e', elf],
119 stdin = subprocess.PIPE,
120 stdout = subprocess.PIPE)
121
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200122 def subtract_load_addr(self, addr):
Jerome Forissier733a15f2017-05-19 17:40:17 +0200123 offs = self._load_addr
Jerome Forissierfd5d0622017-08-30 13:15:23 +0200124 if int(offs, 16) > int(addr, 16):
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200125 return ''
126 return '0x{:x}'.format(int(addr, 16) - int(offs, 16))
127
128 def resolve(self, addr):
129 reladdr = self.subtract_load_addr(addr)
Jerome Forissier733a15f2017-05-19 17:40:17 +0200130 self.spawn_addr2line()
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200131 if not reladdr or not self._addr2line:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200132 return '???'
133 try:
134 print >> self._addr2line.stdin, reladdr
135 ret = self._addr2line.stdout.readline().rstrip('\n')
136 except IOError:
137 ret = '!!!'
138 return ret
139
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200140 def symbol_plus_offset(self, addr):
141 ret = ''
142 prevsize = 0
143 reladdr = self.subtract_load_addr(addr)
144 elf = self.get_elf(self._bin)
145 cmd = self.arch_prefix('nm')
146 if not reladdr or not elf or not cmd:
147 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200148 ireladdr = int(reladdr, 16)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200149 nm = subprocess.Popen([cmd, '--numeric-sort', '--print-size', elf],
150 stdin = subprocess.PIPE,
151 stdout = subprocess.PIPE)
152 for line in iter(nm.stdout.readline, ''):
153 try:
154 addr, size, _, name = line.split()
155 except:
156 # Size is missing
157 addr, _, name = line.split()
158 size = '0'
159 iaddr = int(addr, 16)
160 isize = int(size, 16)
161 if iaddr == ireladdr:
162 ret = name
163 break
164 if iaddr < ireladdr and iaddr + isize >= ireladdr:
165 offs = ireladdr - iaddr
166 ret = name + '+' + str(offs)
167 break
168 if iaddr > ireladdr and prevsize == 0:
169 offs = iaddr + ireladdr
170 ret = prevname + '+' + str(offs)
171 break
172 prevsize = size
173 prevname = name
174 nm.terminate()
175 return ret
176
177 def section_plus_offset(self, addr):
178 ret = ''
179 reladdr = self.subtract_load_addr(addr)
180 elf = self.get_elf(self._bin)
181 cmd = self.arch_prefix('objdump')
182 if not reladdr or not elf or not cmd:
183 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200184 iaddr = int(reladdr, 16)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200185 objdump = subprocess.Popen([cmd, '--section-headers', elf],
186 stdin = subprocess.PIPE,
187 stdout = subprocess.PIPE)
188 for line in iter(objdump.stdout.readline, ''):
189 try:
190 idx, name, size, vma, lma, offs, algn = line.split()
191 except:
192 continue;
193 ivma = int(vma, 16)
194 isize = int(size, 16)
195 if ivma == iaddr:
196 ret = name
197 break
198 if ivma < iaddr and ivma + isize >= iaddr:
199 offs = iaddr - ivma
200 ret = name + '+' + str(offs)
201 break
202 objdump.terminate()
203 return ret
204
205 def process_abort(self, line):
206 ret = ''
207 match = re.search(ABORT_ADDR_RE, line)
208 addr = match.group('addr')
209 pre = match.start('addr')
210 post = match.end('addr')
211 sym = self.symbol_plus_offset(addr)
212 sec = self.section_plus_offset(addr)
213 if sym or sec:
214 ret += line[:pre]
215 ret += addr
216 if sym:
217 ret += ' ' + sym
218 if sec:
219 ret += ' ' + sec
220 ret += line[post:]
221 return ret
222
Jerome Forissier30999122017-08-25 18:42:58 +0200223 # Return all ELF sections with the ALLOC flag
224 def read_sections(self):
225 if self._sections:
226 return
227 elf = self.get_elf(self._bin)
228 cmd = self.arch_prefix('objdump')
229 if not elf or not cmd:
230 return
231 objdump = subprocess.Popen([cmd, '--section-headers', elf],
232 stdin = subprocess.PIPE,
233 stdout = subprocess.PIPE)
234 for line in iter(objdump.stdout.readline, ''):
235 try:
236 _, name, size, vma, _, _, _ = line.split()
237 except:
238 if 'ALLOC' in line:
239 self._sections.append([name, int(vma, 16), int(size, 16)])
240
241 def overlaps(self, section, addr, size):
242 sec_addr = section[1]
243 sec_size = section[2]
244 if not size or not sec_size:
245 return False
246 return (addr <= (sec_addr + sec_size - 1)) and ((addr + size - 1) >= sec_addr)
247
248 def sections_in_region(self, addr, size):
249 ret = ''
250 addr = self.subtract_load_addr(addr)
251 if not addr:
252 return ''
253 iaddr = int(addr, 16)
254 isize = int(size, 16)
255 self.read_sections()
256 for s in self._sections:
257 if self.overlaps(s, iaddr, isize):
258 ret += ' ' + s[0]
259 return ret
260
Jerome Forissier733a15f2017-05-19 17:40:17 +0200261 def reset(self):
262 self._call_stack_found = False
263 self._load_addr = '0'
264 if self._addr2line:
265 self._addr2line.terminate()
266 self._addr2line = None
Jerome Forissierd7204312017-09-04 17:58:52 +0200267 self._arch = None
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200268 self._saved_abort_line = ''
Jerome Forissier30999122017-08-25 18:42:58 +0200269 self._sections = []
Jerome Forissier27b83ad2017-10-06 13:37:35 +0200270 self._bin = "tee.elf"
Jerome Forissier733a15f2017-05-19 17:40:17 +0200271
272 def write(self, line):
273 if self._call_stack_found:
274 match = re.search(STACK_ADDR_RE, line)
275 if match:
276 addr = match.group('addr')
277 pre = match.start('addr')
278 post = match.end('addr')
279 self._out.write(line[:pre])
280 self._out.write(addr)
281 res = self.resolve(addr)
282 if self._strip_path:
283 res = re.sub(re.escape(self._strip_path) + '/*', '',
284 res)
285 self._out.write(' ' + res)
286 self._out.write(line[post:])
287 return
288 else:
289 self.reset()
Jerome Forissier30999122017-08-25 18:42:58 +0200290 match = re.search(REGION_RE, line)
291 if match:
292 addr = match.group('addr')
293 size = match.group('size')
294 self._out.write(line.strip() +
295 self.sections_in_region(addr, size) + '\n');
296 return
Jerome Forissier733a15f2017-05-19 17:40:17 +0200297 match = re.search(CALL_STACK_RE, line)
298 if match:
299 self._call_stack_found = True
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200300 # Here is a good place to resolve the abort address because we
301 # have all the information we need
302 if self._saved_abort_line:
303 self._out.write(self.process_abort(self._saved_abort_line))
Jerome Forissier733a15f2017-05-19 17:40:17 +0200304 match = re.search(TA_UUID_RE, line)
305 if match:
306 self._bin = match.group('uuid')
307 match = re.search(TA_INFO_RE, line)
308 if match:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200309 self._load_addr = match.group('load_addr')
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200310 match = re.search(ABORT_ADDR_RE, line)
311 if match:
Jerome Forissier27b83ad2017-10-06 13:37:35 +0200312 self.reset()
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200313 # At this point the arch and TA load address are unknown.
314 # Save the line so We can translate the abort address later.
315 self._saved_abort_line = line
Jerome Forissier733a15f2017-05-19 17:40:17 +0200316 self._out.write(line)
317
318 def flush(self):
319 self._out.flush()
320
321def main():
322 args = get_args()
323 if args.dir:
324 # Flatten list in case -d is used several times *and* with multiple
325 # arguments
326 args.dirs = [item for sublist in args.dir for item in sublist]
327 else:
328 args.dirs = []
329 symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path)
330
331 for line in sys.stdin:
332 symbolizer.write(line)
333 symbolizer.flush()
334
335if __name__ == "__main__":
336 main()