1#!/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 32import os 33import re 34import subprocess 35import sys 36 37TA_UUID_RE = re.compile(r'Status of TA (?P<uuid>[0-9a-f\-]+)') 38TA_INFO_RE = re.compile(': arch: (?P<arch>\w+) ' 39 'load address: (?P<load_addr>0x[0-9a-f]+)') 40CALL_STACK_RE = re.compile('Call stack:') 41STACK_ADDR_RE = re.compile(r': (?P<addr>0x[0-9a-f]+)') 42X64_REGS_RE = re.compile(': x0 [0-9a-f]{16} x1 [0-9a-f]{16}') 43ABORT_ADDR_RE = re.compile('-abort at address (?P<addr>0x[0-9a-f]+)') 44 45epilog = ''' 46This scripts reads an OP-TEE abort message from stdin and adds debug 47information ('function at file:line') next to each address in the call stack. 48It uses the paths provided on the command line to locate the appropriate ELF 49binary (tee.elf or Trusted Application) and runs arm-linux-gnueabihf-addr2line 50or aarch64-linux-gnu-addr2line to process the addresses. 51 52OP-TEE abort messages are sent to the secure console. They look like the 53following: 54 55 ERROR: TEE-CORE: User TA data-abort at address 0xffffdecd (alignment fault) 56 ... 57 ERROR: TEE-CORE: Call stack: 58 ERROR: TEE-CORE: 0x4000549e 59 ERROR: TEE-CORE: 0x40001f4b 60 ERROR: TEE-CORE: 0x4000273f 61 ERROR: TEE-CORE: 0x40005da7 62 63Inspired by a script of the same name by the Chromium project. 64 65Sample usage: 66 67 $ scripts/symbolize.py -d out/arm-plat-hikey/core -d ../optee_test/out/ta/* 68 <paste whole dump here> 69 ^D 70''' 71 72def get_args(): 73 parser = argparse.ArgumentParser( 74 formatter_class=argparse.RawDescriptionHelpFormatter, 75 description='Symbolizes OP-TEE abort dumps', 76 epilog=epilog) 77 parser.add_argument('-d', '--dir', action='append', nargs='+', 78 help='Search for ELF file in DIR. tee.elf is needed to decode ' 79 'a TEE Core or pseudo-TA abort, while <TA_uuid>.elf is required ' 80 'if a user-mode TA has crashed. For convenience, ELF files ' 81 'may also be given.') 82 parser.add_argument('-s', '--strip_path', 83 help='Strip STRIP_PATH from file paths') 84 85 return parser.parse_args() 86 87class Symbolizer(object): 88 def __init__(self, out, dirs, strip_path): 89 self._out = out 90 self._dirs = dirs 91 self._strip_path = strip_path 92 self._addr2line = None 93 self._bin = 'tee.elf' 94 self.reset() 95 96 def get_elf(self, elf_or_uuid): 97 if not elf_or_uuid.endswith('.elf'): 98 elf_or_uuid += '.elf' 99 for d in self._dirs: 100 if d.endswith(elf_or_uuid) and os.path.isfile(d): 101 return d 102 elf = glob.glob(d + '/' + elf_or_uuid) 103 if elf: 104 return elf[0] 105 106 def arch_prefix(self, cmd): 107 if self._arch == 'arm': 108 return 'arm-linux-gnueabihf-' + cmd 109 elif self._arch == 'aarch64': 110 return 'aarch64-linux-gnu-' + cmd 111 else: 112 return '' 113 114 def spawn_addr2line(self): 115 if not self._addr2line: 116 elf = self.get_elf(self._bin) 117 if not elf: 118 return 119 cmd = self.arch_prefix('addr2line') 120 if not cmd: 121 return 122 self._addr2line = subprocess.Popen([cmd, '-f', '-p', '-e', elf], 123 stdin = subprocess.PIPE, 124 stdout = subprocess.PIPE) 125 126 def subtract_load_addr(self, addr): 127 offs = self._load_addr 128 if int(offs, 16) > int(addr, 16): 129 return '' 130 return '0x{:x}'.format(int(addr, 16) - int(offs, 16)) 131 132 def resolve(self, addr): 133 reladdr = self.subtract_load_addr(addr) 134 self.spawn_addr2line() 135 if not reladdr or not self._addr2line: 136 return '???' 137 try: 138 print >> self._addr2line.stdin, reladdr 139 ret = self._addr2line.stdout.readline().rstrip('\n') 140 except IOError: 141 ret = '!!!' 142 return ret 143 144 def symbol_plus_offset(self, addr): 145 ret = '' 146 prevsize = 0 147 reladdr = self.subtract_load_addr(addr) 148 elf = self.get_elf(self._bin) 149 cmd = self.arch_prefix('nm') 150 if not reladdr or not elf or not cmd: 151 return '' 152 ireladdr = int(reladdr, 0) 153 nm = subprocess.Popen([cmd, '--numeric-sort', '--print-size', elf], 154 stdin = subprocess.PIPE, 155 stdout = subprocess.PIPE) 156 for line in iter(nm.stdout.readline, ''): 157 try: 158 addr, size, _, name = line.split() 159 except: 160 # Size is missing 161 addr, _, name = line.split() 162 size = '0' 163 iaddr = int(addr, 16) 164 isize = int(size, 16) 165 if iaddr == ireladdr: 166 ret = name 167 break 168 if iaddr < ireladdr and iaddr + isize >= ireladdr: 169 offs = ireladdr - iaddr 170 ret = name + '+' + str(offs) 171 break 172 if iaddr > ireladdr and prevsize == 0: 173 offs = iaddr + ireladdr 174 ret = prevname + '+' + str(offs) 175 break 176 prevsize = size 177 prevname = name 178 nm.terminate() 179 return ret 180 181 def section_plus_offset(self, addr): 182 ret = '' 183 reladdr = self.subtract_load_addr(addr) 184 elf = self.get_elf(self._bin) 185 cmd = self.arch_prefix('objdump') 186 if not reladdr or not elf or not cmd: 187 return '' 188 iaddr = int(reladdr, 0) 189 objdump = subprocess.Popen([cmd, '--section-headers', elf], 190 stdin = subprocess.PIPE, 191 stdout = subprocess.PIPE) 192 for line in iter(objdump.stdout.readline, ''): 193 try: 194 idx, name, size, vma, lma, offs, algn = line.split() 195 except: 196 continue; 197 ivma = int(vma, 16) 198 isize = int(size, 16) 199 if ivma == iaddr: 200 ret = name 201 break 202 if ivma < iaddr and ivma + isize >= iaddr: 203 offs = iaddr - ivma 204 ret = name + '+' + str(offs) 205 break 206 objdump.terminate() 207 return ret 208 209 def process_abort(self, line): 210 ret = '' 211 match = re.search(ABORT_ADDR_RE, line) 212 addr = match.group('addr') 213 pre = match.start('addr') 214 post = match.end('addr') 215 sym = self.symbol_plus_offset(addr) 216 sec = self.section_plus_offset(addr) 217 if sym or sec: 218 ret += line[:pre] 219 ret += addr 220 if sym: 221 ret += ' ' + sym 222 if sec: 223 ret += ' ' + sec 224 ret += line[post:] 225 return ret 226 227 def reset(self): 228 self._call_stack_found = False 229 self._load_addr = '0' 230 if self._addr2line: 231 self._addr2line.terminate() 232 self._addr2line = None 233 self._arch = 'arm' 234 self._saved_abort_line = '' 235 236 def write(self, line): 237 if self._call_stack_found: 238 match = re.search(STACK_ADDR_RE, line) 239 if match: 240 addr = match.group('addr') 241 pre = match.start('addr') 242 post = match.end('addr') 243 self._out.write(line[:pre]) 244 self._out.write(addr) 245 res = self.resolve(addr) 246 if self._strip_path: 247 res = re.sub(re.escape(self._strip_path) + '/*', '', 248 res) 249 self._out.write(' ' + res) 250 self._out.write(line[post:]) 251 return 252 else: 253 self.reset() 254 match = re.search(CALL_STACK_RE, line) 255 if match: 256 self._call_stack_found = True 257 # Here is a good place to resolve the abort address because we 258 # have all the information we need 259 if self._saved_abort_line: 260 self._out.write(self.process_abort(self._saved_abort_line)) 261 match = re.search(TA_UUID_RE, line) 262 if match: 263 self._bin = match.group('uuid') 264 match = re.search(TA_INFO_RE, line) 265 if match: 266 self._arch = match.group('arch') 267 self._load_addr = match.group('load_addr') 268 match = re.search(X64_REGS_RE, line) 269 if match: 270 # Assume _arch represents the TEE core. If we have a TA dump, 271 # it will be overwritten later 272 self._arch = 'aarch64' 273 match = re.search(ABORT_ADDR_RE, line) 274 if match: 275 # At this point the arch and TA load address are unknown. 276 # Save the line so We can translate the abort address later. 277 self._saved_abort_line = line 278 self._out.write(line) 279 280 def flush(self): 281 self._out.flush() 282 283def main(): 284 args = get_args() 285 if args.dir: 286 # Flatten list in case -d is used several times *and* with multiple 287 # arguments 288 args.dirs = [item for sublist in args.dir for item in sublist] 289 else: 290 args.dirs = [] 291 symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path) 292 293 for line in sys.stdin: 294 symbolizer.write(line) 295 symbolizer.flush() 296 297if __name__ == "__main__": 298 main() 299