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
35 # 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 def breakpoint_callback(frame
, bp_loc
, dict):
52 """This callback is registered with every breakpoint and makes sure that the
53 frame containing the breakpoint location is selected"""
54 print("Hit breakpoint " + str(bp_loc
))
56 # Select the frame and the thread containing it
57 frame
.thread
.process
.SetSelectedThread(frame
.thread
)
58 frame
.thread
.SetSelectedFrame(frame
.idx
)
60 # Returning True means that we actually want to stop at this breakpoint
64 # This is a list of breakpoints that are not registered with the breakpoint callback. The list is
65 # populated by the breakpoint listener and checked/emptied whenever a command has been executed
68 # This set contains all breakpoint ids that have already been registered with a callback, and is
69 # used to avoid hooking callbacks into breakpoints more than once
70 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
87 # them with the breakpoint
89 while len(new_breakpoints
) > 0:
91 breakpoint_id
= new_breakpoints
.pop()
93 if breakpoint_id
in registered_breakpoints
:
94 print_debug("breakpoint with id %s is already registered. Ignoring." %
97 print_debug("registering breakpoint callback, id = " + str(breakpoint_id
))
98 callback_command
= ("breakpoint command add -F breakpoint_callback " +
100 command_interpreter
.HandleCommand(callback_command
, res
)
102 print_debug("successfully registered breakpoint callback, id = " +
104 registered_breakpoints
.add(breakpoint_id
)
106 print("Error while trying to register breakpoint callback, id = " +
109 print(res
.GetError())
112 def start_breakpoint_listener(target
):
113 """Listens for breakpoints being added and adds new ones to the callback
115 listener
= lldb
.SBListener("breakpoint listener")
118 event
= lldb
.SBEvent()
121 if listener
.WaitForEvent(120, event
):
122 if lldb
.SBBreakpoint
.EventIsBreakpointEvent(event
) and \
123 lldb
.SBBreakpoint
.GetBreakpointEventTypeFromEvent(event
) == \
124 lldb
.eBreakpointEventTypeAdded
:
125 global new_breakpoints
126 breakpoint
= lldb
.SBBreakpoint
.GetBreakpointFromEvent(event
)
127 print_debug("breakpoint added, id = " + str(breakpoint
.id))
128 new_breakpoints
.append(breakpoint
.id)
130 print_debug("breakpoint listener shutting down")
132 # Start the listener and let it run as a daemon
133 listener_thread
= threading
.Thread(target
=listen
)
134 listener_thread
.daemon
= True
135 listener_thread
.start()
137 # Register the listener with the target
138 target
.GetBroadcaster().AddListener(listener
, lldb
.SBTarget
.eBroadcastBitBreakpointChanged
)
141 def start_watchdog():
142 """Starts a watchdog thread that will terminate the process after a certain
144 watchdog_start_time
= time
.clock()
145 watchdog_max_time
= watchdog_start_time
+ 30
148 while time
.clock() < watchdog_max_time
:
150 print("TIMEOUT: lldb_batchmode.py has been running for too long. Aborting!")
151 thread
.interrupt_main()
153 # Start the listener and let it run as a daemon
154 watchdog_thread
= threading
.Thread(target
=watchdog
)
155 watchdog_thread
.daemon
= True
156 watchdog_thread
.start()
158 ####################################################################################################
160 ####################################################################################################
162 if len(sys
.argv
) != 3:
163 print("usage: python lldb_batchmode.py target-path script-path")
166 target_path
= sys
.argv
[1]
167 script_path
= sys
.argv
[2]
169 print("LLDB batch-mode script")
170 print("----------------------")
171 print("Debugger commands script is '%s'." % script_path
)
172 print("Target executable is '%s'." % target_path
)
173 print("Current working directory is '%s'" % os
.getcwd())
175 # Start the timeout watchdog
178 # Create a new debugger instance
179 debugger
= lldb
.SBDebugger
.Create()
181 # When we step or continue, don't return from the function until the process
182 # stops. We do this by setting the async mode to false.
183 debugger
.SetAsync(False)
185 # Create a target from a file and arch
186 print("Creating a target for '%s'" % target_path
)
187 target_error
= lldb
.SBError()
188 target
= debugger
.CreateTarget(target_path
, None, None, True, target_error
)
191 print("Could not create debugging target '" + target_path
+ "': " +
192 str(target_error
) + ". Aborting.", file=sys
.stderr
)
196 # Register the breakpoint callback for every breakpoint
197 start_breakpoint_listener(target
)
199 command_interpreter
= debugger
.GetCommandInterpreter()
202 script_file
= open(script_path
, 'r')
204 for line
in script_file
:
205 command
= line
.strip()
206 if command
== "run" or command
== "r" or re
.match("^process\s+launch.*", command
):
207 # Before starting to run the program, let the thread sleep a bit, so all
208 # breakpoint added events can be processed
211 execute_command(command_interpreter
, command
)
214 print("Could not read debugging script '%s'." % script_path
, file=sys
.stderr
)
215 print(e
, file=sys
.stderr
)
216 print("Aborting.", file=sys
.stderr
)