]> git.lizzy.rs Git - rust.git/blob - src/etc/lldb_batchmode.py
librustc: Don't try to perform the magical
[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 re
32 import atexit
33
34 # Terminate the debugger
35 atexit.register(lambda: lldb.SBDebugger.Terminate())
36
37 # Set this to True for additional output
38 DEBUG_OUTPUT = False
39
40 def print_debug(s):
41   "Print something if DEBUG_OUTPUT is True"
42   global DEBUG_OUTPUT
43   if DEBUG_OUTPUT:
44     print("DEBUG: " + str(s))
45
46
47 def normalize_whitespace(s):
48   "Replace newlines, tabs, multiple spaces, etc with exactly one space"
49   return re.sub("\s+", " ", s)
50
51
52 # This callback is registered with every breakpoint and makes sure that the frame containing the
53 # breakpoint location is selected
54 def breakpoint_callback(frame, bp_loc, dict):
55   "Called whenever a breakpoint is hit"
56   print("Hit breakpoint " + str(bp_loc))
57
58   # Select the frame and the thread containing it
59   frame.thread.process.SetSelectedThread(frame.thread)
60   frame.thread.SetSelectedFrame(frame.idx)
61
62   # Returning True means that we actually want to stop at this breakpoint
63   return True
64
65
66 # This is a list of breakpoints that are not registered with the breakpoint callback. The list is
67 # populated by the breakpoint listener and checked/emptied whenever a command has been executed
68 new_breakpoints = []
69
70 # This set contains all breakpoint ids that have already been registered with a callback, and is
71 # used to avoid hooking callbacks into breakpoints more than once
72 registered_breakpoints = set()
73
74 def execute_command(command_interpreter, command):
75   "Executes a single CLI command"
76   global new_breakpoints
77   global registered_breakpoints
78
79   res = lldb.SBCommandReturnObject()
80   print(command)
81   command_interpreter.HandleCommand(command, res)
82
83   if res.Succeeded():
84       if res.HasResult():
85           print(normalize_whitespace(res.GetOutput()), end = '\n')
86
87       # If the command introduced any breakpoints, make sure to register 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." % str(breakpoint_id))
95         else:
96           print_debug("registering breakpoint callback, id = " + str(breakpoint_id))
97           callback_command = "breakpoint command add -F breakpoint_callback " + str(breakpoint_id)
98           command_interpreter.HandleCommand(callback_command, res)
99           if res.Succeeded():
100             print_debug("successfully registered breakpoint callback, id = " + str(breakpoint_id))
101             registered_breakpoints.add(breakpoint_id)
102           else:
103             print("Error while trying to register breakpoint callback, id = " + str(breakpoint_id))
104   else:
105       print(res.GetError())
106
107
108 def start_breakpoint_listener(target):
109   "Listens for breakpoints being added and adds new ones to the callback registration list"
110   listener = lldb.SBListener("breakpoint listener")
111
112   def listen():
113     event = lldb.SBEvent()
114     try:
115       while True:
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)
124     except:
125       print_debug("breakpoint listener shutting down")
126
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()
131
132   # Register the listener with the target
133   target.GetBroadcaster().AddListener(listener, lldb.SBTarget.eBroadcastBitBreakpointChanged)
134
135
136 ####################################################################################################
137 # ~main
138 ####################################################################################################
139
140 if len(sys.argv) != 3:
141   print("usage: python lldb_batchmode.py target-path script-path")
142   sys.exit(1)
143
144 target_path = sys.argv[1]
145 script_path = sys.argv[2]
146
147
148 # Create a new debugger instance
149 debugger = lldb.SBDebugger.Create()
150
151 # When we step or continue, don't return from the function until the process
152 # stops. We do this by setting the async mode to false.
153 debugger.SetAsync(False)
154
155 # Create a target from a file and arch
156 print("Creating a target for '%s'" % target_path)
157 target = debugger.CreateTargetWithFileAndArch(target_path, lldb.LLDB_ARCH_DEFAULT)
158
159 if not target:
160   print("Could not create debugging target '" + target_path + "'. Aborting.", file=sys.stderr)
161   sys.exit(1)
162
163
164 # Register the breakpoint callback for every breakpoint
165 start_breakpoint_listener(target)
166
167 command_interpreter = debugger.GetCommandInterpreter()
168
169 try:
170   script_file = open(script_path, 'r')
171
172   for line in script_file:
173     command = line.strip()
174     if command != '':
175       execute_command(command_interpreter, command)
176
177 except IOError as e:
178   print("Could not read debugging script '%s'." % script_path, file = sys.stderr)
179   print(e, file = sys.stderr)
180   print("Aborting.", file = sys.stderr)
181   sys.exit(1)
182 finally:
183   script_file.close()
184