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