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