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