1#!/usr/bin/env python3 2 3# SPDX-License-Identifier: BSD-3-Clause 4# 5# Copyright (c) 2019, Intel Corporation. All rights reserved. 6# 7# Author: Michal Jerzy Wierzbicki <michalx.wierzbicki@linux.intel.com> 8# Adrian Bonislawski <adrian.bonislawski@linux.intel.com> 9 10# Tool for processing FW stack dumps. 11# For more detailed useage, use --help option. 12 13from __future__ import print_function 14import argparse 15import struct 16import os 17import sys 18import itertools 19import re 20import shutil 21import time 22from ctypes import LittleEndianStructure, BigEndianStructure, c_uint32, c_char 23from collections import namedtuple 24from operator import attrgetter 25from functools import partial 26 27def stderr_print(*args, **kwargs): 28 print(*args, file=sys.stderr, **kwargs) 29 30def stdout_print(*args, **kwargs): 31 print(*args, file=sys.stdout, **kwargs) 32 33try: 34 from sty import fg, bg, ef, rs, Rule, Render 35 CAN_COLOUR=True 36except: 37 CAN_COLOUR=False 38 39ArchDefinition = namedtuple('ArchDefinition', ['name', 'bitness', 'endianness']) 40VALID_ARCHS = {} 41[VALID_ARCHS.update({archdef.name : archdef}) 42 for archdef in [ArchDefinition(*tup) for tup in 43 [ 44 ( 'LE32bit', 45 32, 46 LittleEndianStructure, ), 47 ( 'LE64bit', 48 64, 49 LittleEndianStructure, ), 50# ( 'BE32bit', #untested, treat as not implemented 51# 32, 52# BigEndianStructure, ), 53# ( 'BE64bit', #untested, treat as not implemented 54# 64, 55# BigEndianStructure, ), 56 ]] 57] 58 59# Exception casues: 60# CODE: [Exception cause, excvaddr loaded] 61EXCCAUSE_CODE = { 62 0: ["IllegalInstructionCause: Illegal instruction", False], 63 1: ["SyscallCause: SYSCALL instruction", True], 64 2: ["InstructionFetchErrorCause: Processor internal physical address or data error during instruction fetch", True], 65 3: ["LoadStoreErrorCause: Processor internal physical address or data error during load or store", True], 66 4: ["Level1InterruptCause: Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register", False], 67 5: ["AllocaCause: MOVSP instruction, if caller's registers are not in the register file", False], 68 6: ["IntegerDivideByZeroCause: QUOS, QUOU, REMS, or REMU divisor operand is zero", False], 69 8: ["PrivilegedCause: Attempt to execute a privileged operation when CRING ? 0", False], 70 9: ["LoadStoreAlignmentCause: Load or store to an unaligned address", True], 71 12: ["InstrPIFDataErrorCause: PIF data error during instruction fetch", True], 72 13: ["LoadStorePIFDataErrorCause: ynchronous PIF data error during LoadStore access", True], 73 14: ["InstrPIFAddrErrorCause: PIF address error during instruction fetch", True], 74 15: ["LoadStorePIFAddrErrorCause: Synchronous PIF address error during LoadStore access", True], 75 16: ["InstTLBMissCause: Error during Instruction TLB refill", True], 76 17: ["InstTLBMultiHitCause: Multiple instruction TLB entries matched", True], 77 18: ["InstFetchPrivilegeCause: An instruction fetch referenced a virtual address at a ring level less than CRING", True], 78 20: ["InstFetchProhibitedCause: An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch", True], 79 24: ["LoadStoreTLBMissCause: Error during TLB refill for a load or store", True], 80 25: ["LoadStoreTLBMultiHitCause: Multiple TLB entries matched for a load or store", True], 81 26: ["LoadStorePrivilegeCause: A load or store referenced a virtual address at a ring level less than CRING", True], 82 28: ["LoadProhibitedCause: A load referenced a page mapped with an attribute that does not permit loads", True], 83 29: ["StoreProhibitedCause: A store referenced a page mapped with an attribute that does not permit stores", True], 84 32: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False], 85 33: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False], 86 34: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False], 87 35: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False], 88 36: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False], 89 37: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False], 90 38: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False], 91 39: ["CoprocessornDisabled: Coprocessor n instruction when cpn disabled. n varies 0..7 as the cause varies 32..39", False] 92} 93 94def valid_archs_print(): 95 archs = ''.join("{0}, ".format(x) for x in VALID_ARCHS) 96 archs = archs[:len(archs)-2] 97 return "{0}.".format(archs) 98 99TERM_SIZE = shutil.get_terminal_size((120, 20)) 100AR_WINDOW_WIDTH = 4 101IS_COLOUR=False 102 103class argparse_readable_file( argparse.Action): 104 def raise_error(self, filepath, reason): 105 raise argparse.ArgumentTypeError( 106 "is_readable_file:{0} {1}".format( 107 filepath, 108 reason 109 )) 110 def is_readable_file(self, filepath): 111 if not os.path.isfile(filepath): 112 self.raise_error(filepath, "is not a valid path") 113 if os.access(filepath, os.R_OK): 114 return True 115 else: 116 self.raise_error(filepath, "is not a readable file") 117 return False 118 def __call__(self, parser, namespace, values, option_string=None): 119 filepath = values[0] 120 if (self.is_readable_file(filepath)): 121 setattr(namespace, self.dest, filepath) 122 123class argparse_writeable_file(argparse.Action): 124 def raise_error(self, filepath, reason): 125 raise argparse.ArgumentTypeError( 126 "is_writeable_file:{0} {1}".format( 127 filepath, 128 reason 129 )) 130 def is_writeable_file(self, filepath): 131 absdir = os.path.abspath(os.path.dirname(filepath)) 132 if os.path.isdir(absdir) and not os.path.exists(filepath): 133 return True 134 else: 135 if not os.path.isfile(filepath): 136 self.raise_error(filepath, "is not a valid path") 137 if os.access(filepath, os.W_OK): 138 return True 139 else: 140 self.raise_error(filepath, "is not a writeable file") 141 return False 142 def __call__(self, parser, namespace, values, option_string=None): 143 filepath = values[0] 144 if (self.is_writeable_file(filepath)): 145 setattr(namespace, self.dest, filepath) 146 else: 147 self.raise_error( 148 filepath, 149 "failed to determine whether file is writeable" 150 ) 151 152class argparse_architecture( argparse.Action): 153 def raise_error(self, value, reason): 154 raise argparse.ArgumentTypeError( 155 "architecture: {0} {1}".format( 156 value, 157 reason 158 )) 159 def is_valid_architecture(self, value): 160 if value in VALID_ARCHS: 161 return True 162 return False 163 def __call__(self, parser, namespace, values, option_string=None): 164 value = values[0] 165 if (self.is_valid_architecture(value)): 166 setattr(namespace, self.dest, VALID_ARCHS[value]) 167 else: 168 self.raise_error( 169 value, 170 "is invalid architecture. Valid architectures are: {0}".format(valid_archs_print()) 171 ) 172 173def parse_params(): 174 parser = argparse.ArgumentParser( 175 description="Tool for processing FW stack dumps." 176 +" In verbose mode it prints DSP registers, and function call" 177 +" addresses in stack up to that which caused core dump." 178 +" It then prints either to file or to stdin all gdb" 179 +" commands unwrapping those function call addresses to" 180 +" function calls in human readable format." 181 ) 182 ArgTuple = namedtuple('ArgTuple', ['name', 'optionals', 'parent']) 183 ArgTuple.__new__.__defaults__ = ((), {}, parser) 184 185 # below cannot be empty once declared 186 outputMethod = parser.add_mutually_exclusive_group(required=True) 187 inputMethod = parser.add_mutually_exclusive_group() 188 [parent.add_argument(*name, **optionals) 189 for name, optionals, parent in sorted([ArgTuple(*x) for x in 190 [ 191 ( ( '-a', '--arch' , ), { 192 'type' : str, 193 'help' :'determine architecture of dump file; valid archs are: {0}' 194 .format(valid_archs_print()), 195 'action' : argparse_architecture, 196 'nargs' : 1, 197 'default': VALID_ARCHS['LE64bit'], 198 },), 199 ( ( '-v', '--verbose' , ), { 200 'help' :'increase output verbosity', 201 'action':'store_true', 202 },), 203 ( ( '--stdin' , ), { 204 'help' :'input is from stdin', 205 'action' : 'store_true', 206 }, 207 inputMethod), 208 ( ( '-o', '--outfile' , ), { 209 'type' : str, 210 'help' :'output is to FILE', 211 'action' : argparse_writeable_file, 212 'nargs' : 1, 213 }, 214 outputMethod), 215 ( ( '--stdout' , ), { 216 'help' :'output is to stdout', 217 'action' :'store_true', 218 }, 219 outputMethod), 220 ( ( '-c', '--colour', ), { 221 'help' :'set output to be colourful!', 222 'action':'store_true', 223 },), 224 ( ( '-l', '--columncount', ), { 225 'type' : int, 226 'help' :'set how many colums to group the output in, ignored without -v', 227 'action':'store', 228 'nargs' : 1, 229 },), 230 ( ( '-i', '--infile' , ), { 231 'type' : str, 232 'help' :'path to sys dump bin', 233 'action' : argparse_readable_file, 234 'nargs' : 1, 235 }, 236 inputMethod), 237 ]], 238 key=lambda argtup: (argtup.parent.__hash__(), argtup.name) 239 ) 240 ] 241 242 parsed = parser.parse_args() 243 244 if parsed.columncount and not parsed.verbose: 245 stderr_print("INFO: -l option will be ignored without -v") 246 247 return parsed 248 249def chunks(l, n): 250 return [l[i:i + n] for i in range(0, len(l), n)] 251 252def flaten(l): 253 return [item for sublist in l for item in sublist] 254 255def raiseIfArchNotValid(arch): 256 if arch not in VALID_ARCHS.values(): 257 raise ValueError( 258 "CoreDumpFactory: {0} not in valid architectures: {1}" 259 .format(arch, valid_archs_print()) 260 ) 261 endiannesses = [arch.endianness for arch in VALID_ARCHS.values()] 262 if arch.endianness not in endiannesses: 263 raise ValueError( 264 "CoreDumpFactory: {0} not in valid endiannesses: {1}" 265 .format(endianness, endiannesses) 266 ) 267 268def FileInfoFactory(arch, filename_length): 269 raiseIfArchNotValid(arch) 270 class FileInfo(arch.endianness): 271 _fields_ = [ 272 ("hdr", c_uint32), 273 ("code", c_uint32), 274 ("filename", filename_length * c_char), 275 ("line_no", c_uint32) 276 ] 277 def __str__(self): 278 return "{}:{:d}".format(self.filename.decode(), self.line_no) 279 if FileInfo is None: 280 raise RuntimeError( 281 "FileInfoFactory: failed to produce FileInfo({0})" 282 .format(arch.name) 283 ) 284 return FileInfo 285 286class Colourer(): 287 #TODO: Add detection of 8bit/24bit terminal 288 # Add 8bit/24bit colours (with flag, maybe --colour=24bit) 289 # Use this below as fallback only 290 __print = partial(stdout_print) 291 if CAN_COLOUR == True: 292 __style = { 293 'o' : fg.red, 294 'O' : fg.yellow, 295 'y' : fg.blue, 296 'Y' : fg.cyan, 297 'D' : fg.white + bg.red, 298 } 299 matchings = [] 300 matchingsInParenth = [] 301 matchingsInStderr = [] 302 def __init__(self): 303 if CAN_COLOUR == True: 304 self.matchings = [ 305 ( 306 lambda x: self.enstyleNumHex(x.group()), 307 re.compile(r'\b([A-Fa-f0-9]{8})\b') 308 ), 309 ( 310 lambda x: '0x' + self.enstyleNumHex(x.group(2)), 311 re.compile(r'(0x)([A-Fa-f0-9]{1,})') 312 ), 313 ( 314 lambda x: self.enstyleNumBin(x.group()), 315 re.compile(r'\b(b[01]+)\b') 316 ), 317 ( 318 r'\1' + 319 r'\2' + 320 self.enstyle( fg.green , r'\3') , 321 re.compile(r'(\#)(ar)([0-9]+)\b') 322 ), 323 ( 324 self.enstyle(bg.green , r'\1') + 325 rs.all + '\n', 326 re.compile(r'(\(xt-gdb\)\ *)') 327 ), 328 ( 329 r'\1' + 330 r'\2' + 331 self.enstyle( fg.green , r'\3') + 332 r':' + 333 self.enstyle( fg.magenta , r'\4') , 334 re.compile(r'(\bat\b\ *)(.+/)?(.*):([0-9]+)') 335 ), 336 ( 337 lambda x:\ 338 'in '+ 339 self.enstyle(fg.green, x.group(2))+ 340 self.enstyleFuncParenth(x.group(3)), 341 re.compile(r'(\bin\b\ *)([^\ ]+)\ *(\(.*\))') 342 ), 343 ] 344 self.matchingsInParenth = [ 345 ( 346 self.enstyle( fg.yellow , r'\1') + 347 self.enstyle( fg.magenta , r'=' ) + 348 r'\2', 349 re.compile(r'([\-_a-zA-Z0-9]+)\ *=\ *([^,]+)') 350 ), 351 ( 352 self.enstyle( fg.magenta , r'\1') , 353 re.compile(r'(\ *[\(\)]\ *)') 354 ), 355 ( 356 self.enstyle( fg.magenta , r', ') , 357 re.compile(r'(\ *,\ *)') 358 ), 359 ] 360 self.matchingsInStderr = [ 361 ( 362 self.enstyle(bg.yellow + fg.black , r'\1') , 363 re.compile(r'([Ww]arning)') 364 ), 365 ( 366 self.enstyle(bg.red + fg.black , r'\1') , 367 re.compile(r'([Ee]rror)') 368 ), 369 ( 370 self.enstyle(bg.magenta + fg.black , r'\1') , 371 re.compile(r'([Ff]atal)') 372 ), 373 ] 374 375 def toGroup(self, txt): 376 return [ (label, sum(1 for _ in group)) 377 for label, group in itertools.groupby(txt) ] 378 379 def leadingZero(self, txt, char): 380 result = "" 381 groups = self.toGroup(txt) 382 lead = 0 383 if groups[0][0] == '0': 384 lead = min(4, groups[0][1]) 385 result += char.lower() * lead 386 result += char.upper() * (4-lead) 387 return result 388 389 def findSub(self, txt, mask, sub, char): 390 pos = txt.find(sub) 391 if pos >= 0: 392 return mask[:pos] + char * len(sub) + mask[(len(sub)+pos):] 393 else: 394 return mask 395 396 def enstyleFuncParenth(self, txt): 397 result = txt 398 for repl, regex in self.matchingsInParenth: 399 result = re.sub(regex, repl, result) 400 return result 401 402 def enstyleNumBin(self, txt): 403 result = rs.all + bg.magenta + "b" 404 prev = "" 405 for c in txt[1:]: 406 if prev != c: 407 prev = c 408 result += rs.all 409 if c == "0": 410 result += fg.red 411 result += c 412 result += rs.all 413 return result 414 415 def enstyleNumHex(self, txt): 416 if len(txt) < 8: 417 txt = (8-len(txt))*'0' + txt 418 p1 = 'o' 419 p2 = 'y' 420 if txt == "00000000": 421 styleMask = p1 * 8 422 elif txt.lower() == "deadbeef": 423 styleMask = "DDDDDDDD" 424 else: 425 styleMask = "".join( 426 [self.leadingZero(string, style) 427 for string, style in [ 428 (txt[:4], p1), 429 (txt[4:], p2), 430 ]]) 431 styleMask = "".join( 432 [self.findSub(txt, styleMask, string, style) 433 for string, style in [ 434 ('dead', 'D'), 435 ]]) 436 437 result = "" 438 thisstyle = '' 439 for iter, style in enumerate(styleMask): 440 if thisstyle != style: 441 thisstyle = style 442 result += rs.all + self.__style[thisstyle] 443 result += txt[iter] 444 result += rs.all 445 return result 446 447 def enstyleStderr(self, txt): 448 if txt is None: 449 return '' 450 result = txt 451 for repl, regex in self.matchingsInStderr: 452 result = re.sub(regex, repl, result) 453 for repl, regex in self.matchings: 454 result = re.sub(regex, repl, result) 455 return fg.red + result + rs.all 456 457 def enstyle(self, style, txt): 458 return style + txt + rs.all 459 460 def produce_string(self, txt): 461 result = txt 462 for repl, regex in self.matchings: 463 result = re.sub(regex, repl, result) 464 return result 465 466 def print(self, txt): 467 self.__print(self.produce_string(txt)) 468 469def CoreDumpFactory(dsp_arch): 470 raiseIfArchNotValid(dsp_arch) 471 class CoreDump(dsp_arch.endianness): 472 _fields_ = [(x, c_uint32) for x in 473 [ 474 # struct sof_ipc_dsp_oops_arch_hdr { 475 "arch", 476 "totalsize", 477 # } 478 # struct sof_ipc_dsp_oops_plat_hdr { 479 "configidhi", 480 "configidlo", 481 "numaregs", 482 "stackoffset", 483 "stackptr", 484 # } 485 "exccause", 486 "excvaddr", 487 "ps" 488 ] 489 + ["epc" + str(x) for x in range(1,7+1)] 490 + ["eps" + str(x) for x in range(2,7+1)] 491 + [ 492 "depc", 493 "intenable", 494 "interrupt", 495 "sar", 496 "debugcause", 497 "windowbase", 498 "windowstart", 499 "excsave1" # to 500 ] 501 ] + [ 502 ("a", dsp_arch.bitness * c_uint32) 503 ] 504 505 def __init__(self, columncount): 506 self.dsp_arch = dsp_arch 507 self._fields_ 508 self.ar_regex = re.compile(r'ar[0-9]+') 509 # below: smart column count 510 self._longest_field = len(max([x[0] for x in self._fields_], key=len)) 511 if columncount is not None: 512 self.columncount = max (1, int(columncount[0])) 513 else: 514 self.columncount = max(1, 515 int(TERM_SIZE[0]/(self._longest_field + 2 + 2 * AR_WINDOW_WIDTH + 2)) 516 ) 517 self.columncount_ar = ( 518 self.columncount 519 if self.columncount <= AR_WINDOW_WIDTH else 520 AR_WINDOW_WIDTH * int(self.columncount/AR_WINDOW_WIDTH) 521 ) 522 523 def __windowbase_shift(self, iter, direction): 524 return (iter + self.windowbase * AR_WINDOW_WIDTH * direction)\ 525 % self.dsp_arch.bitness 526 def windowbase_shift_left(self, iter): 527 return self.__windowbase_shift(iter, -1) 528 def windowbase_shift_right(self, iter): 529 return self.__windowbase_shift(iter, 1) 530 531 def reg_from_string(self, string): 532 if self.ar_regex.fullmatch(string): 533 return self.a[self.windowbase_shift_left(int(string[2:]))] 534 else: 535 return self.__getattribute__(string) 536 537 def __str__(self): 538 string = "" 539 string += "exccause" 540 return string 541 542 def to_string(self, is_gdb): 543 # in case windowbase in dump has invalid value 544 windowbase_shift = min( 545 self.windowbase * AR_WINDOW_WIDTH, 546 self.dsp_arch.bitness 547 ) 548 # flatten + chunk enable to smartly print in N columns 549 string = ''.join([self.fmt(is_gdb, x) 550 for x in flaten( 551 [chunks(word, self.columncount) for word in [ 552 ["arch", "totalsize", "stackptr", "stackoffset"], 553 ["configidhi", "configidlo", "numaregs"], 554 ]] 555 ) 556 ]) 557 558 string += "\n# CPU registers:\n\n" 559 560 string += ''.join([self.fmt(is_gdb, x) 561 for x in flaten( 562 [chunks(word, self.columncount) for word in [ 563 ["exccause", "excvaddr", "ps"], 564 ["epc" + str(x) for x in range(1,7+1)], 565 ["eps" + str(x) for x in range(2,7+1)], 566 ["depc", "intenable", "interrupt", "sar", "debugcause"], 567 ["windowbase", "windowstart"], 568 ["excsave1"], 569 ]] + 570 [chunks(word, self.columncount_ar) for word in [ 571 ["ar" + str(x) for x in itertools.chain( 572 range( windowbase_shift, self.dsp_arch.bitness), 573 range(0, windowbase_shift), 574 )] 575 ]] 576 ) 577 ]) 578 579 if not is_gdb: 580 string += "\n" 581 return string 582 583 def fmt_gdb_command(self): 584 return "set ${}=0x{:08x}\n" 585 586 def fmt_pretty_form(self, separator = "|"): 587 return separator + "{:" + str(self._longest_field) + "} {:08x} " 588 589 def fmt_separator(self, name): 590 separator = "# " 591 return separator 592 593 def fmt_pretty_auto(self, name): 594 return self.fmt_pretty_form(self.fmt_separator(name)) 595 596 def fmt(self, is_gdb, names): 597 if is_gdb: 598 fmtr = lambda name: self.fmt_gdb_command() 599 else: 600 fmtr = lambda name: self.fmt_pretty_auto(name) 601 602 string = "" 603 for name in names: 604 string += fmtr(name).format( 605 name, self.reg_from_string(name) 606 ) 607 if not is_gdb: 608 string += "\n" 609 return string 610 611 def windowstart_process(self): 612 string = "" 613 binary = "{0:b}".format(self.windowstart) 614 bit_start = len(binary)-1-self.windowbase 615 fnc_num = 0 616 header = "" 617 618 for it, c in enumerate(binary[bit_start:]): 619 if c != "0": 620 header += str(fnc_num) 621 fnc_num += 1 622 else: 623 header += " " 624 for it, c in enumerate(binary[:bit_start]): 625 if c != "0": 626 header += str(fnc_num) 627 fnc_num += 1 628 else: 629 header += " " 630 header = header[self.windowbase+1:]+ header[:self.windowbase+1] 631 632 string += "# windowbase: {:0X}\n".format(self.windowbase) 633 string += "# {0}\n".format(header) 634 string += "# windowstart: b{0}\n\n".format(binary) 635 string += "# reg a0 a1\n" 636 string += "# (return) (sptr)\n" 637 string += "# --- -------- -------\n" 638 fnc_num = 0 639 for iter, digit in enumerate(binary[bit_start:]): 640 if (digit == '1'): 641 reg = "ar{0}".format(AR_WINDOW_WIDTH * (self.windowbase - iter)) 642 reg1 = "ar{0}".format(AR_WINDOW_WIDTH * (self.windowbase - iter) + 1) 643 string += "# {0:2d} ".format(++fnc_num) 644 string += self.fmt_pretty_auto(reg).format( 645 reg, self.reg_from_string(reg) 646 ) + " {:0x} ".format(self.reg_from_string(reg1)) + "\n" 647 fnc_num += 1 648 for iter, digit in enumerate(binary[:bit_start]): 649 if (digit == '1'): 650 reg = "ar{0}".format(AR_WINDOW_WIDTH * (len(binary) - 1 - iter)) 651 reg1 = "ar{0}".format(AR_WINDOW_WIDTH * (len(binary) - 1 - iter) + 1) 652 string += "# {0:2d} ".format(++fnc_num) 653 string += self.fmt_pretty_auto(reg).format( 654 reg, self.reg_from_string(reg) 655 ) + " {:0x} ".format(self.reg_from_string(reg1)) + "\n" 656 fnc_num += 1 657 return string 658 659 if CoreDump is None: 660 raise RuntimeError( 661 "CoreDumpFactory: failed to produce CoreDump({0})" 662 .format(dsp_arch.name) 663 ) 664 return CoreDump 665 666class CoreDumpReader(object): 667 def __init__(self, args): 668 self.core_dump = CoreDumpFactory(args.arch)( 669 args.columncount 670 ) 671 self.file_info = FileInfoFactory(args.arch, 32)() 672 673 if IS_COLOUR: 674 colourer = Colourer() 675 if args.verbose: 676 verbosePrint =\ 677 colourer.print\ 678 if IS_COLOUR else\ 679 stdout_print 680 else: 681 verbosePrint = lambda *discard_this: None 682 683 if args.stdout: 684 stdoutOpen = lambda: None 685 stdoutPrint = print 686 stdoutClose = lambda: None 687 elif args.outfile: 688 #TODO: open file in stdOutOpen 689 stdoutDest = open(args.outfile, "w") 690 stdoutOpen = lambda: None 691 stdoutPrint = stdoutDest.write 692 stdoutClose = stdoutDest.close 693 else: 694 raise RuntimeError("CoreDumpReader: No output method.") 695 696 if args.stdin: 697 inStream = lambda: sys.stdin.buffer 698 elif args.infile: 699 inStream = lambda: open(args.infile, "rb") 700 else: 701 raise RuntimeError("CoreDumpReader: No input method.") 702 703 with inStream() as cd_file: 704 [cd_file.readinto(x) for x in [ 705 self.core_dump, 706 self.file_info 707 ]] 708 self.stack = cd_file.read() 709 710 verbosePrint("# Core header:\n") 711 verbosePrint(self.core_dump.to_string(0)) 712 713 verbosePrint(self.core_dump.windowstart_process()) 714 715 stack_base = self.core_dump.stackptr 716 stack_dw_num = int(len(self.stack)/AR_WINDOW_WIDTH) 717 verbosePrint("# Stack dumped from {:08x} dwords num {:d}" 718 .format(stack_base, stack_dw_num)) 719 720 stdoutOpen() 721 stdoutPrint("break *0xbefe0000\nrun\n") 722 stdoutPrint(self.core_dump.to_string(1)) 723 724 #TODO: make this elegant 725 for dw, addr in [( 726 struct.unpack("I", self.stack[i*AR_WINDOW_WIDTH : (i+1)*AR_WINDOW_WIDTH])[0], 727 stack_base + i*AR_WINDOW_WIDTH 728 ) for i in range(0, stack_dw_num)]: 729 stdoutPrint("set *0x{:08x}=0x{:08x}\n" 730 .format(addr, dw)) 731 732 # Exccause 63 is reserved for panics; the other causes come 733 # from actual exceptions 734 if self.core_dump.exccause != 63: 735 verbosePrint("\n# *EXCEPTION*\n") 736 verbosePrint("# exccause: " + EXCCAUSE_CODE[self.core_dump.exccause][0]) 737 if EXCCAUSE_CODE[self.core_dump.exccause][1]: 738 verbosePrint("# excvaddr: " + str(self.core_dump.excvaddr)) 739 stdoutPrint('p "Exception location:"\nlist *$epc1\n'); 740 else: 741 verbosePrint("# Location: " + str(self.file_info)); 742 743 # TODO: if excsave1 is not empty, pc should be set to that value 744 # (exception mode, not forced panic mode) 745 stdoutPrint('set $pc=&arch_dump_regs_a\np "backtrace"\nbacktrace\n') 746 stdoutClose() 747 748if __name__ == "__main__": 749 args = parse_params() 750 if args.colour: 751 if CAN_COLOUR: 752 IS_COLOUR=True 753 else: 754 stderr_print("INFO: Cannot color the output: module 'sty' not found") 755 CoreDumpReader(args) 756 757