blob: 5b136d0350a4179fda68e8dd5b532319ec649b70 [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 Forissier5f7df502018-02-15 16:57:55 +010064 parser.add_argument('-s', '--strip_path', nargs='?',
65 help='Strip STRIP_PATH from file paths (default: current directory, '
66 'use -s with no argument to show full paths)',
67 default=os.getcwd())
Jerome Forissier733a15f2017-05-19 17:40:17 +020068
69 return parser.parse_args()
70
71class Symbolizer(object):
72 def __init__(self, out, dirs, strip_path):
73 self._out = out
74 self._dirs = dirs
75 self._strip_path = strip_path
76 self._addr2line = None
77 self._bin = 'tee.elf'
78 self.reset()
79
80 def get_elf(self, elf_or_uuid):
81 if not elf_or_uuid.endswith('.elf'):
82 elf_or_uuid += '.elf'
83 for d in self._dirs:
Jerome Forissier157e6212017-08-24 15:49:16 +020084 if d.endswith(elf_or_uuid) and os.path.isfile(d):
85 return d
Jerome Forissier733a15f2017-05-19 17:40:17 +020086 elf = glob.glob(d + '/' + elf_or_uuid)
87 if elf:
88 return elf[0]
89
Jerome Forissierd7204312017-09-04 17:58:52 +020090 def set_arch(self):
91 if self._arch:
92 return
93 if self._bin:
94 p = subprocess.Popen([ 'file', self.get_elf(self._bin) ],
95 stdout=subprocess.PIPE)
96 output = p.stdout.readlines()
97 p.terminate()
98 if 'ARM aarch64,' in output[0]:
99 self._arch = 'aarch64-linux-gnu-'
100 elif 'ARM,' in output[0]:
101 self._arch = 'arm-linux-gnueabihf-'
102
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200103 def arch_prefix(self, cmd):
Jerome Forissierd7204312017-09-04 17:58:52 +0200104 self.set_arch()
105 return self._arch + cmd
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200106
Jerome Forissier733a15f2017-05-19 17:40:17 +0200107 def spawn_addr2line(self):
108 if not self._addr2line:
109 elf = self.get_elf(self._bin)
110 if not elf:
111 return
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200112 cmd = self.arch_prefix('addr2line')
113 if not cmd:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200114 return
115 self._addr2line = subprocess.Popen([cmd, '-f', '-p', '-e', elf],
116 stdin = subprocess.PIPE,
117 stdout = subprocess.PIPE)
118
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200119 def subtract_load_addr(self, addr):
Jerome Forissier733a15f2017-05-19 17:40:17 +0200120 offs = self._load_addr
Jerome Forissierfd5d0622017-08-30 13:15:23 +0200121 if int(offs, 16) > int(addr, 16):
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200122 return ''
123 return '0x{:x}'.format(int(addr, 16) - int(offs, 16))
124
125 def resolve(self, addr):
126 reladdr = self.subtract_load_addr(addr)
Jerome Forissier733a15f2017-05-19 17:40:17 +0200127 self.spawn_addr2line()
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200128 if not reladdr or not self._addr2line:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200129 return '???'
130 try:
131 print >> self._addr2line.stdin, reladdr
132 ret = self._addr2line.stdout.readline().rstrip('\n')
133 except IOError:
134 ret = '!!!'
135 return ret
136
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200137 def symbol_plus_offset(self, addr):
138 ret = ''
139 prevsize = 0
140 reladdr = self.subtract_load_addr(addr)
141 elf = self.get_elf(self._bin)
142 cmd = self.arch_prefix('nm')
143 if not reladdr or not elf or not cmd:
144 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200145 ireladdr = int(reladdr, 16)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200146 nm = subprocess.Popen([cmd, '--numeric-sort', '--print-size', elf],
147 stdin = subprocess.PIPE,
148 stdout = subprocess.PIPE)
149 for line in iter(nm.stdout.readline, ''):
150 try:
151 addr, size, _, name = line.split()
152 except:
153 # Size is missing
154 addr, _, name = line.split()
155 size = '0'
156 iaddr = int(addr, 16)
157 isize = int(size, 16)
158 if iaddr == ireladdr:
159 ret = name
160 break
161 if iaddr < ireladdr and iaddr + isize >= ireladdr:
162 offs = ireladdr - iaddr
163 ret = name + '+' + str(offs)
164 break
165 if iaddr > ireladdr and prevsize == 0:
166 offs = iaddr + ireladdr
167 ret = prevname + '+' + str(offs)
168 break
169 prevsize = size
170 prevname = name
171 nm.terminate()
172 return ret
173
174 def section_plus_offset(self, addr):
175 ret = ''
176 reladdr = self.subtract_load_addr(addr)
177 elf = self.get_elf(self._bin)
178 cmd = self.arch_prefix('objdump')
179 if not reladdr or not elf or not cmd:
180 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200181 iaddr = int(reladdr, 16)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200182 objdump = subprocess.Popen([cmd, '--section-headers', elf],
183 stdin = subprocess.PIPE,
184 stdout = subprocess.PIPE)
185 for line in iter(objdump.stdout.readline, ''):
186 try:
187 idx, name, size, vma, lma, offs, algn = line.split()
188 except:
189 continue;
190 ivma = int(vma, 16)
191 isize = int(size, 16)
192 if ivma == iaddr:
193 ret = name
194 break
195 if ivma < iaddr and ivma + isize >= iaddr:
196 offs = iaddr - ivma
197 ret = name + '+' + str(offs)
198 break
199 objdump.terminate()
200 return ret
201
202 def process_abort(self, line):
203 ret = ''
204 match = re.search(ABORT_ADDR_RE, line)
205 addr = match.group('addr')
206 pre = match.start('addr')
207 post = match.end('addr')
208 sym = self.symbol_plus_offset(addr)
209 sec = self.section_plus_offset(addr)
210 if sym or sec:
211 ret += line[:pre]
212 ret += addr
213 if sym:
214 ret += ' ' + sym
215 if sec:
216 ret += ' ' + sec
217 ret += line[post:]
218 return ret
219
Jerome Forissier30999122017-08-25 18:42:58 +0200220 # Return all ELF sections with the ALLOC flag
221 def read_sections(self):
222 if self._sections:
223 return
224 elf = self.get_elf(self._bin)
225 cmd = self.arch_prefix('objdump')
226 if not elf or not cmd:
227 return
228 objdump = subprocess.Popen([cmd, '--section-headers', elf],
229 stdin = subprocess.PIPE,
230 stdout = subprocess.PIPE)
231 for line in iter(objdump.stdout.readline, ''):
232 try:
233 _, name, size, vma, _, _, _ = line.split()
234 except:
235 if 'ALLOC' in line:
236 self._sections.append([name, int(vma, 16), int(size, 16)])
237
238 def overlaps(self, section, addr, size):
239 sec_addr = section[1]
240 sec_size = section[2]
241 if not size or not sec_size:
242 return False
243 return (addr <= (sec_addr + sec_size - 1)) and ((addr + size - 1) >= sec_addr)
244
245 def sections_in_region(self, addr, size):
246 ret = ''
247 addr = self.subtract_load_addr(addr)
248 if not addr:
249 return ''
250 iaddr = int(addr, 16)
251 isize = int(size, 16)
252 self.read_sections()
253 for s in self._sections:
254 if self.overlaps(s, iaddr, isize):
255 ret += ' ' + s[0]
256 return ret
257
Jerome Forissier733a15f2017-05-19 17:40:17 +0200258 def reset(self):
259 self._call_stack_found = False
260 self._load_addr = '0'
261 if self._addr2line:
262 self._addr2line.terminate()
263 self._addr2line = None
Jerome Forissierd7204312017-09-04 17:58:52 +0200264 self._arch = None
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200265 self._saved_abort_line = ''
Jerome Forissier30999122017-08-25 18:42:58 +0200266 self._sections = []
Jerome Forissier27b83ad2017-10-06 13:37:35 +0200267 self._bin = "tee.elf"
Jerome Forissier733a15f2017-05-19 17:40:17 +0200268
269 def write(self, line):
270 if self._call_stack_found:
271 match = re.search(STACK_ADDR_RE, line)
272 if match:
273 addr = match.group('addr')
274 pre = match.start('addr')
275 post = match.end('addr')
276 self._out.write(line[:pre])
277 self._out.write(addr)
278 res = self.resolve(addr)
279 if self._strip_path:
280 res = re.sub(re.escape(self._strip_path) + '/*', '',
281 res)
282 self._out.write(' ' + res)
283 self._out.write(line[post:])
284 return
285 else:
286 self.reset()
Jerome Forissier30999122017-08-25 18:42:58 +0200287 match = re.search(REGION_RE, line)
288 if match:
289 addr = match.group('addr')
290 size = match.group('size')
291 self._out.write(line.strip() +
292 self.sections_in_region(addr, size) + '\n');
293 return
Jerome Forissier733a15f2017-05-19 17:40:17 +0200294 match = re.search(CALL_STACK_RE, line)
295 if match:
296 self._call_stack_found = True
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200297 # Here is a good place to resolve the abort address because we
298 # have all the information we need
299 if self._saved_abort_line:
300 self._out.write(self.process_abort(self._saved_abort_line))
Jerome Forissier733a15f2017-05-19 17:40:17 +0200301 match = re.search(TA_UUID_RE, line)
302 if match:
303 self._bin = match.group('uuid')
304 match = re.search(TA_INFO_RE, line)
305 if match:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200306 self._load_addr = match.group('load_addr')
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200307 match = re.search(ABORT_ADDR_RE, line)
308 if match:
Jerome Forissier27b83ad2017-10-06 13:37:35 +0200309 self.reset()
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200310 # At this point the arch and TA load address are unknown.
311 # Save the line so We can translate the abort address later.
312 self._saved_abort_line = line
Jerome Forissier733a15f2017-05-19 17:40:17 +0200313 self._out.write(line)
314
315 def flush(self):
316 self._out.flush()
317
318def main():
319 args = get_args()
320 if args.dir:
321 # Flatten list in case -d is used several times *and* with multiple
322 # arguments
323 args.dirs = [item for sublist in args.dir for item in sublist]
324 else:
325 args.dirs = []
326 symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path)
327
328 for line in sys.stdin:
329 symbolizer.write(line)
330 symbolizer.flush()
331
332if __name__ == "__main__":
333 main()