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]+)') 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. For convenience, ELF files ' 80 'may also be given.') 81 parser.add_argument('-s', '--strip_path', 82 help='Strip STRIP_PATH from file paths') 83 84 return parser.parse_args() 85 86class Symbolizer(object): 87 def __init__(self, out, dirs, strip_path): 88 self._out = out 89 self._dirs = dirs 90 self._strip_path = strip_path 91 self._addr2line = None 92 self._bin = 'tee.elf' 93 self.reset() 94 95 def get_elf(self, elf_or_uuid): 96 if not elf_or_uuid.endswith('.elf'): 97 elf_or_uuid += '.elf' 98 for d in self._dirs: 99 if d.endswith(elf_or_uuid) and os.path.isfile(d): 100 return d 101 elf = glob.glob(d + '/' + elf_or_uuid) 102 if elf: 103 return elf[0] 104 105 def set_arch(self): 106 if self._arch: 107 return 108 if self._bin: 109 p = subprocess.Popen([ 'file', self.get_elf(self._bin) ], 110 stdout=subprocess.PIPE) 111 output = p.stdout.readlines() 112 p.terminate() 113 if 'ARM aarch64,' in output[0]: 114 self._arch = 'aarch64-linux-gnu-' 115 elif 'ARM,' in output[0]: 116 self._arch = 'arm-linux-gnueabihf-' 117 118 def arch_prefix(self, cmd): 119 self.set_arch() 120 return self._arch + cmd 121 122 def spawn_addr2line(self): 123 if not self._addr2line: 124 elf = self.get_elf(self._bin) 125 if not elf: 126 return 127 cmd = self.arch_prefix('addr2line') 128 if not cmd: 129 return 130 self._addr2line = subprocess.Popen([cmd, '-f', '-p', '-e', elf], 131 stdin = subprocess.PIPE, 132 stdout = subprocess.PIPE) 133 134 def subtract_load_addr(self, addr): 135 offs = self._load_addr 136 if int(offs, 16) > int(addr, 16): 137 return '' 138 return '0x{:x}'.format(int(addr, 16) - int(offs, 16)) 139 140 def resolve(self, addr): 141 reladdr = self.subtract_load_addr(addr) 142 self.spawn_addr2line() 143 if not reladdr or not self._addr2line: 144 return '???' 145 try: 146 print >> self._addr2line.stdin, reladdr 147 ret = self._addr2line.stdout.readline().rstrip('\n') 148 except IOError: 149 ret = '!!!' 150 return ret 151 152 def symbol_plus_offset(self, addr): 153 ret = '' 154 prevsize = 0 155 reladdr = self.subtract_load_addr(addr) 156 elf = self.get_elf(self._bin) 157 cmd = self.arch_prefix('nm') 158 if not reladdr or not elf or not cmd: 159 return '' 160 ireladdr = int(reladdr, 0) 161 nm = subprocess.Popen([cmd, '--numeric-sort', '--print-size', elf], 162 stdin = subprocess.PIPE, 163 stdout = subprocess.PIPE) 164 for line in iter(nm.stdout.readline, ''): 165 try: 166 addr, size, _, name = line.split() 167 except: 168 # Size is missing 169 addr, _, name = line.split() 170 size = '0' 171 iaddr = int(addr, 16) 172 isize = int(size, 16) 173 if iaddr == ireladdr: 174 ret = name 175 break 176 if iaddr < ireladdr and iaddr + isize >= ireladdr: 177 offs = ireladdr - iaddr 178 ret = name + '+' + str(offs) 179 break 180 if iaddr > ireladdr and prevsize == 0: 181 offs = iaddr + ireladdr 182 ret = prevname + '+' + str(offs) 183 break 184 prevsize = size 185 prevname = name 186 nm.terminate() 187 return ret 188 189 def section_plus_offset(self, addr): 190 ret = '' 191 reladdr = self.subtract_load_addr(addr) 192 elf = self.get_elf(self._bin) 193 cmd = self.arch_prefix('objdump') 194 if not reladdr or not elf or not cmd: 195 return '' 196 iaddr = int(reladdr, 0) 197 objdump = subprocess.Popen([cmd, '--section-headers', elf], 198 stdin = subprocess.PIPE, 199 stdout = subprocess.PIPE) 200 for line in iter(objdump.stdout.readline, ''): 201 try: 202 idx, name, size, vma, lma, offs, algn = line.split() 203 except: 204 continue; 205 ivma = int(vma, 16) 206 isize = int(size, 16) 207 if ivma == iaddr: 208 ret = name 209 break 210 if ivma < iaddr and ivma + isize >= iaddr: 211 offs = iaddr - ivma 212 ret = name + '+' + str(offs) 213 break 214 objdump.terminate() 215 return ret 216 217 def process_abort(self, line): 218 ret = '' 219 match = re.search(ABORT_ADDR_RE, line) 220 addr = match.group('addr') 221 pre = match.start('addr') 222 post = match.end('addr') 223 sym = self.symbol_plus_offset(addr) 224 sec = self.section_plus_offset(addr) 225 if sym or sec: 226 ret += line[:pre] 227 ret += addr 228 if sym: 229 ret += ' ' + sym 230 if sec: 231 ret += ' ' + sec 232 ret += line[post:] 233 return ret 234 235 def reset(self): 236 self._call_stack_found = False 237 self._load_addr = '0' 238 if self._addr2line: 239 self._addr2line.terminate() 240 self._addr2line = None 241 self._arch = None 242 self._saved_abort_line = '' 243 244 def write(self, line): 245 if self._call_stack_found: 246 match = re.search(STACK_ADDR_RE, line) 247 if match: 248 addr = match.group('addr') 249 pre = match.start('addr') 250 post = match.end('addr') 251 self._out.write(line[:pre]) 252 self._out.write(addr) 253 res = self.resolve(addr) 254 if self._strip_path: 255 res = re.sub(re.escape(self._strip_path) + '/*', '', 256 res) 257 self._out.write(' ' + res) 258 self._out.write(line[post:]) 259 return 260 else: 261 self.reset() 262 match = re.search(CALL_STACK_RE, line) 263 if match: 264 self._call_stack_found = True 265 # Here is a good place to resolve the abort address because we 266 # have all the information we need 267 if self._saved_abort_line: 268 self._out.write(self.process_abort(self._saved_abort_line)) 269 match = re.search(TA_UUID_RE, line) 270 if match: 271 self._bin = match.group('uuid') 272 match = re.search(TA_INFO_RE, line) 273 if match: 274 self._load_addr = match.group('load_addr') 275 match = re.search(ABORT_ADDR_RE, line) 276 if match: 277 # At this point the arch and TA load address are unknown. 278 # Save the line so We can translate the abort address later. 279 self._saved_abort_line = line 280 self._out.write(line) 281 282 def flush(self): 283 self._out.flush() 284 285def main(): 286 args = get_args() 287 if args.dir: 288 # Flatten list in case -d is used several times *and* with multiple 289 # arguments 290 args.dirs = [item for sublist in args.dir for item in sublist] 291 else: 292 args.dirs = [] 293 symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path) 294 295 for line in sys.stdin: 296 symbolizer.write(line) 297 symbolizer.flush() 298 299if __name__ == "__main__": 300 main() 301