1 # Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2 # file at the top-level directory of this distribution and at
3 # http://rust-lang.org/COPYRIGHT.
5 # Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 # http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 # <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 # option. This file may not be copied, modified, or distributed
9 # except according to those terms.
11 # This script allows to use LLDB in a way similar to GDB's batch mode. That is, given a text file
12 # containing LLDB commands (one command per line), this script will execute the commands one after
14 # LLDB also has the -s and -S commandline options which also execute a list of commands from a text
15 # file. However, this command are execute `immediately`: a the command of a `run` or `continue`
16 # command will be executed immediately after the `run` or `continue`, without waiting for the next
17 # breakpoint to be hit. This a command sequence like the following will not yield reliable results:
23 # Most of the time the `print` command will be executed while the program is still running will thus
24 # fail. Using this Python script, the above will work as expected.
26 from __future__ import print_function
36 # Set this to True for additional output
40 "Print something if DEBUG_OUTPUT is True"
43 print("DEBUG: " + str(s))
46 def normalize_whitespace(s):
47 "Replace newlines, tabs, multiple spaces, etc with exactly one space"
48 return re.sub("\s+", " ", s)
51 # This callback is registered with every breakpoint and makes sure that the frame containing the
52 # breakpoint location is selected
53 def breakpoint_callback(frame, bp_loc, dict):
54 "Called whenever a breakpoint is hit"
55 print("Hit breakpoint " + str(bp_loc))
57 # Select the frame and the thread containing it
58 frame.thread.process.SetSelectedThread(frame.thread)
59 frame.thread.SetSelectedFrame(frame.idx)
61 # Returning True means that we actually want to stop at this breakpoint
65 # This is a list of breakpoints that are not registered with the breakpoint callback. The list is
66 # populated by the breakpoint listener and checked/emptied whenever a command has been executed
69 # This set contains all breakpoint ids that have already been registered with a callback, and is
70 # used to avoid hooking callbacks into breakpoints more than once
71 registered_breakpoints = set()
73 def execute_command(command_interpreter, command):
74 "Executes a single CLI command"
75 global new_breakpoints
76 global registered_breakpoints
78 res = lldb.SBCommandReturnObject()
80 command_interpreter.HandleCommand(command, res)
84 print(normalize_whitespace(res.GetOutput()), end = '\n')
86 # If the command introduced any breakpoints, make sure to register them with the breakpoint
88 while len(new_breakpoints) > 0:
90 breakpoint_id = new_breakpoints.pop()
92 if breakpoint_id in registered_breakpoints:
93 print_debug("breakpoint with id %s is already registered. Ignoring." % str(breakpoint_id))
95 print_debug("registering breakpoint callback, id = " + str(breakpoint_id))
96 callback_command = "breakpoint command add -F breakpoint_callback " + str(breakpoint_id)
97 command_interpreter.HandleCommand(callback_command, res)
99 print_debug("successfully registered breakpoint callback, id = " + str(breakpoint_id))
100 registered_breakpoints.add(breakpoint_id)
102 print("Error while trying to register breakpoint callback, id = " + str(breakpoint_id))
104 print(res.GetError())
107 def start_breakpoint_listener(target):
108 "Listens for breakpoints being added and adds new ones to the callback registration list"
109 listener = lldb.SBListener("breakpoint listener")
112 event = lldb.SBEvent()
115 if listener.WaitForEvent(120, event):
116 if lldb.SBBreakpoint.EventIsBreakpointEvent(event) and \
117 lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event) == \
118 lldb.eBreakpointEventTypeAdded:
119 global new_breakpoints
120 breakpoint = lldb.SBBreakpoint.GetBreakpointFromEvent(event)
121 print_debug("breakpoint added, id = " + str(breakpoint.id))
122 new_breakpoints.append(breakpoint.id)
124 print_debug("breakpoint listener shutting down")
126 # Start the listener and let it run as a daemon
127 listener_thread = threading.Thread(target = listen)
128 listener_thread.daemon = True
129 listener_thread.start()
131 # Register the listener with the target
132 target.GetBroadcaster().AddListener(listener, lldb.SBTarget.eBroadcastBitBreakpointChanged)
135 def start_watchdog():
136 "Starts a watchdog thread that will terminate the process after a certain period of time"
137 watchdog_start_time = time.clock()
138 watchdog_max_time = watchdog_start_time + 30
141 while time.clock() < watchdog_max_time:
143 print("TIMEOUT: lldb_batchmode.py has been running for too long. Aborting!")
144 thread.interrupt_main()
146 # Start the listener and let it run as a daemon
147 watchdog_thread = threading.Thread(target = watchdog)
148 watchdog_thread.daemon = True
149 watchdog_thread.start()
151 ####################################################################################################
153 ####################################################################################################
155 if len(sys.argv) != 3:
156 print("usage: python lldb_batchmode.py target-path script-path")
159 target_path = sys.argv[1]
160 script_path = sys.argv[2]
162 print("LLDB batch-mode script")
163 print("----------------------")
164 print("Debugger commands script is '%s'." % script_path)
165 print("Target executable is '%s'." % target_path)
166 print("Current working directory is '%s'" % os.getcwd())
168 # Start the timeout watchdog
171 # Create a new debugger instance
172 debugger = lldb.SBDebugger.Create()
174 # When we step or continue, don't return from the function until the process
175 # stops. We do this by setting the async mode to false.
176 debugger.SetAsync(False)
178 # Create a target from a file and arch
179 print("Creating a target for '%s'" % target_path)
180 target_error = lldb.SBError()
181 target = debugger.CreateTarget(target_path, None, None, True, target_error)
184 print("Could not create debugging target '" + target_path + "': " + str(target_error) +
185 ". Aborting.", file=sys.stderr)
189 # Register the breakpoint callback for every breakpoint
190 start_breakpoint_listener(target)
192 command_interpreter = debugger.GetCommandInterpreter()
195 script_file = open(script_path, 'r')
197 for line in script_file:
198 command = line.strip()
199 if command == "run" or command == "r" or re.match("^process\s+launch.*", command):
200 # Before starting to run the program, let the thread sleep a bit, so all
201 # breakpoint added events can be processed
204 execute_command(command_interpreter, command)
207 print("Could not read debugging script '%s'." % script_path, file = sys.stderr)
208 print(e, file = sys.stderr)
209 print("Aborting.", file = sys.stderr)