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 re 33import subprocess 34import sys 35 36TA_UUID_RE = re.compile(r'Status of TA (?P<uuid>[0-9a-f\-]+)') 37TA_INFO_RE = re.compile(': arch: (?P<arch>\w+) ' 38 'load address: (?P<load_addr>0x[0-9a-f]+)') 39CALL_STACK_RE = re.compile('Call stack:') 40STACK_ADDR_RE = re.compile(r': (?P<addr>0x[0-9a-f]+)') 41X64_REGS_RE = re.compile(': x0 [0-9a-f]{16} x1 [0-9a-f]{16}') 42 43epilog = ''' 44This scripts reads an OP-TEE abort message from stdin and adds debug 45information ('function at file:line') next to each address in the call stack. 46It uses the paths provided on the command line to locate the appropriate ELF 47binary (tee.elf or Trusted Application) and runs arm-linux-gnueabihf-addr2line 48or aarch64-linux-gnu-addr2line to process the addresses. 49 50OP-TEE abort messages are sent to the secure console. They look like the 51following: 52 53 ERROR: TEE-CORE: User TA data-abort at address 0xffffdecd (alignment fault) 54 ... 55 ERROR: TEE-CORE: Call stack: 56 ERROR: TEE-CORE: 0x4000549e 57 ERROR: TEE-CORE: 0x40001f4b 58 ERROR: TEE-CORE: 0x4000273f 59 ERROR: TEE-CORE: 0x40005da7 60 61Inspired by a script of the same name by the Chromium project. 62 63Sample usage: 64 65 $ scripts/symbolize.py -d out/arm-plat-hikey/core -d ../optee_test/out/ta/* 66 <paste whole dump here> 67 ^D 68''' 69 70def get_args(): 71 parser = argparse.ArgumentParser( 72 formatter_class=argparse.RawDescriptionHelpFormatter, 73 description='Symbolizes OP-TEE abort dumps', 74 epilog=epilog) 75 parser.add_argument('-d', '--dir', action='append', nargs='+', 76 help='Search for ELF file in DIR. tee.elf is needed to decode ' 77 'a TEE Core or pseudo-TA abort, while <TA_uuid>.elf is required ' 78 'if a user-mode TA has crashed.') 79 parser.add_argument('-s', '--strip_path', 80 help='Strip STRIP_PATH from file paths') 81 82 return parser.parse_args() 83 84class Symbolizer(object): 85 def __init__(self, out, dirs, strip_path): 86 self._out = out 87 self._dirs = dirs 88 self._strip_path = strip_path 89 self._addr2line = None 90 self._bin = 'tee.elf' 91 self.reset() 92 93 def get_elf(self, elf_or_uuid): 94 if not elf_or_uuid.endswith('.elf'): 95 elf_or_uuid += '.elf' 96 for d in self._dirs: 97 elf = glob.glob(d + '/' + elf_or_uuid) 98 if elf: 99 return elf[0] 100 101 def spawn_addr2line(self): 102 if not self._addr2line: 103 elf = self.get_elf(self._bin) 104 if not elf: 105 return 106 if self._arch == 'arm': 107 cmd = 'arm-linux-gnueabihf-addr2line' 108 elif self._arch == 'aarch64': 109 cmd = 'aarch64-linux-gnu-addr2line' 110 else: 111 return 112 self._addr2line = subprocess.Popen([cmd, '-f', '-p', '-e', elf], 113 stdin = subprocess.PIPE, 114 stdout = subprocess.PIPE) 115 116 def resolve(self, addr): 117 offs = self._load_addr 118 if int(offs, 0) > int(addr, 0): 119 return '???' 120 reladdr = '0x{:x}'.format(int(addr, 0) - int(offs, 0)) 121 self.spawn_addr2line() 122 if not self._addr2line: 123 return '???' 124 try: 125 print >> self._addr2line.stdin, reladdr 126 ret = self._addr2line.stdout.readline().rstrip('\n') 127 except IOError: 128 ret = '!!!' 129 return ret 130 131 def reset(self): 132 self._call_stack_found = False 133 self._load_addr = '0' 134 if self._addr2line: 135 self._addr2line.terminate() 136 self._addr2line = None 137 self._arch = 'arm' 138 139 def write(self, line): 140 if self._call_stack_found: 141 match = re.search(STACK_ADDR_RE, line) 142 if match: 143 addr = match.group('addr') 144 pre = match.start('addr') 145 post = match.end('addr') 146 self._out.write(line[:pre]) 147 self._out.write(addr) 148 res = self.resolve(addr) 149 if self._strip_path: 150 res = re.sub(re.escape(self._strip_path) + '/*', '', 151 res) 152 self._out.write(' ' + res) 153 self._out.write(line[post:]) 154 return 155 else: 156 self.reset() 157 match = re.search(CALL_STACK_RE, line) 158 if match: 159 self._call_stack_found = True 160 match = re.search(TA_UUID_RE, line) 161 if match: 162 self._bin = match.group('uuid') 163 match = re.search(TA_INFO_RE, line) 164 if match: 165 self._arch = match.group('arch') 166 self._load_addr = match.group('load_addr') 167 match = re.search(X64_REGS_RE, line) 168 if match: 169 # Assume _arch represents the TEE core. If we have a TA dump, 170 # it will be overwritten later 171 self._arch = 'aarch64' 172 self._out.write(line) 173 174 def flush(self): 175 self._out.flush() 176 177def main(): 178 args = get_args() 179 if args.dir: 180 # Flatten list in case -d is used several times *and* with multiple 181 # arguments 182 args.dirs = [item for sublist in args.dir for item in sublist] 183 else: 184 args.dirs = [] 185 symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path) 186 187 for line in sys.stdin: 188 symbolizer.write(line) 189 symbolizer.flush() 190 191if __name__ == "__main__": 192 main() 193