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 or panic message from stdin and adds debug 29information to the output, such as '<function> at <file>:<line>' next to each 30address in the call stack. Any message generated by OP-TEE and containing a 31call stack can in principle be processed by this script. This currently 32includes aborts and panics from the TEE core as well as from any TA. 33The paths provided on the command line are used to locate the appropriate ELF 34binary (tee.elf or Trusted Application). The GNU binutils (addr2line, objdump, 35nm) are used to extract the debug info. 36 37OP-TEE abort and panic messages are sent to the secure console. They look like 38the following: 39 40 E/TC:0 User TA data-abort at address 0xffffdecd (alignment fault) 41 ... 42 E/TC:0 Call stack: 43 E/TC:0 0x4000549e 44 E/TC:0 0x40001f4b 45 E/TC:0 0x4000273f 46 E/TC:0 0x40005da7 47 48Inspired by a script of the same name by the Chromium project. 49 50Sample usage: 51 52 $ scripts/symbolize.py -d out/arm-plat-hikey/core -d ../optee_test/out/ta/* 53 <paste whole dump here> 54 ^D 55''' 56 57def get_args(): 58 parser = argparse.ArgumentParser( 59 formatter_class=argparse.RawDescriptionHelpFormatter, 60 description='Symbolizes OP-TEE abort dumps', 61 epilog=epilog) 62 parser.add_argument('-d', '--dir', action='append', nargs='+', 63 help='Search for ELF file in DIR. tee.elf is needed to decode ' 64 'a TEE Core or pseudo-TA abort, while <TA_uuid>.elf is required ' 65 'if a user-mode TA has crashed. For convenience, ELF files ' 66 'may also be given.') 67 parser.add_argument('-s', '--strip_path', nargs='?', 68 help='Strip STRIP_PATH from file paths (default: current directory, ' 69 'use -s with no argument to show full paths)', 70 default=os.getcwd()) 71 72 return parser.parse_args() 73 74class Symbolizer(object): 75 def __init__(self, out, dirs, strip_path): 76 self._out = out 77 self._dirs = dirs 78 self._strip_path = strip_path 79 self._addr2line = None 80 self._bin = 'tee.elf' 81 self.reset() 82 83 def get_elf(self, elf_or_uuid): 84 if not elf_or_uuid.endswith('.elf'): 85 elf_or_uuid += '.elf' 86 for d in self._dirs: 87 if d.endswith(elf_or_uuid) and os.path.isfile(d): 88 return d 89 elf = glob.glob(d + '/' + elf_or_uuid) 90 if elf: 91 return elf[0] 92 93 def set_arch(self): 94 if self._arch: 95 return 96 if self._bin: 97 p = subprocess.Popen([ 'file', self.get_elf(self._bin) ], 98 stdout=subprocess.PIPE) 99 output = p.stdout.readlines() 100 p.terminate() 101 if 'ARM aarch64,' in output[0]: 102 self._arch = 'aarch64-linux-gnu-' 103 elif 'ARM,' in output[0]: 104 self._arch = 'arm-linux-gnueabihf-' 105 106 def arch_prefix(self, cmd): 107 self.set_arch() 108 return self._arch + cmd 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, 16) 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, 16) 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 # Return all ELF sections with the ALLOC flag 224 def read_sections(self): 225 if self._sections: 226 return 227 elf = self.get_elf(self._bin) 228 cmd = self.arch_prefix('objdump') 229 if not elf or not cmd: 230 return 231 objdump = subprocess.Popen([cmd, '--section-headers', elf], 232 stdin = subprocess.PIPE, 233 stdout = subprocess.PIPE) 234 for line in iter(objdump.stdout.readline, ''): 235 try: 236 _, name, size, vma, _, _, _ = line.split() 237 except: 238 if 'ALLOC' in line: 239 self._sections.append([name, int(vma, 16), int(size, 16)]) 240 241 def overlaps(self, section, addr, size): 242 sec_addr = section[1] 243 sec_size = section[2] 244 if not size or not sec_size: 245 return False 246 return (addr <= (sec_addr + sec_size - 1)) and ((addr + size - 1) >= sec_addr) 247 248 def sections_in_region(self, addr, size): 249 ret = '' 250 addr = self.subtract_load_addr(addr) 251 if not addr: 252 return '' 253 iaddr = int(addr, 16) 254 isize = int(size, 16) 255 self.read_sections() 256 for s in self._sections: 257 if self.overlaps(s, iaddr, isize): 258 ret += ' ' + s[0] 259 return ret 260 261 def reset(self): 262 self._call_stack_found = False 263 self._load_addr = '0' 264 if self._addr2line: 265 self._addr2line.terminate() 266 self._addr2line = None 267 self._arch = None 268 self._saved_abort_line = '' 269 self._sections = [] 270 self._bin = "tee.elf" 271 272 def write(self, line): 273 if self._call_stack_found: 274 match = re.search(STACK_ADDR_RE, line) 275 if match: 276 addr = match.group('addr') 277 pre = match.start('addr') 278 post = match.end('addr') 279 self._out.write(line[:pre]) 280 self._out.write(addr) 281 res = self.resolve(addr) 282 if self._strip_path: 283 res = re.sub(re.escape(self._strip_path) + '/*', '', 284 res) 285 self._out.write(' ' + res) 286 self._out.write(line[post:]) 287 return 288 else: 289 self.reset() 290 match = re.search(REGION_RE, line) 291 if match: 292 addr = match.group('addr') 293 size = match.group('size') 294 self._out.write(line.strip() + 295 self.sections_in_region(addr, size) + '\n'); 296 return 297 match = re.search(CALL_STACK_RE, line) 298 if match: 299 self._call_stack_found = True 300 # Here is a good place to resolve the abort address because we 301 # have all the information we need 302 if self._saved_abort_line: 303 self._out.write(self.process_abort(self._saved_abort_line)) 304 match = re.search(TA_UUID_RE, line) 305 if match: 306 self._bin = match.group('uuid') 307 match = re.search(TA_INFO_RE, line) 308 if match: 309 self._load_addr = match.group('load_addr') 310 match = re.search(ABORT_ADDR_RE, line) 311 if match: 312 self.reset() 313 # At this point the arch and TA load address are unknown. 314 # Save the line so We can translate the abort address later. 315 self._saved_abort_line = line 316 self._out.write(line) 317 318 def flush(self): 319 self._out.flush() 320 321def main(): 322 args = get_args() 323 if args.dir: 324 # Flatten list in case -d is used several times *and* with multiple 325 # arguments 326 args.dirs = [item for sublist in args.dir for item in sublist] 327 else: 328 args.dirs = [] 329 symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path) 330 331 for line in sys.stdin: 332 symbolizer.write(line) 333 symbolizer.flush() 334 335if __name__ == "__main__": 336 main() 337