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