]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
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. | |
4 | # | |
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. | |
10 | ||
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 | |
13 | # the other. | |
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: | |
18 | # | |
19 | # break 11 | |
20 | # run | |
21 | # print x | |
22 | # | |
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. | |
25 | ||
26 | from __future__ import print_function | |
27 | import lldb | |
28 | import os | |
29 | import sys | |
30 | import threading | |
31 | import thread | |
32 | import re | |
1a4d82fc JJ |
33 | import time |
34 | ||
35 | # Set this to True for additional output | |
36 | DEBUG_OUTPUT = False | |
37 | ||
85aaf69f | 38 | |
1a4d82fc | 39 | def print_debug(s): |
85aaf69f SL |
40 | "Print something if DEBUG_OUTPUT is True" |
41 | global DEBUG_OUTPUT | |
42 | if DEBUG_OUTPUT: | |
43 | print("DEBUG: " + str(s)) | |
1a4d82fc JJ |
44 | |
45 | ||
46 | def normalize_whitespace(s): | |
85aaf69f SL |
47 | "Replace newlines, tabs, multiple spaces, etc with exactly one space" |
48 | return re.sub("\s+", " ", s) | |
1a4d82fc JJ |
49 | |
50 | ||
1a4d82fc | 51 | def breakpoint_callback(frame, bp_loc, dict): |
85aaf69f SL |
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)) | |
1a4d82fc | 55 | |
85aaf69f SL |
56 | # Select the frame and the thread containing it |
57 | frame.thread.process.SetSelectedThread(frame.thread) | |
58 | frame.thread.SetSelectedFrame(frame.idx) | |
1a4d82fc | 59 | |
85aaf69f SL |
60 | # Returning True means that we actually want to stop at this breakpoint |
61 | return True | |
1a4d82fc JJ |
62 | |
63 | ||
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 | |
66 | new_breakpoints = [] | |
67 | ||
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() | |
71 | ||
85aaf69f | 72 | |
1a4d82fc | 73 | def execute_command(command_interpreter, command): |
85aaf69f SL |
74 | "Executes a single CLI command" |
75 | global new_breakpoints | |
76 | global registered_breakpoints | |
77 | ||
78 | res = lldb.SBCommandReturnObject() | |
79 | print(command) | |
80 | command_interpreter.HandleCommand(command, res) | |
81 | ||
82 | if res.Succeeded(): | |
83 | if res.HasResult(): | |
84 | print(normalize_whitespace(res.GetOutput()), end='\n') | |
85 | ||
86 | # If the command introduced any breakpoints, make sure to register | |
87 | # them with the breakpoint | |
88 | # callback | |
89 | while len(new_breakpoints) > 0: | |
90 | res.Clear() | |
91 | breakpoint_id = new_breakpoints.pop() | |
92 | ||
93 | if breakpoint_id in registered_breakpoints: | |
94 | print_debug("breakpoint with id %s is already registered. Ignoring." % | |
95 | str(breakpoint_id)) | |
96 | else: | |
97 | print_debug("registering breakpoint callback, id = " + str(breakpoint_id)) | |
98 | callback_command = ("breakpoint command add -F breakpoint_callback " + | |
99 | str(breakpoint_id)) | |
100 | command_interpreter.HandleCommand(callback_command, res) | |
101 | if res.Succeeded(): | |
102 | print_debug("successfully registered breakpoint callback, id = " + | |
103 | str(breakpoint_id)) | |
104 | registered_breakpoints.add(breakpoint_id) | |
105 | else: | |
106 | print("Error while trying to register breakpoint callback, id = " + | |
107 | str(breakpoint_id)) | |
108 | else: | |
109 | print(res.GetError()) | |
1a4d82fc JJ |
110 | |
111 | ||
112 | def start_breakpoint_listener(target): | |
85aaf69f SL |
113 | """Listens for breakpoints being added and adds new ones to the callback |
114 | registration list""" | |
115 | listener = lldb.SBListener("breakpoint listener") | |
116 | ||
117 | def listen(): | |
118 | event = lldb.SBEvent() | |
119 | try: | |
120 | while True: | |
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) | |
129 | except: | |
130 | print_debug("breakpoint listener shutting down") | |
131 | ||
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() | |
136 | ||
137 | # Register the listener with the target | |
138 | target.GetBroadcaster().AddListener(listener, lldb.SBTarget.eBroadcastBitBreakpointChanged) | |
1a4d82fc JJ |
139 | |
140 | ||
141 | def start_watchdog(): | |
85aaf69f SL |
142 | """Starts a watchdog thread that will terminate the process after a certain |
143 | period of time""" | |
144 | watchdog_start_time = time.clock() | |
145 | watchdog_max_time = watchdog_start_time + 30 | |
146 | ||
147 | def watchdog(): | |
148 | while time.clock() < watchdog_max_time: | |
149 | time.sleep(1) | |
150 | print("TIMEOUT: lldb_batchmode.py has been running for too long. Aborting!") | |
151 | thread.interrupt_main() | |
152 | ||
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() | |
1a4d82fc JJ |
157 | |
158 | #################################################################################################### | |
159 | # ~main | |
160 | #################################################################################################### | |
161 | ||
162 | if len(sys.argv) != 3: | |
85aaf69f SL |
163 | print("usage: python lldb_batchmode.py target-path script-path") |
164 | sys.exit(1) | |
1a4d82fc JJ |
165 | |
166 | target_path = sys.argv[1] | |
167 | script_path = sys.argv[2] | |
168 | ||
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()) | |
174 | ||
175 | # Start the timeout watchdog | |
176 | start_watchdog() | |
177 | ||
178 | # Create a new debugger instance | |
179 | debugger = lldb.SBDebugger.Create() | |
180 | ||
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) | |
184 | ||
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) | |
189 | ||
190 | if not target: | |
85aaf69f SL |
191 | print("Could not create debugging target '" + target_path + "': " + |
192 | str(target_error) + ". Aborting.", file=sys.stderr) | |
193 | sys.exit(1) | |
1a4d82fc JJ |
194 | |
195 | ||
196 | # Register the breakpoint callback for every breakpoint | |
197 | start_breakpoint_listener(target) | |
198 | ||
199 | command_interpreter = debugger.GetCommandInterpreter() | |
200 | ||
201 | try: | |
85aaf69f | 202 | script_file = open(script_path, 'r') |
1a4d82fc | 203 | |
85aaf69f SL |
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 | |
209 | time.sleep(0.5) | |
210 | if command != '': | |
211 | execute_command(command_interpreter, command) | |
1a4d82fc JJ |
212 | |
213 | except IOError as e: | |
85aaf69f SL |
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) | |
217 | sys.exit(1) | |
1a4d82fc | 218 | finally: |
85aaf69f | 219 | script_file.close() |