1 # This script allows to use LLDB in a way similar to GDB's batch mode. That is, given a text file
2 # containing LLDB commands (one command per line), this script will execute the commands one after
4 # LLDB also has the -s and -S commandline options which also execute a list of commands from a text
5 # file. However, this command are execute `immediately`: the command of a `run` or `continue`
6 # command will be executed immediately after the `run` or `continue`, without waiting for the next
7 # breakpoint to be hit. This a command sequence like the following will not yield reliable results:
13 # Most of the time the `print` command will be executed while the program is still running will thus
14 # fail. Using this Python script, the above will work as expected.
16 from __future__ import print_function
26 except ModuleNotFoundError:
27 # The `thread` module was renamed to `_thread` in Python 3.
28 import _thread as thread
30 # Set this to True for additional output
35 """Print something if DEBUG_OUTPUT is True"""
38 print("DEBUG: " + str(s))
41 def normalize_whitespace(s):
42 """Replace newlines, tabs, multiple spaces, etc with exactly one space"""
43 return re.sub("\s+", " ", s)
46 def breakpoint_callback(frame, bp_loc, dict):
47 """This callback is registered with every breakpoint and makes sure that the
48 frame containing the breakpoint location is selected"""
49 print("Hit breakpoint " + str(bp_loc))
51 # Select the frame and the thread containing it
52 frame.thread.process.SetSelectedThread(frame.thread)
53 frame.thread.SetSelectedFrame(frame.idx)
55 # Returning True means that we actually want to stop at this breakpoint
59 # This is a list of breakpoints that are not registered with the breakpoint callback. The list is
60 # populated by the breakpoint listener and checked/emptied whenever a command has been executed
63 # This set contains all breakpoint ids that have already been registered with a callback, and is
64 # used to avoid hooking callbacks into breakpoints more than once
65 registered_breakpoints = set()
68 def execute_command(command_interpreter, command):
69 """Executes a single CLI command"""
70 global new_breakpoints
71 global registered_breakpoints
73 res = lldb.SBCommandReturnObject()
75 command_interpreter.HandleCommand(command, res)
79 print(normalize_whitespace(res.GetOutput() or ''), end='\n')
81 # If the command introduced any breakpoints, make sure to register
82 # them with the breakpoint
84 while len(new_breakpoints) > 0:
86 breakpoint_id = new_breakpoints.pop()
88 if breakpoint_id in registered_breakpoints:
89 print_debug("breakpoint with id %s is already registered. Ignoring." %
92 print_debug("registering breakpoint callback, id = " + str(breakpoint_id))
93 callback_command = ("breakpoint command add -F breakpoint_callback " +
95 command_interpreter.HandleCommand(callback_command, res)
97 print_debug("successfully registered breakpoint callback, id = " +
99 registered_breakpoints.add(breakpoint_id)
101 print("Error while trying to register breakpoint callback, id = " +
104 print(res.GetError())
107 def start_breakpoint_listener(target):
108 """Listens for breakpoints being added and adds new ones to the callback
110 listener = lldb.SBListener("breakpoint listener")
113 event = lldb.SBEvent()
116 if listener.WaitForEvent(120, event):
117 if lldb.SBBreakpoint.EventIsBreakpointEvent(event) and \
118 lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event) == \
119 lldb.eBreakpointEventTypeAdded:
120 global new_breakpoints
121 breakpoint = lldb.SBBreakpoint.GetBreakpointFromEvent(event)
122 print_debug("breakpoint added, id = " + str(breakpoint.id))
123 new_breakpoints.append(breakpoint.id)
125 print_debug("breakpoint listener shutting down")
127 # Start the listener and let it run as a daemon
128 listener_thread = threading.Thread(target=listen)
129 listener_thread.daemon = True
130 listener_thread.start()
132 # Register the listener with the target
133 target.GetBroadcaster().AddListener(listener, lldb.SBTarget.eBroadcastBitBreakpointChanged)
136 def start_watchdog():
137 """Starts a watchdog thread that will terminate the process after a certain
139 watchdog_start_time = time.clock()
140 watchdog_max_time = watchdog_start_time + 30
143 while time.clock() < watchdog_max_time:
145 print("TIMEOUT: lldb_batchmode.py has been running for too long. Aborting!")
146 thread.interrupt_main()
148 # Start the listener and let it run as a daemon
149 watchdog_thread = threading.Thread(target=watchdog)
150 watchdog_thread.daemon = True
151 watchdog_thread.start()
153 ####################################################################################################
155 ####################################################################################################
157 if len(sys.argv) != 3:
158 print("usage: python lldb_batchmode.py target-path script-path")
161 target_path = sys.argv[1]
162 script_path = sys.argv[2]
164 print("LLDB batch-mode script")
165 print("----------------------")
166 print("Debugger commands script is '%s'." % script_path)
167 print("Target executable is '%s'." % target_path)
168 print("Current working directory is '%s'" % os.getcwd())
170 # Start the timeout watchdog
173 # Create a new debugger instance
174 debugger = lldb.SBDebugger.Create()
176 # When we step or continue, don't return from the function until the process
177 # stops. We do this by setting the async mode to false.
178 debugger.SetAsync(False)
180 # Create a target from a file and arch
181 print("Creating a target for '%s'" % target_path)
182 target_error = lldb.SBError()
183 target = debugger.CreateTarget(target_path, None, None, True, target_error)
186 print("Could not create debugging target '" + target_path + "': " +
187 str(target_error) + ". Aborting.", file=sys.stderr)
191 # Register the breakpoint callback for every breakpoint
192 start_breakpoint_listener(target)
194 command_interpreter = debugger.GetCommandInterpreter()
197 script_file = open(script_path, 'r')
199 for line in script_file:
200 command = line.strip()
201 if command == "run" or command == "r" or re.match("^process\s+launch.*", command):
202 # Before starting to run the program, let the thread sleep a bit, so all
203 # breakpoint added events can be processed
206 execute_command(command_interpreter, command)
209 print("Could not read debugging script '%s'." % script_path, file=sys.stderr)
210 print(e, file=sys.stderr)
211 print("Aborting.", file=sys.stderr)