]> git.lizzy.rs Git - rust.git/blob - src/etc/lldb_batchmode.py
Rollup merge of #67642 - Mark-Simulacrum:relax-bounds, r=Amanieu
[rust.git] / src / etc / lldb_batchmode.py
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
3 # the other.
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:
8 #
9 #   break 11
10 #   run
11 #   print x
12 #
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.
15
16 from __future__ import print_function
17 import lldb
18 import os
19 import sys
20 import threading
21 import re
22 import time
23
24 try:
25     import thread
26 except ModuleNotFoundError:
27     # The `thread` module was renamed to `_thread` in Python 3.
28     import _thread as thread
29
30 # Set this to True for additional output
31 DEBUG_OUTPUT = False
32
33
34 def print_debug(s):
35     """Print something if DEBUG_OUTPUT is True"""
36     global DEBUG_OUTPUT
37     if DEBUG_OUTPUT:
38         print("DEBUG: " + str(s))
39
40
41 def normalize_whitespace(s):
42     """Replace newlines, tabs, multiple spaces, etc with exactly one space"""
43     return re.sub("\s+", " ", s)
44
45
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
50     # HACK(eddyb) print a newline to avoid continuing an unfinished line.
51     print("")
52     print("Hit breakpoint " + str(bp_loc))
53
54     # Select the frame and the thread containing it
55     frame.thread.process.SetSelectedThread(frame.thread)
56     frame.thread.SetSelectedFrame(frame.idx)
57
58     # Returning True means that we actually want to stop at this breakpoint
59     return True
60
61
62 # This is a list of breakpoints that are not registered with the breakpoint callback. The list is
63 # populated by the breakpoint listener and checked/emptied whenever a command has been executed
64 new_breakpoints = []
65
66 # This set contains all breakpoint ids that have already been registered with a callback, and is
67 # used to avoid hooking callbacks into breakpoints more than once
68 registered_breakpoints = set()
69
70
71 def execute_command(command_interpreter, command):
72     """Executes a single CLI command"""
73     global new_breakpoints
74     global registered_breakpoints
75
76     res = lldb.SBCommandReturnObject()
77     print(command)
78     command_interpreter.HandleCommand(command, res)
79
80     if res.Succeeded():
81         if res.HasResult():
82             print(normalize_whitespace(res.GetOutput() or ''), end='\n')
83
84         # If the command introduced any breakpoints, make sure to register
85         # them with the breakpoint
86         # callback
87         while len(new_breakpoints) > 0:
88             res.Clear()
89             breakpoint_id = new_breakpoints.pop()
90
91             if breakpoint_id in registered_breakpoints:
92                 print_debug("breakpoint with id %s is already registered. Ignoring." %
93                             str(breakpoint_id))
94             else:
95                 print_debug("registering breakpoint callback, id = " + str(breakpoint_id))
96                 callback_command = ("breakpoint command add -F breakpoint_callback " +
97                                     str(breakpoint_id))
98                 command_interpreter.HandleCommand(callback_command, res)
99                 if res.Succeeded():
100                     print_debug("successfully registered breakpoint callback, id = " +
101                                 str(breakpoint_id))
102                     registered_breakpoints.add(breakpoint_id)
103                 else:
104                     print("Error while trying to register breakpoint callback, id = " +
105                           str(breakpoint_id))
106     else:
107         print(res.GetError())
108
109
110 def start_breakpoint_listener(target):
111     """Listens for breakpoints being added and adds new ones to the callback
112     registration list"""
113     listener = lldb.SBListener("breakpoint listener")
114
115     def listen():
116         event = lldb.SBEvent()
117         try:
118             while True:
119                 if listener.WaitForEvent(120, event):
120                     if lldb.SBBreakpoint.EventIsBreakpointEvent(event) and \
121                             lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event) == \
122                             lldb.eBreakpointEventTypeAdded:
123                         global new_breakpoints
124                         breakpoint = lldb.SBBreakpoint.GetBreakpointFromEvent(event)
125                         print_debug("breakpoint added, id = " + str(breakpoint.id))
126                         new_breakpoints.append(breakpoint.id)
127         except:
128             print_debug("breakpoint listener shutting down")
129
130     # Start the listener and let it run as a daemon
131     listener_thread = threading.Thread(target=listen)
132     listener_thread.daemon = True
133     listener_thread.start()
134
135     # Register the listener with the target
136     target.GetBroadcaster().AddListener(listener, lldb.SBTarget.eBroadcastBitBreakpointChanged)
137
138
139 def start_watchdog():
140     """Starts a watchdog thread that will terminate the process after a certain
141     period of time"""
142     watchdog_start_time = time.clock()
143     watchdog_max_time = watchdog_start_time + 30
144
145     def watchdog():
146         while time.clock() < watchdog_max_time:
147             time.sleep(1)
148         print("TIMEOUT: lldb_batchmode.py has been running for too long. Aborting!")
149         thread.interrupt_main()
150
151     # Start the listener and let it run as a daemon
152     watchdog_thread = threading.Thread(target=watchdog)
153     watchdog_thread.daemon = True
154     watchdog_thread.start()
155
156 ####################################################################################################
157 # ~main
158 ####################################################################################################
159
160
161 if len(sys.argv) != 3:
162     print("usage: python lldb_batchmode.py target-path script-path")
163     sys.exit(1)
164
165 target_path = sys.argv[1]
166 script_path = sys.argv[2]
167
168 print("LLDB batch-mode script")
169 print("----------------------")
170 print("Debugger commands script is '%s'." % script_path)
171 print("Target executable is '%s'." % target_path)
172 print("Current working directory is '%s'" % os.getcwd())
173
174 # Start the timeout watchdog
175 start_watchdog()
176
177 # Create a new debugger instance
178 debugger = lldb.SBDebugger.Create()
179
180 # When we step or continue, don't return from the function until the process
181 # stops. We do this by setting the async mode to false.
182 debugger.SetAsync(False)
183
184 # Create a target from a file and arch
185 print("Creating a target for '%s'" % target_path)
186 target_error = lldb.SBError()
187 target = debugger.CreateTarget(target_path, None, None, True, target_error)
188
189 if not target:
190     print("Could not create debugging target '" + target_path + "': " +
191           str(target_error) + ". Aborting.", file=sys.stderr)
192     sys.exit(1)
193
194
195 # Register the breakpoint callback for every breakpoint
196 start_breakpoint_listener(target)
197
198 command_interpreter = debugger.GetCommandInterpreter()
199
200 try:
201     script_file = open(script_path, 'r')
202
203     for line in script_file:
204         command = line.strip()
205         if command == "run" or command == "r" or re.match("^process\s+launch.*", command):
206             # Before starting to run the program, let the thread sleep a bit, so all
207             # breakpoint added events can be processed
208             time.sleep(0.5)
209         if command != '':
210             execute_command(command_interpreter, command)
211
212 except IOError as e:
213     print("Could not read debugging script '%s'." % script_path, file=sys.stderr)
214     print(e, file=sys.stderr)
215     print("Aborting.", file=sys.stderr)
216     sys.exit(1)
217 finally:
218     debugger.Terminate()
219     script_file.close()