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