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