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