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