1 #!/usr/bin/python 2 3 #---------------------------------------------------------------------- 4 # Be sure to add the python path that points to the LLDB shared library. 5 # 6 # To use this in the embedded python interpreter using "lldb": 7 # 8 # cd /path/containing/crashlog.py 9 # lldb 10 # (lldb) script import crashlog 11 # "crashlog" command installed, type "crashlog --help" for detailed help 12 # (lldb) crashlog ~/Library/Logs/DiagnosticReports/a.crash 13 # 14 # The benefit of running the crashlog command inside lldb in the 15 # embedded python interpreter is when the command completes, there 16 # will be a target with all of the files loaded at the locations 17 # described in the crash log. Only the files that have stack frames 18 # in the backtrace will be loaded unless the "--load-all" option 19 # has been specified. This allows users to explore the program in the 20 # state it was in right at crash time. 21 # 22 # On MacOSX csh, tcsh: 23 # ( setenv PYTHONPATH /path/to/LLDB.framework/Resources/Python ; ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash ) 24 # 25 # On MacOSX sh, bash: 26 # PYTHONPATH=/path/to/LLDB.framework/Resources/Python ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash 27 #---------------------------------------------------------------------- 28 29 import lldb 30 import commands 31 import optparse 32 import os 33 import plistlib 34 import re 35 import shlex 36 import sys 37 import time 38 import uuid 39 40 class Address: 41 """Class that represents an address that will be symbolicated""" 42 def __init__(self, target, load_addr): 43 self.target = target 44 self.load_addr = load_addr # The load address that this object represents 45 self.so_addr = None # the resolved lldb.SBAddress (if any), named so_addr for section/offset address 46 self.sym_ctx = None # The cached symbol context for this address 47 self.description = None # Any original textual description of this address to be used as a backup in case symbolication fails 48 self.symbolication = None # The cached symbolicated string that describes this address 49 self.inlined = False 50 def __str__(self): 51 s = "%#16.16x" % (self.load_addr) 52 if self.symbolication: 53 s += " %s" % (self.symbolication) 54 elif self.description: 55 s += " %s" % (self.description) 56 elif self.so_addr: 57 s += " %s" % (self.so_addr) 58 return s 59 60 def resolve_addr(self): 61 if self.so_addr == None: 62 self.so_addr = self.target.ResolveLoadAddress (self.load_addr) 63 return self.so_addr 64 65 def is_inlined(self): 66 return self.inlined 67 68 def get_symbol_context(self): 69 if self.sym_ctx == None: 70 sb_addr = self.resolve_addr() 71 if sb_addr: 72 self.sym_ctx = self.target.ResolveSymbolContextForAddress (sb_addr, lldb.eSymbolContextEverything) 73 else: 74 self.sym_ctx = lldb.SBSymbolContext() 75 return self.sym_ctx 76 77 def get_instructions(self): 78 sym_ctx = self.get_symbol_context() 79 if sym_ctx: 80 function = sym_ctx.GetFunction() 81 if function: 82 return function.GetInstructions(self.target) 83 return sym_ctx.GetSymbol().GetInstructions(self.target) 84 return None 85 86 def symbolicate(self, verbose = False): 87 if self.symbolication == None: 88 self.symbolication = '' 89 self.inlined = False 90 sym_ctx = self.get_symbol_context() 91 if sym_ctx: 92 module = sym_ctx.GetModule() 93 if module: 94 # Print full source file path in verbose mode 95 if verbose: 96 self.symbolication += str(module.GetFileSpec()) + '`' 97 else: 98 self.symbolication += module.GetFileSpec().GetFilename() + '`' 99 function_start_load_addr = -1 100 function = sym_ctx.GetFunction() 101 block = sym_ctx.GetBlock() 102 line_entry = sym_ctx.GetLineEntry() 103 symbol = sym_ctx.GetSymbol() 104 inlined_block = block.GetContainingInlinedBlock(); 105 if function: 106 self.symbolication += function.GetName() 107 108 if inlined_block: 109 self.inlined = True 110 self.symbolication += ' [inlined] ' + inlined_block.GetInlinedName(); 111 block_range_idx = inlined_block.GetRangeIndexForBlockAddress (self.so_addr) 112 if block_range_idx < lldb.UINT32_MAX: 113 block_range_start_addr = inlined_block.GetRangeStartAddress (block_range_idx) 114 function_start_load_addr = block_range_start_addr.GetLoadAddress (self.target) 115 if function_start_load_addr == -1: 116 function_start_load_addr = function.GetStartAddress().GetLoadAddress (self.target) 117 elif symbol: 118 self.symbolication += symbol.GetName() 119 function_start_load_addr = symbol.GetStartAddress().GetLoadAddress (self.target) 120 else: 121 self.symbolication = '' 122 return False 123 124 # Dump the offset from the current function or symbol if it is non zero 125 function_offset = self.load_addr - function_start_load_addr 126 if function_offset > 0: 127 self.symbolication += " + %u" % (function_offset) 128 elif function_offset < 0: 129 self.symbolication += " %i (invalid negative offset, file a bug) " % function_offset 130 131 # Print out any line information if any is available 132 if line_entry.GetFileSpec(): 133 # Print full source file path in verbose mode 134 if verbose: 135 self.symbolication += ' at %s' % line_entry.GetFileSpec() 136 else: 137 self.symbolication += ' at %s' % line_entry.GetFileSpec().GetFilename() 138 self.symbolication += ':%u' % line_entry.GetLine () 139 column = line_entry.GetColumn() 140 if column > 0: 141 self.symbolication += ':%u' % column 142 return True 143 return False 144 145 class Section: 146 """Class that represents an load address range""" 147 sect_info_regex = re.compile('(?P<name>[^=]+)=(?P<range>.*)') 148 addr_regex = re.compile('^\s*(?P<start>0x[0-9A-Fa-f]+)\s*$') 149 range_regex = re.compile('^\s*(?P<start>0x[0-9A-Fa-f]+)\s*(?P<op>[-+])\s*(?P<end>0x[0-9A-Fa-f]+)\s*$') 150 151 def __init__(self, start_addr = None, end_addr = None, name = None): 152 self.start_addr = start_addr 153 self.end_addr = end_addr 154 self.name = name 155 156 def contains(self, addr): 157 return self.start_addr <= addr and addr < self.end_addr; 158 159 def set_from_string(self, s): 160 match = self.sect_info_regex.match (s) 161 if match: 162 self.name = match.group('name') 163 range_str = match.group('range') 164 addr_match = self.addr_regex.match(range_str) 165 if addr_match: 166 self.start_addr = int(addr_match.group('start'), 16) 167 self.end_addr = None 168 return True 169 170 range_match = self.range_regex.match(range_str) 171 if range_match: 172 self.start_addr = int(range_match.group('start'), 16) 173 self.end_addr = int(range_match.group('end'), 16) 174 op = range_match.group('op') 175 if op == '+': 176 self.end_addr += self.start_addr 177 return True 178 print 'error: invalid section info string "%s"' % s 179 print 'Valid section info formats are:' 180 print 'Format Example Description' 181 print '--------------------- -----------------------------------------------' 182 print '<name>=<base> __TEXT=0x123000 Section from base address only' 183 print '<name>=<base>-<end> __TEXT=0x123000-0x124000 Section from base address and end address' 184 print '<name>=<base>+<size> __TEXT=0x123000+0x1000 Section from base address and size' 185 return False 186 187 def __str__(self): 188 if self.name: 189 if self.end_addr != None: 190 if self.start_addr != None: 191 return "%s=[0x%16.16x - 0x%16.16x)" % (self.name, self.start_addr, self.end_addr) 192 else: 193 if self.start_addr != None: 194 return "%s=0x%16.16x" % (self.name, self.start_addr) 195 return self.name 196 return "<invalid>" 197 198 class Image: 199 """A class that represents an executable image and any associated data""" 200 201 def __init__(self, path, uuid = None): 202 self.path = path 203 self.resolved_path = None 204 self.resolved = False 205 self.unavailable = False 206 self.uuid = uuid 207 self.section_infos = list() 208 self.identifier = None 209 self.version = None 210 self.arch = None 211 self.module = None 212 self.symfile = None 213 self.slide = None 214 215 216 def dump(self, prefix): 217 print "%s%s" % (prefix, self) 218 219 def debug_dump(self): 220 print 'path = "%s"' % (self.path) 221 print 'resolved_path = "%s"' % (self.resolved_path) 222 print 'resolved = %i' % (self.resolved) 223 print 'unavailable = %i' % (self.unavailable) 224 print 'uuid = %s' % (self.uuid) 225 print 'section_infos = %s' % (self.section_infos) 226 print 'identifier = "%s"' % (self.identifier) 227 print 'version = %s' % (self.version) 228 print 'arch = %s' % (self.arch) 229 print 'module = %s' % (self.module) 230 print 'symfile = "%s"' % (self.symfile) 231 print 'slide = %i (0x%x)' % (self.slide, self.slide) 232 233 def __str__(self): 234 s = "%s %s %s" % (self.get_uuid(), self.version, self.get_resolved_path()) 235 for section_info in self.section_infos: 236 s += ", %s" % (section_info) 237 if self.slide != None: 238 s += ', slide = 0x%16.16x' % self.slide 239 return s 240 241 def add_section(self, section): 242 #print "added '%s' to '%s'" % (section, self.path) 243 self.section_infos.append (section) 244 245 def get_section_containing_load_addr (self, load_addr): 246 for section_info in self.section_infos: 247 if section_info.contains(load_addr): 248 return section_info 249 return None 250 251 def get_resolved_path(self): 252 if self.resolved_path: 253 return self.resolved_path 254 elif self.path: 255 return self.path 256 return None 257 258 def get_resolved_path_basename(self): 259 path = self.get_resolved_path() 260 if path: 261 return os.path.basename(path) 262 return None 263 264 def symfile_basename(self): 265 if self.symfile: 266 return os.path.basename(self.symfile) 267 return None 268 269 def has_section_load_info(self): 270 return self.section_infos or self.slide != None 271 272 def load_module(self, target): 273 if self.unavailable: 274 return None # We already warned that we couldn't find this module, so don't return an error string 275 # Load this module into "target" using the section infos to 276 # set the section load addresses 277 if self.has_section_load_info(): 278 if target: 279 if self.module: 280 if self.section_infos: 281 num_sections_loaded = 0 282 for section_info in self.section_infos: 283 if section_info.name: 284 section = self.module.FindSection (section_info.name) 285 if section: 286 error = target.SetSectionLoadAddress (section, section_info.start_addr) 287 if error.Success(): 288 num_sections_loaded += 1 289 else: 290 return 'error: %s' % error.GetCString() 291 else: 292 return 'error: unable to find the section named "%s"' % section_info.name 293 else: 294 return 'error: unable to find "%s" section in "%s"' % (range.name, self.get_resolved_path()) 295 if num_sections_loaded == 0: 296 return 'error: no sections were successfully loaded' 297 else: 298 err = target.SetModuleLoadAddress(self.module, self.slide) 299 if err.Fail(): 300 return err.GetCString() 301 return None 302 else: 303 return 'error: invalid module' 304 else: 305 return 'error: invalid target' 306 else: 307 return 'error: no section infos' 308 309 def add_module(self, target): 310 '''Add the Image described in this object to "target" and load the sections if "load" is True.''' 311 if target: 312 # Try and find using UUID only first so that paths need not match up 313 uuid_str = self.get_normalized_uuid_string() 314 if uuid_str: 315 self.module = target.AddModule (None, None, uuid_str) 316 if not self.module: 317 self.locate_module_and_debug_symbols () 318 if self.unavailable: 319 return None 320 resolved_path = self.get_resolved_path() 321 self.module = target.AddModule (resolved_path, self.arch, uuid_str, self.symfile) 322 if not self.module: 323 return 'error: unable to get module for (%s) "%s"' % (self.arch, self.get_resolved_path()) 324 if self.has_section_load_info(): 325 return self.load_module(target) 326 else: 327 return None # No sections, the module was added to the target, so success 328 else: 329 return 'error: invalid target' 330 331 def locate_module_and_debug_symbols (self): 332 # By default, just use the paths that were supplied in: 333 # self.path 334 # self.resolved_path 335 # self.module 336 # self.symfile 337 # Subclasses can inherit from this class and override this function 338 self.resolved = True 339 return True 340 341 def get_uuid(self): 342 if not self.uuid and self.module: 343 self.uuid = uuid.UUID(self.module.GetUUIDString()) 344 return self.uuid 345 346 def get_normalized_uuid_string(self): 347 if self.uuid: 348 return str(self.uuid).upper() 349 return None 350 351 def create_target(self): 352 '''Create a target using the information in this Image object.''' 353 if self.unavailable: 354 return None 355 356 if self.locate_module_and_debug_symbols (): 357 resolved_path = self.get_resolved_path(); 358 path_spec = lldb.SBFileSpec (resolved_path) 359 #result.PutCString ('plist[%s] = %s' % (uuid, self.plist)) 360 error = lldb.SBError() 361 target = lldb.debugger.CreateTarget (resolved_path, self.arch, None, False, error); 362 if target: 363 self.module = target.FindModule(path_spec) 364 if self.has_section_load_info(): 365 err = self.load_module(target) 366 if err: 367 print 'ERROR: ', err 368 return target 369 else: 370 print 'error: unable to create a valid target for (%s) "%s"' % (self.arch, self.path) 371 else: 372 print 'error: unable to locate main executable (%s) "%s"' % (self.arch, self.path) 373 return None 374 375 class Symbolicator: 376 377 def __init__(self): 378 """A class the represents the information needed to symbolicate addresses in a program""" 379 self.target = None 380 self.images = list() # a list of images to be used when symbolicating 381 self.addr_mask = 0xffffffffffffffff 382 383 def __str__(self): 384 s = "Symbolicator:\n" 385 if self.target: 386 s += "Target = '%s'\n" % (self.target) 387 s += "Target modules:'\n" 388 for m in self.target.modules: 389 print m 390 s += "Images:\n" 391 for image in self.images: 392 s += ' %s\n' % (image) 393 return s 394 395 def find_images_with_identifier(self, identifier): 396 images = list() 397 for image in self.images: 398 if image.identifier == identifier: 399 images.append(image) 400 return images 401 402 def find_image_containing_load_addr(self, load_addr): 403 for image in self.images: 404 if image.get_section_containing_load_addr (load_addr): 405 return image 406 return None 407 408 def create_target(self): 409 if self.target: 410 return self.target 411 412 if self.images: 413 for image in self.images: 414 self.target = image.create_target () 415 if self.target: 416 if self.target.GetAddressByteSize() == 4: 417 triple = self.target.triple 418 if triple: 419 arch = triple.split('-')[0] 420 if "arm" in arch: 421 self.addr_mask = 0xfffffffffffffffe 422 return self.target 423 return None 424 425 def symbolicate(self, load_addr, verbose = False): 426 if not self.target: 427 self.create_target() 428 if self.target: 429 live_process = False 430 process = self.target.process 431 if process: 432 state = process.state 433 if state > lldb.eStateUnloaded and state < lldb.eStateDetached: 434 live_process = True 435 # If we don't have a live process, we can attempt to find the image 436 # that a load address belongs to and lazily load its module in the 437 # target, but we shouldn't do any of this if we have a live process 438 if not live_process: 439 image = self.find_image_containing_load_addr (load_addr) 440 if image: 441 image.add_module (self.target) 442 symbolicated_address = Address(self.target, load_addr) 443 if symbolicated_address.symbolicate (verbose): 444 if symbolicated_address.so_addr: 445 symbolicated_addresses = list() 446 symbolicated_addresses.append(symbolicated_address) 447 # See if we were able to reconstruct anything? 448 while 1: 449 inlined_parent_so_addr = lldb.SBAddress() 450 inlined_parent_sym_ctx = symbolicated_address.sym_ctx.GetParentOfInlinedScope (symbolicated_address.so_addr, inlined_parent_so_addr) 451 if not inlined_parent_sym_ctx: 452 break 453 if not inlined_parent_so_addr: 454 break 455 456 symbolicated_address = Address(self.target, inlined_parent_so_addr.GetLoadAddress(self.target)) 457 symbolicated_address.sym_ctx = inlined_parent_sym_ctx 458 symbolicated_address.so_addr = inlined_parent_so_addr 459 symbolicated_address.symbolicate (verbose) 460 461 # push the new frame onto the new frame stack 462 symbolicated_addresses.append (symbolicated_address) 463 464 if symbolicated_addresses: 465 return symbolicated_addresses 466 else: 467 print 'error: no target in Symbolicator' 468 return None 469 470 471 def disassemble_instructions (target, instructions, pc, insts_before_pc, insts_after_pc, non_zeroeth_frame): 472 lines = list() 473 pc_index = -1 474 comment_column = 50 475 for inst_idx, inst in enumerate(instructions): 476 inst_pc = inst.GetAddress().GetLoadAddress(target); 477 if pc == inst_pc: 478 pc_index = inst_idx 479 mnemonic = inst.GetMnemonic (target) 480 operands = inst.GetOperands (target) 481 comment = inst.GetComment (target) 482 #data = inst.GetData (target) 483 lines.append ("%#16.16x: %8s %s" % (inst_pc, mnemonic, operands)) 484 if comment: 485 line_len = len(lines[-1]) 486 if line_len < comment_column: 487 lines[-1] += ' ' * (comment_column - line_len) 488 lines[-1] += "; %s" % comment 489 490 if pc_index >= 0: 491 # If we are disassembling the non-zeroeth frame, we need to backup the PC by 1 492 if non_zeroeth_frame and pc_index > 0: 493 pc_index = pc_index - 1 494 if insts_before_pc == -1: 495 start_idx = 0 496 else: 497 start_idx = pc_index - insts_before_pc 498 if start_idx < 0: 499 start_idx = 0 500 if insts_before_pc == -1: 501 end_idx = inst_idx 502 else: 503 end_idx = pc_index + insts_after_pc 504 if end_idx > inst_idx: 505 end_idx = inst_idx 506 for i in range(start_idx, end_idx+1): 507 if i == pc_index: 508 print ' -> ', lines[i] 509 else: 510 print ' ', lines[i] 511 512 def print_module_section_data (section): 513 print section 514 section_data = section.GetSectionData() 515 if section_data: 516 ostream = lldb.SBStream() 517 section_data.GetDescription (ostream, section.GetFileAddress()) 518 print ostream.GetData() 519 520 def print_module_section (section, depth): 521 print section 522 if depth > 0: 523 num_sub_sections = section.GetNumSubSections() 524 for sect_idx in range(num_sub_sections): 525 print_module_section (section.GetSubSectionAtIndex(sect_idx), depth - 1) 526 527 def print_module_sections (module, depth): 528 for sect in module.section_iter(): 529 print_module_section (sect, depth) 530 531 def print_module_symbols (module): 532 for sym in module: 533 print sym 534 535 def Symbolicate(command_args): 536 537 usage = "usage: %prog [options] <addr1> [addr2 ...]" 538 description='''Symbolicate one or more addresses using LLDB's python scripting API..''' 539 parser = optparse.OptionParser(description=description, prog='crashlog.py',usage=usage) 540 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False) 541 parser.add_option('-p', '--platform', type='string', metavar='platform', dest='platform', help='Specify the platform to use when creating the debug target. Valid values include "localhost", "darwin-kernel", "ios-simulator", "remote-freebsd", "remote-macosx", "remote-ios", "remote-linux".') 542 parser.add_option('-f', '--file', type='string', metavar='file', dest='file', help='Specify a file to use when symbolicating') 543 parser.add_option('-a', '--arch', type='string', metavar='arch', dest='arch', help='Specify a architecture to use when symbolicating') 544 parser.add_option('-s', '--slide', type='int', metavar='slide', dest='slide', help='Specify the slide to use on the file specified with the --file option', default=None) 545 parser.add_option('--section', type='string', action='append', dest='section_strings', help='specify <sect-name>=<start-addr> or <sect-name>=<start-addr>-<end-addr>') 546 try: 547 (options, args) = parser.parse_args(command_args) 548 except: 549 return 550 symbolicator = Symbolicator() 551 images = list(); 552 if options.file: 553 image = Image(options.file); 554 image.arch = options.arch 555 # Add any sections that were specified with one or more --section options 556 if options.section_strings: 557 for section_str in options.section_strings: 558 section = Section() 559 if section.set_from_string (section_str): 560 image.add_section (section) 561 else: 562 sys.exit(1) 563 if options.slide != None: 564 image.slide = options.slide 565 symbolicator.images.append(image) 566 567 target = symbolicator.create_target() 568 if options.verbose: 569 print symbolicator 570 if target: 571 for addr_str in args: 572 addr = int(addr_str, 0) 573 symbolicated_addrs = symbolicator.symbolicate(addr, options.verbose) 574 for symbolicated_addr in symbolicated_addrs: 575 print symbolicated_addr 576 print 577 else: 578 print 'error: no target for %s' % (symbolicator) 579 580 if __name__ == '__main__': 581 # Create a new debugger instance 582 lldb.debugger = lldb.SBDebugger.Create() 583 Symbolicate (sys.argv[1:]) 584