| #!/usr/bin/env python3 | |
| import lldb | |
| import optparse | |
| import shlex | |
| import string | |
| import sys | |
| class DumpLineTables: | |
| command_name = "dump-line-tables" | |
| short_description = ( | |
| "Dumps full paths to compile unit files and optionally all line table files." | |
| ) | |
| description = "Dumps all line tables from all compile units for any modules specified as arguments. Specifying the --verbose flag will output address ranges for each line entry." | |
| usage = "usage: %prog [options] MODULE1 [MODULE2 ...]" | |
| def create_options(self): | |
| self.parser = optparse.OptionParser( | |
| description=self.description, prog=self.command_name, usage=self.usage | |
| ) | |
| self.parser.add_option( | |
| "-v", | |
| "--verbose", | |
| action="store_true", | |
| dest="verbose", | |
| help="Display verbose output.", | |
| default=False, | |
| ) | |
| def get_short_help(self): | |
| return self.short_description | |
| def get_long_help(self): | |
| return self.help_string | |
| def __init__(self, debugger, unused): | |
| self.create_options() | |
| self.help_string = self.parser.format_help() | |
| def __call__(self, debugger, command, exe_ctx, result): | |
| # Use the Shell Lexer to properly parse up command options just like a | |
| # shell would | |
| command_args = shlex.split(command) | |
| try: | |
| (options, args) = self.parser.parse_args(command_args) | |
| except: | |
| # if you don't handle exceptions, passing an incorrect argument to the OptionParser will cause LLDB to exit | |
| # (courtesy of OptParse dealing with argument errors by throwing SystemExit) | |
| result.SetError("option parsing failed") | |
| return | |
| # Always get program state from the SBExecutionContext passed in as exe_ctx | |
| target = exe_ctx.GetTarget() | |
| if not target.IsValid(): | |
| result.SetError("invalid target") | |
| return | |
| for module_path in args: | |
| module = target.module[module_path] | |
| if not module: | |
| result.SetError('no module found that matches "%s".' % (module_path)) | |
| return | |
| num_cus = module.GetNumCompileUnits() | |
| print('Module: "%s"' % (module.file.fullpath), end=" ", file=result) | |
| if num_cus == 0: | |
| print("no debug info.", file=result) | |
| continue | |
| print("has %u compile units:" % (num_cus), file=result) | |
| for cu_idx in range(num_cus): | |
| cu = module.GetCompileUnitAtIndex(cu_idx) | |
| print(" Compile Unit: %s" % (cu.file.fullpath), file=result) | |
| for line_idx in range(cu.GetNumLineEntries()): | |
| line_entry = cu.GetLineEntryAtIndex(line_idx) | |
| start_file_addr = line_entry.addr.file_addr | |
| end_file_addr = line_entry.end_addr.file_addr | |
| # If the two addresses are equal, this line table entry | |
| # is a termination entry | |
| if options.verbose: | |
| if start_file_addr != end_file_addr: | |
| result.PutCString( | |
| " [%#x - %#x): %s" | |
| % (start_file_addr, end_file_addr, line_entry) | |
| ) | |
| else: | |
| if start_file_addr == end_file_addr: | |
| result.PutCString(" %#x: END" % (start_file_addr)) | |
| else: | |
| result.PutCString( | |
| " %#x: %s" % (start_file_addr, line_entry) | |
| ) | |
| if start_file_addr == end_file_addr: | |
| result.PutCString("\n") | |
| class DumpFiles: | |
| command_name = "dump-files" | |
| short_description = ( | |
| "Dumps full paths to compile unit files and optionally all line table files." | |
| ) | |
| usage = "usage: %prog [options] MODULE1 [MODULE2 ...]" | |
| description = """This class adds a dump-files command to the LLDB interpreter. | |
| This command will dump all compile unit file paths found for each source file | |
| for the binaries specified as arguments in the current target. Specify the | |
| --support-files or -s option to see all file paths that a compile unit uses in | |
| its lines tables. This is handy for troubleshooting why breakpoints aren't | |
| working in IDEs that specify full paths to source files when setting file and | |
| line breakpoints. Sometimes symlinks cause the debug info to contain the symlink | |
| path and an IDE will resolve the path to the actual file and use the resolved | |
| path when setting breakpoints. | |
| """ | |
| def create_options(self): | |
| # Pass add_help_option = False, since this keeps the command in line with lldb commands, | |
| # and we wire up "help command" to work by providing the long & short help methods below. | |
| self.parser = optparse.OptionParser( | |
| description=self.description, | |
| prog=self.command_name, | |
| usage=self.usage, | |
| add_help_option=False, | |
| ) | |
| self.parser.add_option( | |
| "-s", | |
| "--support-files", | |
| action="store_true", | |
| dest="support_files", | |
| help="Dumps full paths to all files used in a compile unit.", | |
| default=False, | |
| ) | |
| def get_short_help(self): | |
| return self.short_description | |
| def get_long_help(self): | |
| return self.help_string | |
| def __init__(self, debugger, unused): | |
| self.create_options() | |
| self.help_string = self.parser.format_help() | |
| def __call__(self, debugger, command, exe_ctx, result): | |
| # Use the Shell Lexer to properly parse up command options just like a | |
| # shell would | |
| command_args = shlex.split(command) | |
| try: | |
| (options, args) = self.parser.parse_args(command_args) | |
| except: | |
| # if you don't handle exceptions, passing an incorrect argument to the OptionParser will cause LLDB to exit | |
| # (courtesy of OptParse dealing with argument errors by throwing SystemExit) | |
| result.SetError("option parsing failed") | |
| return | |
| # Always get program state from the SBExecutionContext passed in as exe_ctx | |
| target = exe_ctx.GetTarget() | |
| if not target.IsValid(): | |
| result.SetError("invalid target") | |
| return | |
| if len(args) == 0: | |
| result.SetError("one or more executable paths must be specified") | |
| return | |
| for module_path in args: | |
| module = target.module[module_path] | |
| if not module: | |
| result.SetError('no module found that matches "%s".' % (module_path)) | |
| return | |
| num_cus = module.GetNumCompileUnits() | |
| print('Module: "%s"' % (module.file.fullpath), end=" ", file=result) | |
| if num_cus == 0: | |
| print("no debug info.", file=result) | |
| continue | |
| print("has %u compile units:" % (num_cus), file=result) | |
| for i in range(num_cus): | |
| cu = module.GetCompileUnitAtIndex(i) | |
| print(" Compile Unit: %s" % (cu.file.fullpath), file=result) | |
| if options.support_files: | |
| num_support_files = cu.GetNumSupportFiles() | |
| for j in range(num_support_files): | |
| path = cu.GetSupportFileAtIndex(j).fullpath | |
| print(" file[%u]: %s" % (j, path), file=result) | |
| def __lldb_init_module(debugger, dict): | |
| # This initializer is being run from LLDB in the embedded command interpreter | |
| # Add any commands contained in this module to LLDB | |
| debugger.HandleCommand( | |
| "command script add -o -c %s.DumpLineTables %s" | |
| % (__name__, DumpLineTables.command_name) | |
| ) | |
| debugger.HandleCommand( | |
| "command script add -o -c %s.DumpFiles %s" % (__name__, DumpFiles.command_name) | |
| ) | |
| print( | |
| 'The "%s" and "%s" commands have been installed.' | |
| % (DumpLineTables.command_name, DumpFiles.command_name) | |
| ) |