]> git.lizzy.rs Git - rust.git/blob - src/etc/lldb_batchmode.py
Auto merge of #29325 - alexcrichton:revert-trait-accessibility, r=nrc
[rust.git] / src / etc / lldb_batchmode.py
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
33 import time
34
35 # Set this to True for additional output
36 DEBUG_OUTPUT = False
37
38
39 def print_debug(s):
40     "Print something if DEBUG_OUTPUT is True"
41     global DEBUG_OUTPUT
42     if DEBUG_OUTPUT:
43         print("DEBUG: " + str(s))
44
45
46 def normalize_whitespace(s):
47     "Replace newlines, tabs, multiple spaces, etc with exactly one space"
48     return re.sub("\s+", " ", s)
49
50
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))
55
56     # Select the frame and the thread containing it
57     frame.thread.process.SetSelectedThread(frame.thread)
58     frame.thread.SetSelectedFrame(frame.idx)
59
60     # Returning True means that we actually want to stop at this breakpoint
61     return True
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
72
73 def execute_command(command_interpreter, command):
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())
110
111
112 def start_breakpoint_listener(target):
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)
139
140
141 def start_watchdog():
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()
157
158 ####################################################################################################
159 # ~main
160 ####################################################################################################
161
162 if len(sys.argv) != 3:
163     print("usage: python lldb_batchmode.py target-path script-path")
164     sys.exit(1)
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:
191     print("Could not create debugging target '" + target_path + "': " +
192           str(target_error) + ". Aborting.", file=sys.stderr)
193     sys.exit(1)
194
195
196 # Register the breakpoint callback for every breakpoint
197 start_breakpoint_listener(target)
198
199 command_interpreter = debugger.GetCommandInterpreter()
200
201 try:
202     script_file = open(script_path, 'r')
203
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)
212
213 except IOError as e:
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)
218 finally:
219     script_file.close()