1#!/usr/bin/python
2
3#----------------------------------------------------------------------
4# Be sure to add the python path that points to the LLDB shared library.
5# On MacOSX csh, tcsh:
6#   setenv PYTHONPATH /Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python
7# On MacOSX sh, bash:
8#   export PYTHONPATH=/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python
9#----------------------------------------------------------------------
10
11from __future__ import print_function
12
13import optparse
14import os
15import platform
16import sys
17import subprocess
18
19#----------------------------------------------------------------------
20# Code that auto imports LLDB
21#----------------------------------------------------------------------
22try:
23    # Just try for LLDB in case PYTHONPATH is already correctly setup
24    import lldb
25except ImportError:
26    lldb_python_dirs = list()
27    # lldb is not in the PYTHONPATH, try some defaults for the current platform
28    platform_system = platform.system()
29    if platform_system == 'Darwin':
30        # On Darwin, try the currently selected Xcode directory
31        xcode_dir = subprocess.check_output("xcode-select --print-path", shell=True)
32        if xcode_dir:
33            lldb_python_dirs.append(
34                os.path.realpath(
35                    xcode_dir +
36                    '/../SharedFrameworks/LLDB.framework/Resources/Python'))
37            lldb_python_dirs.append(
38                xcode_dir + '/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
39        lldb_python_dirs.append(
40            '/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
41    success = False
42    for lldb_python_dir in lldb_python_dirs:
43        if os.path.exists(lldb_python_dir):
44            if not (sys.path.__contains__(lldb_python_dir)):
45                sys.path.append(lldb_python_dir)
46                try:
47                    import lldb
48                except ImportError:
49                    pass
50                else:
51                    print('imported lldb from: "%s"' % (lldb_python_dir))
52                    success = True
53                    break
54    if not success:
55        print("error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly")
56        sys.exit(1)
57
58
59def print_threads(process, options):
60    if options.show_threads:
61        for thread in process:
62            print('%s %s' % (thread, thread.GetFrameAtIndex(0)))
63
64
65def run_commands(command_interpreter, commands):
66    return_obj = lldb.SBCommandReturnObject()
67    for command in commands:
68        command_interpreter.HandleCommand(command, return_obj)
69        if return_obj.Succeeded():
70            print(return_obj.GetOutput())
71        else:
72            print(return_obj)
73            if options.stop_on_error:
74                break
75
76
77def main(argv):
78    description = '''Debugs a program using the LLDB python API and uses asynchronous broadcast events to watch for process state changes.'''
79    epilog = '''Examples:
80
81#----------------------------------------------------------------------
82# Run "/bin/ls" with the arguments "-lAF /tmp/", and set a breakpoint
83# at "malloc" and backtrace and read all registers each time we stop
84#----------------------------------------------------------------------
85% ./process_events.py --breakpoint malloc --stop-command bt --stop-command 'register read' -- /bin/ls -lAF /tmp/
86
87'''
88    optparse.OptionParser.format_epilog = lambda self, formatter: self.epilog
89    parser = optparse.OptionParser(
90        description=description,
91        prog='process_events',
92        usage='usage: process_events [options] program [arg1 arg2]',
93        epilog=epilog)
94    parser.add_option(
95        '-v',
96        '--verbose',
97        action='store_true',
98        dest='verbose',
99        help="Enable verbose logging.",
100        default=False)
101    parser.add_option(
102        '-b',
103        '--breakpoint',
104        action='append',
105        type='string',
106        metavar='BPEXPR',
107        dest='breakpoints',
108        help='Breakpoint commands to create after the target has been created, the values will be sent to the "_regexp-break" command which supports breakpoints by name, file:line, and address.')
109    parser.add_option(
110        '-a',
111        '--arch',
112        type='string',
113        dest='arch',
114        help='The architecture to use when creating the debug target.',
115        default=None)
116    parser.add_option(
117        '--platform',
118        type='string',
119        metavar='platform',
120        dest='platform',
121        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".',
122        default=None)
123    parser.add_option(
124        '-l',
125        '--launch-command',
126        action='append',
127        type='string',
128        metavar='CMD',
129        dest='launch_commands',
130        help='LLDB command interpreter commands to run once after the process has launched. This option can be specified more than once.',
131        default=[])
132    parser.add_option(
133        '-s',
134        '--stop-command',
135        action='append',
136        type='string',
137        metavar='CMD',
138        dest='stop_commands',
139        help='LLDB command interpreter commands to run each time the process stops. This option can be specified more than once.',
140        default=[])
141    parser.add_option(
142        '-c',
143        '--crash-command',
144        action='append',
145        type='string',
146        metavar='CMD',
147        dest='crash_commands',
148        help='LLDB command interpreter commands to run in case the process crashes. This option can be specified more than once.',
149        default=[])
150    parser.add_option(
151        '-x',
152        '--exit-command',
153        action='append',
154        type='string',
155        metavar='CMD',
156        dest='exit_commands',
157        help='LLDB command interpreter commands to run once after the process has exited. This option can be specified more than once.',
158        default=[])
159    parser.add_option(
160        '-T',
161        '--no-threads',
162        action='store_false',
163        dest='show_threads',
164        help="Don't show threads when process stops.",
165        default=True)
166    parser.add_option(
167        '--ignore-errors',
168        action='store_false',
169        dest='stop_on_error',
170        help="Don't stop executing LLDB commands if the command returns an error. This applies to all of the LLDB command interpreter commands that get run for launch, stop, crash and exit.",
171        default=True)
172    parser.add_option(
173        '-n',
174        '--run-count',
175        type='int',
176        dest='run_count',
177        metavar='N',
178        help='How many times to run the process in case the process exits.',
179        default=1)
180    parser.add_option(
181        '-t',
182        '--event-timeout',
183        type='int',
184        dest='event_timeout',
185        metavar='SEC',
186        help='Specify the timeout in seconds to wait for process state change events.',
187        default=lldb.UINT32_MAX)
188    parser.add_option(
189        '-e',
190        '--environment',
191        action='append',
192        type='string',
193        metavar='ENV',
194        dest='env_vars',
195        help='Environment variables to set in the inferior process when launching a process.')
196    parser.add_option(
197        '-d',
198        '--working-dir',
199        type='string',
200        metavar='DIR',
201        dest='working_dir',
202        help='The the current working directory when launching a process.',
203        default=None)
204    parser.add_option(
205        '-p',
206        '--attach-pid',
207        type='int',
208        dest='attach_pid',
209        metavar='PID',
210        help='Specify a process to attach to by process ID.',
211        default=-1)
212    parser.add_option(
213        '-P',
214        '--attach-name',
215        type='string',
216        dest='attach_name',
217        metavar='PROCESSNAME',
218        help='Specify a process to attach to by name.',
219        default=None)
220    parser.add_option(
221        '-w',
222        '--attach-wait',
223        action='store_true',
224        dest='attach_wait',
225        help='Wait for the next process to launch when attaching to a process by name.',
226        default=False)
227    try:
228        (options, args) = parser.parse_args(argv)
229    except:
230        return
231
232    attach_info = None
233    launch_info = None
234    exe = None
235    if args:
236        exe = args.pop(0)
237        launch_info = lldb.SBLaunchInfo(args)
238        if options.env_vars:
239            launch_info.SetEnvironmentEntries(options.env_vars, True)
240        if options.working_dir:
241            launch_info.SetWorkingDirectory(options.working_dir)
242    elif options.attach_pid != -1:
243        if options.run_count == 1:
244            attach_info = lldb.SBAttachInfo(options.attach_pid)
245        else:
246            print("error: --run-count can't be used with the --attach-pid option")
247            sys.exit(1)
248    elif not options.attach_name is None:
249        if options.run_count == 1:
250            attach_info = lldb.SBAttachInfo(
251                options.attach_name, options.attach_wait)
252        else:
253            print("error: --run-count can't be used with the --attach-name option")
254            sys.exit(1)
255    else:
256        print('error: a program path for a program to debug and its arguments are required')
257        sys.exit(1)
258
259    # Create a new debugger instance
260    debugger = lldb.SBDebugger.Create()
261    debugger.SetAsync(True)
262    command_interpreter = debugger.GetCommandInterpreter()
263    # Create a target from a file and arch
264
265    if exe:
266        print("Creating a target for '%s'" % exe)
267    error = lldb.SBError()
268    target = debugger.CreateTarget(
269        exe, options.arch, options.platform, True, error)
270
271    if target:
272
273        # Set any breakpoints that were specified in the args if we are launching. We use the
274        # command line command to take advantage of the shorthand breakpoint
275        # creation
276        if launch_info and options.breakpoints:
277            for bp in options.breakpoints:
278                debugger.HandleCommand("_regexp-break %s" % (bp))
279            run_commands(command_interpreter, ['breakpoint list'])
280
281        for run_idx in range(options.run_count):
282            # Launch the process. Since we specified synchronous mode, we won't return
283            # from this function until we hit the breakpoint at main
284            error = lldb.SBError()
285
286            if launch_info:
287                if options.run_count == 1:
288                    print('Launching "%s"...' % (exe))
289                else:
290                    print('Launching "%s"... (launch %u of %u)' % (exe, run_idx + 1, options.run_count))
291
292                process = target.Launch(launch_info, error)
293            else:
294                if options.attach_pid != -1:
295                    print('Attaching to process %i...' % (options.attach_pid))
296                else:
297                    if options.attach_wait:
298                        print('Waiting for next to process named "%s" to launch...' % (options.attach_name))
299                    else:
300                        print('Attaching to existing process named "%s"...' % (options.attach_name))
301                process = target.Attach(attach_info, error)
302
303            # Make sure the launch went ok
304            if process and process.GetProcessID() != lldb.LLDB_INVALID_PROCESS_ID:
305
306                pid = process.GetProcessID()
307                print('Process is %i' % (pid))
308                if attach_info:
309                    # continue process if we attached as we won't get an
310                    # initial event
311                    process.Continue()
312
313                listener = debugger.GetListener()
314                # sign up for process state change events
315                stop_idx = 0
316                done = False
317                while not done:
318                    event = lldb.SBEvent()
319                    if listener.WaitForEvent(options.event_timeout, event):
320                        if lldb.SBProcess.EventIsProcessEvent(event):
321                            state = lldb.SBProcess.GetStateFromEvent(event)
322                            if state == lldb.eStateInvalid:
323                                # Not a state event
324                                print('process event = %s' % (event))
325                            else:
326                                print("process state changed event: %s" % (lldb.SBDebugger.StateAsCString(state)))
327                                if state == lldb.eStateStopped:
328                                    if stop_idx == 0:
329                                        if launch_info:
330                                            print("process %u launched" % (pid))
331                                            run_commands(
332                                                command_interpreter, ['breakpoint list'])
333                                        else:
334                                            print("attached to process %u" % (pid))
335                                            for m in target.modules:
336                                                print(m)
337                                            if options.breakpoints:
338                                                for bp in options.breakpoints:
339                                                    debugger.HandleCommand(
340                                                        "_regexp-break %s" % (bp))
341                                                run_commands(
342                                                    command_interpreter, ['breakpoint list'])
343                                        run_commands(
344                                            command_interpreter, options.launch_commands)
345                                    else:
346                                        if options.verbose:
347                                            print("process %u stopped" % (pid))
348                                        run_commands(
349                                            command_interpreter, options.stop_commands)
350                                    stop_idx += 1
351                                    print_threads(process, options)
352                                    print("continuing process %u" % (pid))
353                                    process.Continue()
354                                elif state == lldb.eStateExited:
355                                    exit_desc = process.GetExitDescription()
356                                    if exit_desc:
357                                        print("process %u exited with status %u: %s" % (pid, process.GetExitStatus(), exit_desc))
358                                    else:
359                                        print("process %u exited with status %u" % (pid, process.GetExitStatus()))
360                                    run_commands(
361                                        command_interpreter, options.exit_commands)
362                                    done = True
363                                elif state == lldb.eStateCrashed:
364                                    print("process %u crashed" % (pid))
365                                    print_threads(process, options)
366                                    run_commands(
367                                        command_interpreter, options.crash_commands)
368                                    done = True
369                                elif state == lldb.eStateDetached:
370                                    print("process %u detached" % (pid))
371                                    done = True
372                                elif state == lldb.eStateRunning:
373                                    # process is running, don't say anything,
374                                    # we will always get one of these after
375                                    # resuming
376                                    if options.verbose:
377                                        print("process %u resumed" % (pid))
378                                elif state == lldb.eStateUnloaded:
379                                    print("process %u unloaded, this shouldn't happen" % (pid))
380                                    done = True
381                                elif state == lldb.eStateConnected:
382                                    print("process connected")
383                                elif state == lldb.eStateAttaching:
384                                    print("process attaching")
385                                elif state == lldb.eStateLaunching:
386                                    print("process launching")
387                        else:
388                            print('event = %s' % (event))
389                    else:
390                        # timeout waiting for an event
391                        print("no process event for %u seconds, killing the process..." % (options.event_timeout))
392                        done = True
393                # Now that we are done dump the stdout and stderr
394                process_stdout = process.GetSTDOUT(1024)
395                if process_stdout:
396                    print("Process STDOUT:\n%s" % (process_stdout))
397                    while process_stdout:
398                        process_stdout = process.GetSTDOUT(1024)
399                        print(process_stdout)
400                process_stderr = process.GetSTDERR(1024)
401                if process_stderr:
402                    print("Process STDERR:\n%s" % (process_stderr))
403                    while process_stderr:
404                        process_stderr = process.GetSTDERR(1024)
405                        print(process_stderr)
406                process.Kill()  # kill the process
407            else:
408                if error:
409                    print(error)
410                else:
411                    if launch_info:
412                        print('error: launch failed')
413                    else:
414                        print('error: attach failed')
415
416    lldb.SBDebugger.Terminate()
417
418if __name__ == '__main__':
419    main(sys.argv[1:])
420