blob: 5168203f278f8b2a3df850bd57390e8f59fa04c8 [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 = '''
28This scripts reads an OP-TEE abort message from stdin and adds debug
29information ('function at file:line') next to each address in the call stack.
30It uses the paths provided on the command line to locate the appropriate ELF
31binary (tee.elf or Trusted Application) and runs arm-linux-gnueabihf-addr2line
32or aarch64-linux-gnu-addr2line to process the addresses.
33
34OP-TEE abort messages are sent to the secure console. They look like the
35following:
36
37 ERROR: TEE-CORE: User TA data-abort at address 0xffffdecd (alignment fault)
38 ...
39 ERROR: TEE-CORE: Call stack:
40 ERROR: TEE-CORE: 0x4000549e
41 ERROR: TEE-CORE: 0x40001f4b
42 ERROR: TEE-CORE: 0x4000273f
43 ERROR: TEE-CORE: 0x40005da7
44
45Inspired by a script of the same name by the Chromium project.
46
47Sample usage:
48
49 $ scripts/symbolize.py -d out/arm-plat-hikey/core -d ../optee_test/out/ta/*
50 <paste whole dump here>
51 ^D
52'''
53
54def get_args():
55 parser = argparse.ArgumentParser(
56 formatter_class=argparse.RawDescriptionHelpFormatter,
57 description='Symbolizes OP-TEE abort dumps',
58 epilog=epilog)
59 parser.add_argument('-d', '--dir', action='append', nargs='+',
60 help='Search for ELF file in DIR. tee.elf is needed to decode '
61 'a TEE Core or pseudo-TA abort, while <TA_uuid>.elf is required '
Jerome Forissier157e6212017-08-24 15:49:16 +020062 'if a user-mode TA has crashed. For convenience, ELF files '
63 'may also be given.')
Jerome Forissier733a15f2017-05-19 17:40:17 +020064 parser.add_argument('-s', '--strip_path',
65 help='Strip STRIP_PATH from file paths')
66
67 return parser.parse_args()
68
69class Symbolizer(object):
70 def __init__(self, out, dirs, strip_path):
71 self._out = out
72 self._dirs = dirs
73 self._strip_path = strip_path
74 self._addr2line = None
75 self._bin = 'tee.elf'
76 self.reset()
77
78 def get_elf(self, elf_or_uuid):
79 if not elf_or_uuid.endswith('.elf'):
80 elf_or_uuid += '.elf'
81 for d in self._dirs:
Jerome Forissier157e6212017-08-24 15:49:16 +020082 if d.endswith(elf_or_uuid) and os.path.isfile(d):
83 return d
Jerome Forissier733a15f2017-05-19 17:40:17 +020084 elf = glob.glob(d + '/' + elf_or_uuid)
85 if elf:
86 return elf[0]
87
Jerome Forissierd7204312017-09-04 17:58:52 +020088 def set_arch(self):
89 if self._arch:
90 return
91 if self._bin:
92 p = subprocess.Popen([ 'file', self.get_elf(self._bin) ],
93 stdout=subprocess.PIPE)
94 output = p.stdout.readlines()
95 p.terminate()
96 if 'ARM aarch64,' in output[0]:
97 self._arch = 'aarch64-linux-gnu-'
98 elif 'ARM,' in output[0]:
99 self._arch = 'arm-linux-gnueabihf-'
100
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200101 def arch_prefix(self, cmd):
Jerome Forissierd7204312017-09-04 17:58:52 +0200102 self.set_arch()
103 return self._arch + cmd
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200104
Jerome Forissier733a15f2017-05-19 17:40:17 +0200105 def spawn_addr2line(self):
106 if not self._addr2line:
107 elf = self.get_elf(self._bin)
108 if not elf:
109 return
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200110 cmd = self.arch_prefix('addr2line')
111 if not cmd:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200112 return
113 self._addr2line = subprocess.Popen([cmd, '-f', '-p', '-e', elf],
114 stdin = subprocess.PIPE,
115 stdout = subprocess.PIPE)
116
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200117 def subtract_load_addr(self, addr):
Jerome Forissier733a15f2017-05-19 17:40:17 +0200118 offs = self._load_addr
Jerome Forissierfd5d0622017-08-30 13:15:23 +0200119 if int(offs, 16) > int(addr, 16):
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200120 return ''
121 return '0x{:x}'.format(int(addr, 16) - int(offs, 16))
122
123 def resolve(self, addr):
124 reladdr = self.subtract_load_addr(addr)
Jerome Forissier733a15f2017-05-19 17:40:17 +0200125 self.spawn_addr2line()
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200126 if not reladdr or not self._addr2line:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200127 return '???'
128 try:
129 print >> self._addr2line.stdin, reladdr
130 ret = self._addr2line.stdout.readline().rstrip('\n')
131 except IOError:
132 ret = '!!!'
133 return ret
134
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200135 def symbol_plus_offset(self, addr):
136 ret = ''
137 prevsize = 0
138 reladdr = self.subtract_load_addr(addr)
139 elf = self.get_elf(self._bin)
140 cmd = self.arch_prefix('nm')
141 if not reladdr or not elf or not cmd:
142 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200143 ireladdr = int(reladdr, 16)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200144 nm = subprocess.Popen([cmd, '--numeric-sort', '--print-size', elf],
145 stdin = subprocess.PIPE,
146 stdout = subprocess.PIPE)
147 for line in iter(nm.stdout.readline, ''):
148 try:
149 addr, size, _, name = line.split()
150 except:
151 # Size is missing
152 addr, _, name = line.split()
153 size = '0'
154 iaddr = int(addr, 16)
155 isize = int(size, 16)
156 if iaddr == ireladdr:
157 ret = name
158 break
159 if iaddr < ireladdr and iaddr + isize >= ireladdr:
160 offs = ireladdr - iaddr
161 ret = name + '+' + str(offs)
162 break
163 if iaddr > ireladdr and prevsize == 0:
164 offs = iaddr + ireladdr
165 ret = prevname + '+' + str(offs)
166 break
167 prevsize = size
168 prevname = name
169 nm.terminate()
170 return ret
171
172 def section_plus_offset(self, addr):
173 ret = ''
174 reladdr = self.subtract_load_addr(addr)
175 elf = self.get_elf(self._bin)
176 cmd = self.arch_prefix('objdump')
177 if not reladdr or not elf or not cmd:
178 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200179 iaddr = int(reladdr, 16)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200180 objdump = subprocess.Popen([cmd, '--section-headers', elf],
181 stdin = subprocess.PIPE,
182 stdout = subprocess.PIPE)
183 for line in iter(objdump.stdout.readline, ''):
184 try:
185 idx, name, size, vma, lma, offs, algn = line.split()
186 except:
187 continue;
188 ivma = int(vma, 16)
189 isize = int(size, 16)
190 if ivma == iaddr:
191 ret = name
192 break
193 if ivma < iaddr and ivma + isize >= iaddr:
194 offs = iaddr - ivma
195 ret = name + '+' + str(offs)
196 break
197 objdump.terminate()
198 return ret
199
200 def process_abort(self, line):
201 ret = ''
202 match = re.search(ABORT_ADDR_RE, line)
203 addr = match.group('addr')
204 pre = match.start('addr')
205 post = match.end('addr')
206 sym = self.symbol_plus_offset(addr)
207 sec = self.section_plus_offset(addr)
208 if sym or sec:
209 ret += line[:pre]
210 ret += addr
211 if sym:
212 ret += ' ' + sym
213 if sec:
214 ret += ' ' + sec
215 ret += line[post:]
216 return ret
217
Jerome Forissier30999122017-08-25 18:42:58 +0200218 # Return all ELF sections with the ALLOC flag
219 def read_sections(self):
220 if self._sections:
221 return
222 elf = self.get_elf(self._bin)
223 cmd = self.arch_prefix('objdump')
224 if not elf or not cmd:
225 return
226 objdump = subprocess.Popen([cmd, '--section-headers', elf],
227 stdin = subprocess.PIPE,
228 stdout = subprocess.PIPE)
229 for line in iter(objdump.stdout.readline, ''):
230 try:
231 _, name, size, vma, _, _, _ = line.split()
232 except:
233 if 'ALLOC' in line:
234 self._sections.append([name, int(vma, 16), int(size, 16)])
235
236 def overlaps(self, section, addr, size):
237 sec_addr = section[1]
238 sec_size = section[2]
239 if not size or not sec_size:
240 return False
241 return (addr <= (sec_addr + sec_size - 1)) and ((addr + size - 1) >= sec_addr)
242
243 def sections_in_region(self, addr, size):
244 ret = ''
245 addr = self.subtract_load_addr(addr)
246 if not addr:
247 return ''
248 iaddr = int(addr, 16)
249 isize = int(size, 16)
250 self.read_sections()
251 for s in self._sections:
252 if self.overlaps(s, iaddr, isize):
253 ret += ' ' + s[0]
254 return ret
255
Jerome Forissier733a15f2017-05-19 17:40:17 +0200256 def reset(self):
257 self._call_stack_found = False
258 self._load_addr = '0'
259 if self._addr2line:
260 self._addr2line.terminate()
261 self._addr2line = None
Jerome Forissierd7204312017-09-04 17:58:52 +0200262 self._arch = None
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200263 self._saved_abort_line = ''
Jerome Forissier30999122017-08-25 18:42:58 +0200264 self._sections = []
Jerome Forissier27b83ad2017-10-06 13:37:35 +0200265 self._bin = "tee.elf"
Jerome Forissier733a15f2017-05-19 17:40:17 +0200266
267 def write(self, line):
268 if self._call_stack_found:
269 match = re.search(STACK_ADDR_RE, line)
270 if match:
271 addr = match.group('addr')
272 pre = match.start('addr')
273 post = match.end('addr')
274 self._out.write(line[:pre])
275 self._out.write(addr)
276 res = self.resolve(addr)
277 if self._strip_path:
278 res = re.sub(re.escape(self._strip_path) + '/*', '',
279 res)
280 self._out.write(' ' + res)
281 self._out.write(line[post:])
282 return
283 else:
284 self.reset()
Jerome Forissier30999122017-08-25 18:42:58 +0200285 match = re.search(REGION_RE, line)
286 if match:
287 addr = match.group('addr')
288 size = match.group('size')
289 self._out.write(line.strip() +
290 self.sections_in_region(addr, size) + '\n');
291 return
Jerome Forissier733a15f2017-05-19 17:40:17 +0200292 match = re.search(CALL_STACK_RE, line)
293 if match:
294 self._call_stack_found = True
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200295 # Here is a good place to resolve the abort address because we
296 # have all the information we need
297 if self._saved_abort_line:
298 self._out.write(self.process_abort(self._saved_abort_line))
Jerome Forissier733a15f2017-05-19 17:40:17 +0200299 match = re.search(TA_UUID_RE, line)
300 if match:
301 self._bin = match.group('uuid')
302 match = re.search(TA_INFO_RE, line)
303 if match:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200304 self._load_addr = match.group('load_addr')
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200305 match = re.search(ABORT_ADDR_RE, line)
306 if match:
Jerome Forissier27b83ad2017-10-06 13:37:35 +0200307 self.reset()
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200308 # At this point the arch and TA load address are unknown.
309 # Save the line so We can translate the abort address later.
310 self._saved_abort_line = line
Jerome Forissier733a15f2017-05-19 17:40:17 +0200311 self._out.write(line)
312
313 def flush(self):
314 self._out.flush()
315
316def main():
317 args = get_args()
318 if args.dir:
319 # Flatten list in case -d is used several times *and* with multiple
320 # arguments
321 args.dirs = [item for sublist in args.dir for item in sublist]
322 else:
323 args.dirs = []
324 symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path)
325
326 for line in sys.stdin:
327 symbolizer.write(line)
328 symbolizer.flush()
329
330if __name__ == "__main__":
331 main()