blob: 77f842f703fd63585fa134699cf2bfff3e3a0986 [file] [log] [blame]
Jerome Forissier733a15f2017-05-19 17:40:17 +02001#!/usr/bin/env python
2#
3# Copyright (c) 2017, Linaro Limited
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are met:
8#
9# 1. Redistributions of source code must retain the above copyright notice,
10# this list of conditions and the following disclaimer.
11#
12# 2. Redistributions in binary form must reproduce the above copyright notice,
13# this list of conditions and the following disclaimer in the documentation
14# and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26# POSSIBILITY OF SUCH DAMAGE.
27#
28
29
30import argparse
31import glob
Jerome Forissier157e6212017-08-24 15:49:16 +020032import os
Jerome Forissier733a15f2017-05-19 17:40:17 +020033import re
34import subprocess
35import sys
36
37TA_UUID_RE = re.compile(r'Status of TA (?P<uuid>[0-9a-f\-]+)')
Joakim Becha2b984b2017-12-15 14:34:56 +010038TA_INFO_RE = re.compile(' arch: (?P<arch>\w+) '
Jerome Forissier733a15f2017-05-19 17:40:17 +020039 'load address: (?P<load_addr>0x[0-9a-f]+)')
40CALL_STACK_RE = re.compile('Call stack:')
Joakim Becha2b984b2017-12-15 14:34:56 +010041
42# This gets the address from lines looking like this:
43# E/TC:0 0x001044a8
44STACK_ADDR_RE = re.compile(r'[UEIDFM]/T[AC]:.*(?P<addr>0x[0-9a-f]+)')
Jerome Forissier142c5cc2017-08-24 15:07:17 +020045ABORT_ADDR_RE = re.compile('-abort at address (?P<addr>0x[0-9a-f]+)')
Jerome Forissier30999122017-08-25 18:42:58 +020046REGION_RE = re.compile('region [0-9]+: va (?P<addr>0x[0-9a-f]+) '
47 'pa 0x[0-9a-f]+ size (?P<size>0x[0-9a-f]+)')
Jerome Forissier733a15f2017-05-19 17:40:17 +020048
49epilog = '''
50This scripts reads an OP-TEE abort message from stdin and adds debug
51information ('function at file:line') next to each address in the call stack.
52It uses the paths provided on the command line to locate the appropriate ELF
53binary (tee.elf or Trusted Application) and runs arm-linux-gnueabihf-addr2line
54or aarch64-linux-gnu-addr2line to process the addresses.
55
56OP-TEE abort messages are sent to the secure console. They look like the
57following:
58
59 ERROR: TEE-CORE: User TA data-abort at address 0xffffdecd (alignment fault)
60 ...
61 ERROR: TEE-CORE: Call stack:
62 ERROR: TEE-CORE: 0x4000549e
63 ERROR: TEE-CORE: 0x40001f4b
64 ERROR: TEE-CORE: 0x4000273f
65 ERROR: TEE-CORE: 0x40005da7
66
67Inspired by a script of the same name by the Chromium project.
68
69Sample usage:
70
71 $ scripts/symbolize.py -d out/arm-plat-hikey/core -d ../optee_test/out/ta/*
72 <paste whole dump here>
73 ^D
74'''
75
76def get_args():
77 parser = argparse.ArgumentParser(
78 formatter_class=argparse.RawDescriptionHelpFormatter,
79 description='Symbolizes OP-TEE abort dumps',
80 epilog=epilog)
81 parser.add_argument('-d', '--dir', action='append', nargs='+',
82 help='Search for ELF file in DIR. tee.elf is needed to decode '
83 'a TEE Core or pseudo-TA abort, while <TA_uuid>.elf is required '
Jerome Forissier157e6212017-08-24 15:49:16 +020084 'if a user-mode TA has crashed. For convenience, ELF files '
85 'may also be given.')
Jerome Forissier733a15f2017-05-19 17:40:17 +020086 parser.add_argument('-s', '--strip_path',
87 help='Strip STRIP_PATH from file paths')
88
89 return parser.parse_args()
90
91class Symbolizer(object):
92 def __init__(self, out, dirs, strip_path):
93 self._out = out
94 self._dirs = dirs
95 self._strip_path = strip_path
96 self._addr2line = None
97 self._bin = 'tee.elf'
98 self.reset()
99
100 def get_elf(self, elf_or_uuid):
101 if not elf_or_uuid.endswith('.elf'):
102 elf_or_uuid += '.elf'
103 for d in self._dirs:
Jerome Forissier157e6212017-08-24 15:49:16 +0200104 if d.endswith(elf_or_uuid) and os.path.isfile(d):
105 return d
Jerome Forissier733a15f2017-05-19 17:40:17 +0200106 elf = glob.glob(d + '/' + elf_or_uuid)
107 if elf:
108 return elf[0]
109
Jerome Forissierd7204312017-09-04 17:58:52 +0200110 def set_arch(self):
111 if self._arch:
112 return
113 if self._bin:
114 p = subprocess.Popen([ 'file', self.get_elf(self._bin) ],
115 stdout=subprocess.PIPE)
116 output = p.stdout.readlines()
117 p.terminate()
118 if 'ARM aarch64,' in output[0]:
119 self._arch = 'aarch64-linux-gnu-'
120 elif 'ARM,' in output[0]:
121 self._arch = 'arm-linux-gnueabihf-'
122
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200123 def arch_prefix(self, cmd):
Jerome Forissierd7204312017-09-04 17:58:52 +0200124 self.set_arch()
125 return self._arch + cmd
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200126
Jerome Forissier733a15f2017-05-19 17:40:17 +0200127 def spawn_addr2line(self):
128 if not self._addr2line:
129 elf = self.get_elf(self._bin)
130 if not elf:
131 return
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200132 cmd = self.arch_prefix('addr2line')
133 if not cmd:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200134 return
135 self._addr2line = subprocess.Popen([cmd, '-f', '-p', '-e', elf],
136 stdin = subprocess.PIPE,
137 stdout = subprocess.PIPE)
138
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200139 def subtract_load_addr(self, addr):
Jerome Forissier733a15f2017-05-19 17:40:17 +0200140 offs = self._load_addr
Jerome Forissierfd5d0622017-08-30 13:15:23 +0200141 if int(offs, 16) > int(addr, 16):
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200142 return ''
143 return '0x{:x}'.format(int(addr, 16) - int(offs, 16))
144
145 def resolve(self, addr):
146 reladdr = self.subtract_load_addr(addr)
Jerome Forissier733a15f2017-05-19 17:40:17 +0200147 self.spawn_addr2line()
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200148 if not reladdr or not self._addr2line:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200149 return '???'
150 try:
151 print >> self._addr2line.stdin, reladdr
152 ret = self._addr2line.stdout.readline().rstrip('\n')
153 except IOError:
154 ret = '!!!'
155 return ret
156
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200157 def symbol_plus_offset(self, addr):
158 ret = ''
159 prevsize = 0
160 reladdr = self.subtract_load_addr(addr)
161 elf = self.get_elf(self._bin)
162 cmd = self.arch_prefix('nm')
163 if not reladdr or not elf or not cmd:
164 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200165 ireladdr = int(reladdr, 16)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200166 nm = subprocess.Popen([cmd, '--numeric-sort', '--print-size', elf],
167 stdin = subprocess.PIPE,
168 stdout = subprocess.PIPE)
169 for line in iter(nm.stdout.readline, ''):
170 try:
171 addr, size, _, name = line.split()
172 except:
173 # Size is missing
174 addr, _, name = line.split()
175 size = '0'
176 iaddr = int(addr, 16)
177 isize = int(size, 16)
178 if iaddr == ireladdr:
179 ret = name
180 break
181 if iaddr < ireladdr and iaddr + isize >= ireladdr:
182 offs = ireladdr - iaddr
183 ret = name + '+' + str(offs)
184 break
185 if iaddr > ireladdr and prevsize == 0:
186 offs = iaddr + ireladdr
187 ret = prevname + '+' + str(offs)
188 break
189 prevsize = size
190 prevname = name
191 nm.terminate()
192 return ret
193
194 def section_plus_offset(self, addr):
195 ret = ''
196 reladdr = self.subtract_load_addr(addr)
197 elf = self.get_elf(self._bin)
198 cmd = self.arch_prefix('objdump')
199 if not reladdr or not elf or not cmd:
200 return ''
Jerome Forissier30999122017-08-25 18:42:58 +0200201 iaddr = int(reladdr, 16)
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200202 objdump = subprocess.Popen([cmd, '--section-headers', elf],
203 stdin = subprocess.PIPE,
204 stdout = subprocess.PIPE)
205 for line in iter(objdump.stdout.readline, ''):
206 try:
207 idx, name, size, vma, lma, offs, algn = line.split()
208 except:
209 continue;
210 ivma = int(vma, 16)
211 isize = int(size, 16)
212 if ivma == iaddr:
213 ret = name
214 break
215 if ivma < iaddr and ivma + isize >= iaddr:
216 offs = iaddr - ivma
217 ret = name + '+' + str(offs)
218 break
219 objdump.terminate()
220 return ret
221
222 def process_abort(self, line):
223 ret = ''
224 match = re.search(ABORT_ADDR_RE, line)
225 addr = match.group('addr')
226 pre = match.start('addr')
227 post = match.end('addr')
228 sym = self.symbol_plus_offset(addr)
229 sec = self.section_plus_offset(addr)
230 if sym or sec:
231 ret += line[:pre]
232 ret += addr
233 if sym:
234 ret += ' ' + sym
235 if sec:
236 ret += ' ' + sec
237 ret += line[post:]
238 return ret
239
Jerome Forissier30999122017-08-25 18:42:58 +0200240 # Return all ELF sections with the ALLOC flag
241 def read_sections(self):
242 if self._sections:
243 return
244 elf = self.get_elf(self._bin)
245 cmd = self.arch_prefix('objdump')
246 if not elf or not cmd:
247 return
248 objdump = subprocess.Popen([cmd, '--section-headers', elf],
249 stdin = subprocess.PIPE,
250 stdout = subprocess.PIPE)
251 for line in iter(objdump.stdout.readline, ''):
252 try:
253 _, name, size, vma, _, _, _ = line.split()
254 except:
255 if 'ALLOC' in line:
256 self._sections.append([name, int(vma, 16), int(size, 16)])
257
258 def overlaps(self, section, addr, size):
259 sec_addr = section[1]
260 sec_size = section[2]
261 if not size or not sec_size:
262 return False
263 return (addr <= (sec_addr + sec_size - 1)) and ((addr + size - 1) >= sec_addr)
264
265 def sections_in_region(self, addr, size):
266 ret = ''
267 addr = self.subtract_load_addr(addr)
268 if not addr:
269 return ''
270 iaddr = int(addr, 16)
271 isize = int(size, 16)
272 self.read_sections()
273 for s in self._sections:
274 if self.overlaps(s, iaddr, isize):
275 ret += ' ' + s[0]
276 return ret
277
Jerome Forissier733a15f2017-05-19 17:40:17 +0200278 def reset(self):
279 self._call_stack_found = False
280 self._load_addr = '0'
281 if self._addr2line:
282 self._addr2line.terminate()
283 self._addr2line = None
Jerome Forissierd7204312017-09-04 17:58:52 +0200284 self._arch = None
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200285 self._saved_abort_line = ''
Jerome Forissier30999122017-08-25 18:42:58 +0200286 self._sections = []
Jerome Forissier27b83ad2017-10-06 13:37:35 +0200287 self._bin = "tee.elf"
Jerome Forissier733a15f2017-05-19 17:40:17 +0200288
289 def write(self, line):
290 if self._call_stack_found:
291 match = re.search(STACK_ADDR_RE, line)
292 if match:
293 addr = match.group('addr')
294 pre = match.start('addr')
295 post = match.end('addr')
296 self._out.write(line[:pre])
297 self._out.write(addr)
298 res = self.resolve(addr)
299 if self._strip_path:
300 res = re.sub(re.escape(self._strip_path) + '/*', '',
301 res)
302 self._out.write(' ' + res)
303 self._out.write(line[post:])
304 return
305 else:
306 self.reset()
Jerome Forissier30999122017-08-25 18:42:58 +0200307 match = re.search(REGION_RE, line)
308 if match:
309 addr = match.group('addr')
310 size = match.group('size')
311 self._out.write(line.strip() +
312 self.sections_in_region(addr, size) + '\n');
313 return
Jerome Forissier733a15f2017-05-19 17:40:17 +0200314 match = re.search(CALL_STACK_RE, line)
315 if match:
316 self._call_stack_found = True
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200317 # Here is a good place to resolve the abort address because we
318 # have all the information we need
319 if self._saved_abort_line:
320 self._out.write(self.process_abort(self._saved_abort_line))
Jerome Forissier733a15f2017-05-19 17:40:17 +0200321 match = re.search(TA_UUID_RE, line)
322 if match:
323 self._bin = match.group('uuid')
324 match = re.search(TA_INFO_RE, line)
325 if match:
Jerome Forissier733a15f2017-05-19 17:40:17 +0200326 self._load_addr = match.group('load_addr')
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200327 match = re.search(ABORT_ADDR_RE, line)
328 if match:
Jerome Forissier27b83ad2017-10-06 13:37:35 +0200329 self.reset()
Jerome Forissier142c5cc2017-08-24 15:07:17 +0200330 # At this point the arch and TA load address are unknown.
331 # Save the line so We can translate the abort address later.
332 self._saved_abort_line = line
Jerome Forissier733a15f2017-05-19 17:40:17 +0200333 self._out.write(line)
334
335 def flush(self):
336 self._out.flush()
337
338def main():
339 args = get_args()
340 if args.dir:
341 # Flatten list in case -d is used several times *and* with multiple
342 # arguments
343 args.dirs = [item for sublist in args.dir for item in sublist]
344 else:
345 args.dirs = []
346 symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path)
347
348 for line in sys.stdin:
349 symbolizer.write(line)
350 symbolizer.flush()
351
352if __name__ == "__main__":
353 main()