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