]> git.proxmox.com Git - rustc.git/blame - src/etc/lldb_batchmode.py
Imported Upstream version 1.9.0+dfsg1
[rustc.git] / src / etc / lldb_batchmode.py
CommitLineData
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
26from __future__ import print_function
27import lldb
28import os
29import sys
30import threading
31import thread
32import re
1a4d82fc
JJ
33import time
34
35# Set this to True for additional output
36DEBUG_OUTPUT = False
37
85aaf69f 38
1a4d82fc 39def 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
46def 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 51def 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
66new_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
70registered_breakpoints = set()
71
85aaf69f 72
1a4d82fc 73def 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
112def 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
141def 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
162if len(sys.argv) != 3:
85aaf69f
SL
163 print("usage: python lldb_batchmode.py target-path script-path")
164 sys.exit(1)
1a4d82fc
JJ
165
166target_path = sys.argv[1]
167script_path = sys.argv[2]
168
169print("LLDB batch-mode script")
170print("----------------------")
171print("Debugger commands script is '%s'." % script_path)
172print("Target executable is '%s'." % target_path)
173print("Current working directory is '%s'" % os.getcwd())
174
175# Start the timeout watchdog
176start_watchdog()
177
178# Create a new debugger instance
179debugger = 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.
183debugger.SetAsync(False)
184
185# Create a target from a file and arch
186print("Creating a target for '%s'" % target_path)
187target_error = lldb.SBError()
188target = debugger.CreateTarget(target_path, None, None, True, target_error)
189
190if 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
197start_breakpoint_listener(target)
198
199command_interpreter = debugger.GetCommandInterpreter()
200
201try:
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
213except 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 218finally:
85aaf69f 219 script_file.close()