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