1#!/usr/bin/env python
2
3"""
4Run lldb to disassemble all the available functions for an executable image.
5
6"""
7
8import os
9import re
10import sys
11from optparse import OptionParser
12
13def setupSysPath():
14    """
15    Add LLDB.framework/Resources/Python and the test dir to the sys.path.
16    """
17    # Get the directory containing the current script.
18    scriptPath = sys.path[0]
19    if not scriptPath.endswith(os.path.join('utils', 'test')):
20        print "This script expects to reside in lldb's utils/test directory."
21        sys.exit(-1)
22
23    # This is our base name component.
24    base = os.path.abspath(os.path.join(scriptPath, os.pardir, os.pardir))
25
26    # This is for the goodies in the test directory under base.
27    sys.path.append(os.path.join(base,'test'))
28
29    # These are for xcode build directories.
30    xcode3_build_dir = ['build']
31    xcode4_build_dir = ['build', 'lldb', 'Build', 'Products']
32    dbg = ['Debug']
33    rel = ['Release']
34    bai = ['BuildAndIntegration']
35    python_resource_dir = ['LLDB.framework', 'Resources', 'Python']
36
37    dbgPath  = os.path.join(base, *(xcode3_build_dir + dbg + python_resource_dir))
38    dbgPath2 = os.path.join(base, *(xcode4_build_dir + dbg + python_resource_dir))
39    relPath  = os.path.join(base, *(xcode3_build_dir + rel + python_resource_dir))
40    relPath2 = os.path.join(base, *(xcode4_build_dir + rel + python_resource_dir))
41    baiPath  = os.path.join(base, *(xcode3_build_dir + bai + python_resource_dir))
42    baiPath2 = os.path.join(base, *(xcode4_build_dir + bai + python_resource_dir))
43
44    lldbPath = None
45    if os.path.isfile(os.path.join(dbgPath, 'lldb.py')):
46        lldbPath = dbgPath
47    elif os.path.isfile(os.path.join(dbgPath2, 'lldb.py')):
48        lldbPath = dbgPath2
49    elif os.path.isfile(os.path.join(relPath, 'lldb.py')):
50        lldbPath = relPath
51    elif os.path.isfile(os.path.join(relPath2, 'lldb.py')):
52        lldbPath = relPath2
53    elif os.path.isfile(os.path.join(baiPath, 'lldb.py')):
54        lldbPath = baiPath
55    elif os.path.isfile(os.path.join(baiPath2, 'lldb.py')):
56        lldbPath = baiPath2
57
58    if not lldbPath:
59        print 'This script requires lldb.py to be in either ' + dbgPath + ',',
60        print relPath + ', or ' + baiPath
61        sys.exit(-1)
62
63    # This is to locate the lldb.py module.  Insert it right after sys.path[0].
64    sys.path[1:1] = [lldbPath]
65    #print "sys.path:", sys.path
66
67
68def run_command(ci, cmd, res, echo=True):
69    if echo:
70        print "run command:", cmd
71    ci.HandleCommand(cmd, res)
72    if res.Succeeded():
73        if echo:
74            print "run_command output:", res.GetOutput()
75    else:
76        if echo:
77            print "run command failed!"
78            print "run_command error:", res.GetError()
79
80def do_lldb_disassembly(lldb_commands, exe, disassemble_options, num_symbols,
81                        symbols_to_disassemble,
82                        re_symbol_pattern,
83                        quiet_disassembly):
84    import lldb, atexit, re
85
86    # Create the debugger instance now.
87    dbg = lldb.SBDebugger.Create()
88    if not dbg:
89            raise Exception('Invalid debugger instance')
90
91    # Register an exit callback.
92    atexit.register(lambda: lldb.SBDebugger.Terminate())
93
94    # We want our debugger to be synchronous.
95    dbg.SetAsync(False)
96
97    # Get the command interpreter from the debugger.
98    ci = dbg.GetCommandInterpreter()
99    if not ci:
100        raise Exception('Could not get the command interpreter')
101
102    # And the associated result object.
103    res = lldb.SBCommandReturnObject()
104
105    # See if there any extra command(s) to execute before we issue the file command.
106    for cmd in lldb_commands:
107        run_command(ci, cmd, res, not quiet_disassembly)
108
109    # Now issue the file command.
110    run_command(ci, 'file %s' % exe, res, not quiet_disassembly)
111
112    # Create a target.
113    #target = dbg.CreateTarget(exe)
114    target = dbg.GetSelectedTarget()
115    stream = lldb.SBStream()
116
117    def IsCodeType(symbol):
118        """Check whether an SBSymbol represents code."""
119        return symbol.GetType() == lldb.eSymbolTypeCode
120
121    # Define a generator for the symbols to disassemble.
122    def symbol_iter(num, symbols, re_symbol_pattern, target, verbose):
123        # If we specify the symbols to disassemble, ignore symbol table dump.
124        if symbols:
125            for i in range(len(symbols)):
126                if verbose:
127                    print "symbol:", symbols[i]
128                yield symbols[i]
129        else:
130            limited = True if num != -1 else False
131            if limited:
132                count = 0
133            if re_symbol_pattern:
134                pattern = re.compile(re_symbol_pattern)
135            stream = lldb.SBStream()
136            for m in target.module_iter():
137                if verbose:
138                    print "module:", m
139                for s in m:
140                    if limited and count >= num:
141                        return
142                    # If a regexp symbol pattern is supplied, consult it.
143                    if re_symbol_pattern:
144                        # If the pattern does not match, look for the next symbol.
145                        if not pattern.match(s.GetName()):
146                            continue
147
148                    # If we come here, we're ready to disassemble the symbol.
149                    if verbose:
150                        print "symbol:", s.GetName()
151                    if IsCodeType(s):
152                        if limited:
153                            count = count + 1
154                            if verbose:
155                                print "returning symbol:", s.GetName()
156                        yield s.GetName()
157                    if verbose:
158                        print "start address:", s.GetStartAddress()
159                        print "end address:", s.GetEndAddress()
160                        s.GetDescription(stream)
161                        print "symbol description:", stream.GetData()
162                        stream.Clear()
163
164    # Disassembly time.
165    for symbol in symbol_iter(num_symbols, symbols_to_disassemble, re_symbol_pattern, target, not quiet_disassembly):
166        cmd = "disassemble %s '%s'" % (disassemble_options, symbol)
167        run_command(ci, cmd, res, not quiet_disassembly)
168
169
170def main():
171    # This is to set up the Python path to include the pexpect-2.4 dir.
172    # Remember to update this when/if things change.
173    scriptPath = sys.path[0]
174    sys.path.append(os.path.join(scriptPath, os.pardir, os.pardir, 'test', 'pexpect-2.4'))
175
176    parser = OptionParser(usage="""\
177Run lldb to disassemble all the available functions for an executable image.
178
179Usage: %prog [options]
180""")
181    parser.add_option('-C', '--lldb-command',
182                      type='string', action='append', metavar='COMMAND',
183                      default=[], dest='lldb_commands',
184                      help='Command(s) lldb executes after starting up (can be empty)')
185    parser.add_option('-e', '--executable',
186                      type='string', action='store',
187                      dest='executable',
188                      help="""Mandatory: the executable to do disassembly on.""")
189    parser.add_option('-o', '--options',
190                      type='string', action='store',
191                      dest='disassemble_options',
192                      help="""Mandatory: the options passed to lldb's 'disassemble' command.""")
193    parser.add_option('-q', '--quiet-disassembly',
194                      action='store_true', default=False,
195                      dest='quiet_disassembly',
196                      help="""The symbol(s) to invoke lldb's 'disassemble' command on, if specified.""")
197    parser.add_option('-n', '--num-symbols',
198                      type='int', action='store', default=-1,
199                      dest='num_symbols',
200                      help="""The number of symbols to disassemble, if specified.""")
201    parser.add_option('-p', '--symbol_pattern',
202                      type='string', action='store',
203                      dest='re_symbol_pattern',
204                      help="""The regular expression of symbols to invoke lldb's 'disassemble' command.""")
205    parser.add_option('-s', '--symbol',
206                      type='string', action='append', metavar='SYMBOL', default=[],
207                      dest='symbols_to_disassemble',
208                      help="""The symbol(s) to invoke lldb's 'disassemble' command on, if specified.""")
209
210    opts, args = parser.parse_args()
211
212    lldb_commands = opts.lldb_commands
213
214    if not opts.executable or not opts.disassemble_options:
215        parser.print_help()
216        sys.exit(1)
217
218    executable = opts.executable
219    disassemble_options = opts.disassemble_options
220    quiet_disassembly = opts.quiet_disassembly
221    num_symbols = opts.num_symbols
222    symbols_to_disassemble = opts.symbols_to_disassemble
223    re_symbol_pattern = opts.re_symbol_pattern
224
225    # We have parsed the options.
226    if not quiet_disassembly:
227        print "lldb commands:", lldb_commands
228        print "executable:", executable
229        print "disassemble options:", disassemble_options
230        print "quiet disassembly output:", quiet_disassembly
231        print "num of symbols to disassemble:", num_symbols
232        print "symbols to disassemble:", symbols_to_disassemble
233        print "regular expression of symbols to disassemble:", re_symbol_pattern
234
235    setupSysPath()
236    do_lldb_disassembly(lldb_commands, executable, disassemble_options,
237                        num_symbols,
238                        symbols_to_disassemble,
239                        re_symbol_pattern,
240                        quiet_disassembly)
241
242if __name__ == '__main__':
243    main()
244