1#!/usr/bin/env python
2from __future__ import print_function
3
4import lldb
5import optparse
6import shlex
7import string
8import sys
9
10
11class DumpLineTables:
12    command_name = "dump-line-tables"
13    short_decription = "Dumps full paths to compile unit files and optionally all line table files."
14    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.'
15    usage = "usage: %prog [options] MODULE1 [MODULE2 ...]"
16    def create_options(self):
17        self.parser = optparse.OptionParser(
18            description=self.description,
19            prog=self.command_name,
20            usage=self.usage)
21
22        self.parser.add_option(
23            '-v',
24            '--verbose',
25            action='store_true',
26            dest='verbose',
27            help='Display verbose output.',
28            default=False)
29
30    def get_short_help(self):
31        return self.short_decription
32
33    def get_long_help(self):
34        return self.help_string
35
36    def __init__(self, debugger, unused):
37        self.create_options()
38        self.help_string = self.parser.format_help()
39
40    def __call__(self, debugger, command, exe_ctx, result):
41        # Use the Shell Lexer to properly parse up command options just like a
42        # shell would
43        command_args = shlex.split(command)
44
45        try:
46            (options, args) = self.parser.parse_args(command_args)
47        except:
48            # if you don't handle exceptions, passing an incorrect argument to the OptionParser will cause LLDB to exit
49            # (courtesy of OptParse dealing with argument errors by throwing SystemExit)
50            result.SetError("option parsing failed")
51            return
52
53        # Always get program state from the SBExecutionContext passed in as exe_ctx
54        target = exe_ctx.GetTarget()
55        if not target.IsValid():
56            result.SetError("invalid target")
57            return
58
59        for module_path in args:
60            module = target.module[module_path]
61            if not module:
62                result.SetError('no module found that matches "%s".' % (module_path))
63                return
64            num_cus = module.GetNumCompileUnits()
65            print('Module: "%s"' % (module.file.fullpath), end=' ', file=result)
66            if num_cus == 0:
67                print('no debug info.', file=result)
68                continue
69            print('has %u compile units:' % (num_cus), file=result)
70            for cu_idx in range(num_cus):
71                cu = module.GetCompileUnitAtIndex(cu_idx)
72                print('  Compile Unit: %s' % (cu.file.fullpath), file=result)
73                for line_idx in range(cu.GetNumLineEntries()):
74                    line_entry = cu.GetLineEntryAtIndex(line_idx)
75                    start_file_addr = line_entry.addr.file_addr
76                    end_file_addr = line_entry.end_addr.file_addr
77                    # If the two addresses are equal, this line table entry
78                    # is a termination entry
79                    if options.verbose:
80                        if start_file_addr != end_file_addr:
81                            result.PutCString(
82                                '    [%#x - %#x): %s' %
83                                (start_file_addr, end_file_addr, line_entry))
84                    else:
85                        if start_file_addr == end_file_addr:
86                            result.PutCString('    %#x: END' %
87                                              (start_file_addr))
88                        else:
89                            result.PutCString(
90                                '    %#x: %s' %
91                                (start_file_addr, line_entry))
92                    if start_file_addr == end_file_addr:
93                        result.PutCString("\n")
94
95
96class DumpFiles:
97    command_name = "dump-files"
98    short_description = "Dumps full paths to compile unit files and optionally all line table files."
99    usage = "usage: %prog [options] MODULE1 [MODULE2 ...]"
100    description = '''This class adds a dump-files command to the LLDB interpreter.
101
102This command will dump all compile unit file paths found for each source file
103for the binaries specified as arguments in the current target. Specify the
104--support-files or -s option to see all file paths that a compile unit uses in
105its lines tables. This is handy for troubleshooting why breakpoints aren't
106working in IDEs that specify full paths to source files when setting file and
107line breakpoints. Sometimes symlinks cause the debug info to contain the symlink
108path and an IDE will resolve the path to the actual file and use the resolved
109path when setting breakpoints.
110'''
111    def create_options(self):
112        # Pass add_help_option = False, since this keeps the command in line with lldb commands,
113        # and we wire up "help command" to work by providing the long & short help methods below.
114        self.parser = optparse.OptionParser(
115            description = self.description,
116            prog = self.command_name,
117            usage = self.usage,
118            add_help_option = False)
119
120        self.parser.add_option(
121            '-s',
122            '--support-files',
123            action = 'store_true',
124            dest = 'support_files',
125            help = 'Dumps full paths to all files used in a compile unit.',
126            default = False)
127
128    def get_short_help(self):
129        return self.short_description
130
131    def get_long_help(self):
132        return self.help_string
133
134    def __init__(self, debugger, unused):
135        self.create_options()
136        self.help_string = self.parser.format_help()
137
138    def __call__(self, debugger, command, exe_ctx, result):
139        # Use the Shell Lexer to properly parse up command options just like a
140        # shell would
141        command_args = shlex.split(command)
142
143        try:
144            (options, args) = self.parser.parse_args(command_args)
145        except:
146            # if you don't handle exceptions, passing an incorrect argument to the OptionParser will cause LLDB to exit
147            # (courtesy of OptParse dealing with argument errors by throwing SystemExit)
148            result.SetError("option parsing failed")
149            return
150
151        # Always get program state from the SBExecutionContext passed in as exe_ctx
152        target = exe_ctx.GetTarget()
153        if not target.IsValid():
154            result.SetError("invalid target")
155            return
156
157        if len(args) == 0:
158            result.SetError("one or more executable paths must be specified")
159            return
160
161        for module_path in args:
162            module = target.module[module_path]
163            if not module:
164                result.SetError('no module found that matches "%s".' % (module_path))
165                return
166            num_cus = module.GetNumCompileUnits()
167            print('Module: "%s"' % (module.file.fullpath), end=' ', file=result)
168            if num_cus == 0:
169                print('no debug info.', file=result)
170                continue
171            print('has %u compile units:' % (num_cus), file=result)
172            for i in range(num_cus):
173                cu = module.GetCompileUnitAtIndex(i)
174                print('  Compile Unit: %s' % (cu.file.fullpath), file=result)
175                if options.support_files:
176                    num_support_files = cu.GetNumSupportFiles()
177                    for j in range(num_support_files):
178                        path = cu.GetSupportFileAtIndex(j).fullpath
179                        print('    file[%u]: %s' % (j, path), file=result)
180
181
182def __lldb_init_module(debugger, dict):
183    # This initializer is being run from LLDB in the embedded command interpreter
184
185    # Add any commands contained in this module to LLDB
186    debugger.HandleCommand(
187        'command script add -c %s.DumpLineTables %s' % (__name__,
188                                                        DumpLineTables.command_name))
189    debugger.HandleCommand(
190        'command script add -c %s.DumpFiles %s' % (__name__, DumpFiles.command_name))
191    print('The "%s" and "%s" commands have been installed.' % (DumpLineTables.command_name,
192                                                               DumpFiles.command_name))
193