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