Skip to content

Commit 38d1474

Browse files
committed
Rework debugger init script handling to handle LLDB quirks.
The current hack of `script eval(...)` for LLDB requires our script to use `lldb.debugger`. Some other unrelated hacks involving LLDB create a situation where `lldb.debugger` is sometimes nondeterministically set to `None` temporarily and asynchronously, during a short period after debugger startup. The recommended way to run a script from a file is `command script import`. Unfortunately `command script import` requires the script file basename to be a valid Python identifer plus the `.py` suffix, so we can't use our proc-pid scheme anymore. Instead we now make the init script be responsible for deleting itself when it finishes running. We also have to reformat the LLDB init script to put the code that needs to run eagerly in a `__lldb_init_module()` function. This function takes a `debugger` argument so we can avoid using `lldb.debugger`, which is the ultimate point of this change.
1 parent 49e8eb0 commit 38d1474

3 files changed

Lines changed: 151 additions & 144 deletions

File tree

src/DebuggerExtensionCommandHandler.cc

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,17 @@ def hex_escape(string):
5858
curr_char = ord(curr_char)
5959
result += format(curr_char, '02x')
6060
return result
61-
6261
)Delimiter";
6362

6463
string DebuggerExtensionCommandHandler::gdb_macros() {
6564
DebuggerExtensionCommand::init_auto_args();
6665
stringstream ss;
6766
ss << R"Delimiter(set python print-stack full
6867
python
68+
import re
6969
)Delimiter"
7070
<< shared_python
7171
<< R"Delimiter(
72-
import re
73-
7472
class RRWhere(gdb.Command):
7573
"""Helper to get the location for checkpoints/history. Used by auto-args"""
7674
def __init__(self):
@@ -193,7 +191,8 @@ end
193191
return ss.str();
194192
}
195193

196-
static void lldb_macro_binding(ostream& ss, const DebuggerExtensionCommand& cmd) {
194+
static void lldb_macro_binding(ostream& def_stream, ostream& call_stream,
195+
const DebuggerExtensionCommand& cmd) {
197196
string func_name = "rr_command_";
198197
for (char ch : cmd.name()) {
199198
if (ch == ' ') {
@@ -205,29 +204,30 @@ static void lldb_macro_binding(ostream& ss, const DebuggerExtensionCommand& cmd)
205204
}
206205
func_name.push_back(ch);
207206
}
208-
ss << "def " << func_name << "(debugger, command, exe_ctx, result, internal_dict):\n";
207+
def_stream << "def " << func_name << "(debugger, command, exe_ctx, result, internal_dict):\n";
209208
if (!cmd.docs().empty()) {
210-
ss << " \"\"\"" << cmd.docs() << "\"\"\"\n";
209+
def_stream << " \"\"\"" << cmd.docs() << "\"\"\"\n";
211210
}
212-
ss << " cmd_name = '" << cmd.name() << "'\n"
213-
<< " auto_args = ";
214-
print_python_string_array(ss, cmd.auto_args());
215-
ss << "\n"
216-
<< " command_impl(debugger, command, exe_ctx, result, cmd_name, auto_args)\n"
217-
<< "\n"
218-
<< "lldb.debugger.HandleCommand('command script add -f " << func_name
219-
<< " " << cmd.name() << "')\n\n";
211+
def_stream << " cmd_name = '" << cmd.name() << "'\n"
212+
<< " auto_args = ";
213+
print_python_string_array(def_stream, cmd.auto_args());
214+
def_stream << "\n"
215+
<< " command_impl(debugger, command, exe_ctx, result, cmd_name, auto_args)\n"
216+
<< "\n";
217+
call_stream << " debugger.HandleCommand('command script add -f " << func_name
218+
<< " " << cmd.name() << "')\n";
220219
}
221220

222-
string DebuggerExtensionCommandHandler::lldb_python_macros() {
221+
DebuggerExtensionCommandHandler::LldbCommands
222+
DebuggerExtensionCommandHandler::lldb_python_macros() {
223223
DebuggerExtensionCommand::init_auto_args();
224224
stringstream ss;
225-
ss << shared_python
226-
<< R"Delimiter(
227-
import lldb
225+
ss << R"Delimiter(import lldb
228226
import re
229227
import shlex
230-
228+
)Delimiter"
229+
<< shared_python
230+
<< R"Delimiter(
231231
def run_command_and_get_output(debugger, command):
232232
result = lldb.SBCommandReturnObject()
233233
debugger.GetCommandInterpreter().HandleCommand(command, result)
@@ -256,12 +256,16 @@ def command_impl(debugger, command, exe_ctx, result, cmd_name, auto_args):
256256
257257
)Delimiter";
258258

259+
stringstream call_stream;
259260
if (debugger_command_list) {
260261
for (auto& it : *debugger_command_list) {
261-
lldb_macro_binding(ss, *it);
262+
lldb_macro_binding(ss, call_stream, *it);
262263
}
263264
}
264-
return ss.str();
265+
LldbCommands cmds;
266+
cmds.toplevel_definitions = ss.str();
267+
cmds.run_on_startup = call_stream.str();
268+
return cmds;
265269
}
266270

267271
/*static*/ DebuggerExtensionCommand* DebuggerExtensionCommandHandler::command_for_name(const string& name) {

src/DebuggerExtensionCommandHandler.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ class DebuggerExtensionCommandHandler {
2525

2626
// Declare any registered command with supporting
2727
// wrapper code --- LLDB Python script.
28-
static std::string lldb_python_macros();
28+
struct LldbCommands {
29+
std::string toplevel_definitions;
30+
// 2-space-indented code to run on startup.
31+
std::string run_on_startup;
32+
};
33+
static LldbCommands lldb_python_macros();
2934

3035
static void register_command(DebuggerExtensionCommand& cmd);
3136

src/launch_debugger.cc

Lines changed: 120 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -26,108 +26,115 @@ namespace rr {
2626
// Special-sauce macros defined by rr when launching the gdb client,
2727
// which implement functionality outside of the gdb remote protocol.
2828
// (Don't stare at them too long or you'll go blind ;).)
29-
static const string& gdb_rr_macros() {
30-
static string s;
31-
32-
if (s.empty()) {
33-
stringstream ss;
34-
ss << DebuggerExtensionCommandHandler::gdb_macros()
35-
// gdb warns about redefining inbuilt commands, silence that by
36-
// wrapping it in python code
37-
<< "python gdb.execute('define jump\\nrr-denied jump\\nend')\n"
38-
<< "python gdb.execute('define restart\\nrun c$arg0\\nend')\n"
39-
<< "document restart\n"
40-
<< "restart at checkpoint N\n"
41-
<< "checkpoints are created with the 'checkpoint' command\n"
42-
<< "end\n"
43-
<< "define seek-ticks\n"
44-
<< " run t$arg0\n"
45-
<< "end\n"
46-
<< "document seek-ticks\n"
47-
<< "restart at given ticks value\n"
48-
<< "end\n"
49-
// In gdb version "Fedora 7.8.1-30.fc21", a raw "run" command
50-
// issued before any user-generated resume-execution command
51-
// results in gdb hanging just after the inferior hits an internal
52-
// gdb breakpoint. This happens outside of rr, with gdb
53-
// controlling gdbserver, as well. We work around that by
54-
// ensuring *some* resume-execution command has been issued before
55-
// restarting the session. But, only if the inferior hasn't
56-
// already finished execution ($_thread != 0). If it has and we
57-
// issue the "stepi" command, then gdb refuses to restart
58-
// execution.
59-
<< "define hook-run\n"
60-
<< " rr-hook-run\n"
61-
<< "end\n"
62-
<< "define hookpost-continue\n"
63-
<< " rr-set-suppress-run-hook 1\n"
64-
<< "end\n"
65-
<< "define hookpost-step\n"
66-
<< " rr-set-suppress-run-hook 1\n"
67-
<< "end\n"
68-
<< "define hookpost-stepi\n"
69-
<< " rr-set-suppress-run-hook 1\n"
70-
<< "end\n"
71-
<< "define hookpost-next\n"
72-
<< " rr-set-suppress-run-hook 1\n"
73-
<< "end\n"
74-
<< "define hookpost-nexti\n"
75-
<< " rr-set-suppress-run-hook 1\n"
76-
<< "end\n"
77-
<< "define hookpost-finish\n"
78-
<< " rr-set-suppress-run-hook 1\n"
79-
<< "end\n"
80-
<< "define hookpost-reverse-continue\n"
81-
<< " rr-set-suppress-run-hook 1\n"
82-
<< "end\n"
83-
<< "define hookpost-reverse-step\n"
84-
<< " rr-set-suppress-run-hook 1\n"
85-
<< "end\n"
86-
<< "define hookpost-reverse-stepi\n"
87-
<< " rr-set-suppress-run-hook 1\n"
88-
<< "end\n"
89-
<< "define hookpost-reverse-finish\n"
90-
<< " rr-set-suppress-run-hook 1\n"
91-
<< "end\n"
92-
<< "define hookpost-run\n"
93-
<< " rr-set-suppress-run-hook 0\n"
94-
<< "end\n"
95-
<< "set unwindonsignal on\n"
96-
<< "set non-stop off\n"
97-
<< "handle SIGURG stop\n"
98-
<< "set prompt (rr) \n"
99-
// Try both "set target-async" and "maint set target-async" since
100-
// that changed recently.
101-
<< "python\n"
102-
<< "import re\n"
103-
<< "m = re.compile(r"
104-
<< "'[^0-9]*([0-9]+)\\.([0-9]+)(\\.([0-9]+))?'"
105-
<< ").match(gdb.VERSION)\n"
106-
<< "ver = int(m.group(1))*10000 + int(m.group(2))*100\n"
107-
<< "if m.group(4):\n"
108-
<< " ver = ver + int(m.group(4))\n"
109-
<< "\n"
110-
<< "if ver == 71100:\n"
111-
<< " gdb.write("
112-
<< "'This version of gdb (7.11.0) has known bugs that break rr. "
113-
<< "Install 7.11.1 or later.\\n', gdb.STDERR)\n"
114-
<< "\n"
115-
<< "if ver < 71101:\n"
116-
<< " gdb.execute('set target-async 0')\n"
117-
<< " gdb.execute('maint set target-async 0')\n"
118-
<< "end\n";
119-
s = ss.str();
29+
static string gdb_rr_macros(const string* file_to_delete) {
30+
stringstream ss;
31+
ss << DebuggerExtensionCommandHandler::gdb_macros()
32+
// gdb warns about redefining inbuilt commands, silence that by
33+
// wrapping it in python code
34+
<< "python gdb.execute('define jump\\nrr-denied jump\\nend')\n"
35+
<< "python gdb.execute('define restart\\nrun c$arg0\\nend')\n"
36+
<< "document restart\n"
37+
<< "restart at checkpoint N\n"
38+
<< "checkpoints are created with the 'checkpoint' command\n"
39+
<< "end\n"
40+
<< "define seek-ticks\n"
41+
<< " run t$arg0\n"
42+
<< "end\n"
43+
<< "document seek-ticks\n"
44+
<< "restart at given ticks value\n"
45+
<< "end\n"
46+
// In gdb version "Fedora 7.8.1-30.fc21", a raw "run" command
47+
// issued before any user-generated resume-execution command
48+
// results in gdb hanging just after the inferior hits an internal
49+
// gdb breakpoint. This happens outside of rr, with gdb
50+
// controlling gdbserver, as well. We work around that by
51+
// ensuring *some* resume-execution command has been issued before
52+
// restarting the session. But, only if the inferior hasn't
53+
// already finished execution ($_thread != 0). If it has and we
54+
// issue the "stepi" command, then gdb refuses to restart
55+
// execution.
56+
<< "define hook-run\n"
57+
<< " rr-hook-run\n"
58+
<< "end\n"
59+
<< "define hookpost-continue\n"
60+
<< " rr-set-suppress-run-hook 1\n"
61+
<< "end\n"
62+
<< "define hookpost-step\n"
63+
<< " rr-set-suppress-run-hook 1\n"
64+
<< "end\n"
65+
<< "define hookpost-stepi\n"
66+
<< " rr-set-suppress-run-hook 1\n"
67+
<< "end\n"
68+
<< "define hookpost-next\n"
69+
<< " rr-set-suppress-run-hook 1\n"
70+
<< "end\n"
71+
<< "define hookpost-nexti\n"
72+
<< " rr-set-suppress-run-hook 1\n"
73+
<< "end\n"
74+
<< "define hookpost-finish\n"
75+
<< " rr-set-suppress-run-hook 1\n"
76+
<< "end\n"
77+
<< "define hookpost-reverse-continue\n"
78+
<< " rr-set-suppress-run-hook 1\n"
79+
<< "end\n"
80+
<< "define hookpost-reverse-step\n"
81+
<< " rr-set-suppress-run-hook 1\n"
82+
<< "end\n"
83+
<< "define hookpost-reverse-stepi\n"
84+
<< " rr-set-suppress-run-hook 1\n"
85+
<< "end\n"
86+
<< "define hookpost-reverse-finish\n"
87+
<< " rr-set-suppress-run-hook 1\n"
88+
<< "end\n"
89+
<< "define hookpost-run\n"
90+
<< " rr-set-suppress-run-hook 0\n"
91+
<< "end\n"
92+
<< "set unwindonsignal on\n"
93+
<< "set non-stop off\n"
94+
<< "handle SIGURG stop\n"
95+
<< "set prompt (rr) \n"
96+
// Try both "set target-async" and "maint set target-async" since
97+
// that changed recently.
98+
<< "python\n"
99+
<< "import re\n"
100+
<< "import os\n"
101+
<< "m = re.compile(r"
102+
<< "'[^0-9]*([0-9]+)\\.([0-9]+)(\\.([0-9]+))?'"
103+
<< ").match(gdb.VERSION)\n"
104+
<< "ver = int(m.group(1))*10000 + int(m.group(2))*100\n"
105+
<< "if m.group(4):\n"
106+
<< " ver = ver + int(m.group(4))\n"
107+
<< "\n"
108+
<< "if ver == 71100:\n"
109+
<< " gdb.write("
110+
<< "'This version of gdb (7.11.0) has known bugs that break rr. "
111+
<< "Install 7.11.1 or later.\\n', gdb.STDERR)\n"
112+
<< "\n"
113+
<< "if ver < 71101:\n"
114+
<< " gdb.execute('set target-async 0')\n"
115+
<< " gdb.execute('maint set target-async 0')\n";
116+
if (file_to_delete) {
117+
ss << "os.unlink('" << *file_to_delete << "')\n";
120118
}
121-
return s;
119+
ss << "end\n";
120+
return ss.str();
122121
}
123122

124-
static const string& lldb_python_rr_macros() {
123+
static const string& lldb_python_rr_macros(const string* file_to_delete) {
125124
static string s;
126125

127126
if (s.empty()) {
127+
auto cmds = DebuggerExtensionCommandHandler::lldb_python_macros();
128128
stringstream ss;
129-
ss << DebuggerExtensionCommandHandler::lldb_python_macros()
130-
<< "lldb.debugger.HandleCommand('set set prompt \"(rr) \"')\n";
129+
ss << cmds.toplevel_definitions
130+
<< "import os\n"
131+
<< "def __lldb_init_module(debugger, internal_dict):\n"
132+
<< cmds.run_on_startup
133+
<< " debugger.HandleCommand('set set prompt \"(rr) \"')\n";
134+
if (file_to_delete) {
135+
ss << " os.unlink('" << *file_to_delete << "')\n";
136+
}
137+
ss << "\n";
131138
s = ss.str();
132139
}
133140
return s;
@@ -229,24 +236,6 @@ vector<string> debugger_launch_command(Task* t, int socket_domain,
229236
return cmd;
230237
}
231238

232-
static string create_command_file(const string& macros) {
233-
TempFile file = create_temporary_file("rr-debugger-commands-XXXXXX");
234-
// This fd is just leaked. That's fine since we only call this once
235-
// per rr invocation at the moment.
236-
int fd = file.fd.extract();
237-
unlink(file.name.c_str());
238-
239-
ssize_t len = macros.size();
240-
int written = write(fd, macros.c_str(), len);
241-
if (written != len) {
242-
FATAL() << "Failed to write gdb command file";
243-
}
244-
245-
stringstream procfile;
246-
procfile << "/proc/" << getpid() << "/fd/" << fd;
247-
return procfile.str();
248-
}
249-
250239
string to_shell_string(const vector<string>& args) {
251240
stringstream ss;
252241
for (auto& a : args) {
@@ -290,12 +279,16 @@ void launch_debugger(ScopedFd& params_pipe_fd,
290279
cmd.push_back(debugger_file_path);
291280
vector<string> env = current_env();
292281

282+
// LLDB 'command script import' requires the filename to be a valid Python
283+
// identifier.
284+
TempFile file = create_temporary_file("rr_debugger_commands_XXXXXX");
293285
switch (debugger_type) {
294286
case DebuggerType::GDB: {
295287
push_default_gdb_options(cmd, serve_files);
296-
string gdb_command_file = create_command_file(gdb_rr_macros());
288+
string script = gdb_rr_macros(&file.name);
289+
write_all(file.fd, script.data(), script.size());
297290
cmd.push_back("-x");
298-
cmd.push_back(gdb_command_file);
291+
cmd.push_back(file.name);
299292

300293
bool did_set_remote = false;
301294
for (size_t i = 0; i < options.size(); ++i) {
@@ -317,12 +310,17 @@ void launch_debugger(ScopedFd& params_pipe_fd,
317310
cmd.push_back("--source-quietly");
318311
cmd.insert(cmd.end(), options.begin(), options.end());
319312
push_lldb_target_remote_cmd(cmd, socket_domain, host, port);
320-
// We have to load the commands as a Python script. If we
321-
// use the "script" command to launch a nested Python interpreter,
322-
// Python emits some annoying text that we dont want to see.
323-
string lldb_command_file = create_command_file(lldb_python_rr_macros());
313+
// LLDB 'command script import' requires the file to end in '.py'.
314+
string new_name = file.name + ".py";
315+
if (renameat2(AT_FDCWD, file.name.c_str(), AT_FDCWD, new_name.c_str(),
316+
RENAME_NOREPLACE)) {
317+
FATAL() << "Can't fix temp file name";
318+
}
319+
file.name = new_name;
320+
string script = lldb_python_rr_macros(&file.name);
321+
write_all(file.fd, script.data(), script.size());
324322
cmd.push_back("-o");
325-
cmd.push_back("script exec(open('" + lldb_command_file + "').read())");
323+
cmd.push_back("command script import " + file.name);
326324
env.push_back("LLDB_UNDER_RR=1");
327325
break;
328326
}
@@ -392,8 +390,8 @@ void emergency_debug(Task* t) {
392390
GdbServer::serve_emergency_debugger(std::move(dbg), t);
393391
}
394392

395-
string gdb_init_script() { return gdb_rr_macros(); }
393+
string gdb_init_script() { return gdb_rr_macros(nullptr); }
396394

397-
string lldb_init_script() { return lldb_python_rr_macros(); }
395+
string lldb_init_script() { return lldb_python_rr_macros(nullptr); }
398396

399397
} // namespace rr

0 commit comments

Comments
 (0)