1 #include "debug_lldb.hpp"
2 #include "config.hpp"
3 #include "filesystem.hpp"
4 #include "process.hpp"
5 #include "terminal.hpp"
6 #include "utility.hpp"
7 #include <boost/filesystem.hpp>
8 #include <iostream>
9
10 extern char **environ;
11
12 bool Debug::LLDB::initialized = false;
13
log(const char * msg,void *)14 void log(const char *msg, void *) {
15 std::cout << "debugger log: " << msg << std::endl;
16 }
17
LLDB()18 Debug::LLDB::LLDB() : state(lldb::StateType::eStateInvalid), buffer_size(131072) {
19 #ifndef __APPLE__
20 auto debug_server_path = filesystem::get_executable("lldb-server").string();
21 if(debug_server_path != "lldb-server")
22 Glib::setenv("LLDB_DEBUGSERVER_PATH", debug_server_path, false);
23 #endif
24 }
25
destroy_()26 void Debug::LLDB::destroy_() {
27 {
28 LockGuard lock(mutex);
29 if(process)
30 process->Kill();
31 }
32
33 if(debug_thread.joinable())
34 debug_thread.join();
35
36 LockGuard lock(mutex);
37 if(debugger)
38 lldb::SBDebugger::Destroy(*debugger);
39 }
40
parse_run_arguments(const std::string & command)41 std::tuple<std::vector<std::string>, std::string, std::vector<std::string>> Debug::LLDB::parse_run_arguments(const std::string &command) {
42 std::vector<std::string> environment;
43 std::string executable;
44 std::vector<std::string> arguments;
45
46 size_t start_pos = std::string::npos;
47 bool quote = false;
48 bool double_quote = false;
49 size_t backslash_count = 0;
50 for(size_t c = 0; c <= command.size(); c++) {
51 if(c == command.size() || (!quote && !double_quote && backslash_count % 2 == 0 && command[c] == ' ')) {
52 if(c > 0 && start_pos != std::string::npos) {
53 auto argument = command.substr(start_pos, c - start_pos);
54 if(executable.empty()) {
55 // Check for environment variable
56 bool env_arg = false;
57 for(size_t c = 0; c < argument.size(); ++c) {
58 if((argument[c] >= 'a' && argument[c] <= 'z') || (argument[c] >= 'A' && argument[c] <= 'Z') ||
59 (argument[c] >= '0' && argument[c] <= '9') || argument[c] == '_')
60 continue;
61 else if(argument[c] == '=' && c + 1 < argument.size()) {
62 environment.emplace_back(argument.substr(0, c + 1) + filesystem::unescape_argument(argument.substr(c + 1)));
63 env_arg = true;
64 break;
65 }
66 else
67 break;
68 }
69
70 if(!env_arg)
71 executable = filesystem::unescape_argument(argument);
72 }
73 else
74 arguments.emplace_back(filesystem::unescape_argument(argument));
75 start_pos = std::string::npos;
76 }
77 }
78 else if(command[c] == '\'' && backslash_count % 2 == 0 && !double_quote)
79 quote = !quote;
80 else if(command[c] == '"' && backslash_count % 2 == 0 && !quote)
81 double_quote = !double_quote;
82 else if(command[c] == '\\' && !quote && !double_quote)
83 ++backslash_count;
84 else
85 backslash_count = 0;
86 if(c < command.size() && start_pos == std::string::npos && command[c] != ' ')
87 start_pos = c;
88 }
89
90 return std::make_tuple(environment, executable, arguments);
91 }
92
start(const std::string & command,const boost::filesystem::path & path,const std::vector<std::pair<boost::filesystem::path,int>> & breakpoints,const std::vector<std::string> & startup_commands,const std::string & remote_host)93 void Debug::LLDB::start(const std::string &command, const boost::filesystem::path &path,
94 const std::vector<std::pair<boost::filesystem::path, int>> &breakpoints,
95 const std::vector<std::string> &startup_commands, const std::string &remote_host) {
96 LockGuard lock(mutex);
97
98 if(!debugger) {
99 initialized = true;
100 lldb::SBDebugger::Initialize();
101 debugger = std::make_unique<lldb::SBDebugger>(lldb::SBDebugger::Create(true, log, nullptr));
102 listener = std::make_unique<lldb::SBListener>("juCi++ lldb listener");
103 }
104
105 //Create executable string and argument array
106 auto parsed_run_arguments = parse_run_arguments(command);
107 auto &environment_from_arguments = std::get<0>(parsed_run_arguments);
108 auto &executable = std::get<1>(parsed_run_arguments);
109 #ifdef _WIN32
110 if(remote_host.empty())
111 executable += ".exe";
112 #endif
113 auto &arguments = std::get<2>(parsed_run_arguments);
114
115 std::vector<const char *> argv;
116 argv.reserve(arguments.size());
117 for(auto &argument : arguments)
118 argv.emplace_back(argument.c_str());
119 argv.emplace_back(nullptr);
120
121 executable = filesystem::get_absolute_path(executable, path).string();
122 auto target = debugger->CreateTarget(executable.c_str());
123 if(!target.IsValid()) {
124 Terminal::get().async_print("\e[31mError (debug)\e[m: Could not create debug target to: " + executable + '\n', true);
125 for(auto &handler : on_exit)
126 handler(-1);
127 return;
128 }
129
130 //Set breakpoints
131 for(auto &breakpoint : breakpoints) {
132 if(!(target.BreakpointCreateByLocation(breakpoint.first.string().c_str(), breakpoint.second)).IsValid()) {
133 Terminal::get().async_print("\e[31mError (debug)\e[m: Could not create breakpoint at: " + breakpoint.first.string() + ":" + std::to_string(breakpoint.second) + '\n', true);
134 for(auto &handler : on_exit)
135 handler(-1);
136 return;
137 }
138 }
139
140 lldb::SBError error;
141 if(!remote_host.empty()) {
142 auto connect_string = "connect://" + remote_host;
143 process = std::make_unique<lldb::SBProcess>(target.ConnectRemote(*listener, connect_string.c_str(), "gdb-remote", error));
144 if(error.Fail()) {
145 Terminal::get().async_print(std::string("\e[31mError (debug)\e[m: ") + error.GetCString() + '\n', true);
146 for(auto &handler : on_exit)
147 handler(-1);
148 return;
149 }
150 lldb::SBEvent event;
151 while(true) {
152 if(listener->GetNextEvent(event)) {
153 if((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged) > 0) {
154 auto state = process->GetStateFromEvent(event);
155 this->state = state;
156 if(state == lldb::StateType::eStateConnected)
157 break;
158 }
159 }
160 }
161
162 // Create environment array
163 std::vector<const char *> environment;
164 environment.reserve(environment_from_arguments.size());
165 for(auto &e : environment_from_arguments)
166 environment.emplace_back(e.c_str());
167 environment.emplace_back(nullptr);
168
169 process->RemoteLaunch(argv.data(), environment.data(), nullptr, nullptr, nullptr, nullptr, lldb::eLaunchFlagNone, false, error);
170 if(!error.Fail())
171 process->Continue();
172 }
173 else {
174 // Create environment array
175 std::vector<const char *> environment;
176 environment.reserve(environment_from_arguments.size());
177 for(auto &e : environment_from_arguments)
178 environment.emplace_back(e.c_str());
179 size_t environ_size = 0;
180 while(environ[environ_size])
181 ++environ_size;
182 for(size_t c = 0; c < environ_size; ++c)
183 environment.emplace_back(environ[c]);
184 environment.emplace_back(nullptr);
185
186 process = std::make_unique<lldb::SBProcess>(target.Launch(*listener, argv.data(), environment.data(), nullptr, nullptr, nullptr, path.string().c_str(), lldb::eLaunchFlagNone, false, error));
187 }
188
189 if(error.Fail()) {
190 Terminal::get().async_print(std::string("\e[31mError (debug)\e[m: ") + error.GetCString() + '\n', true);
191 for(auto &handler : on_exit)
192 handler(-1);
193 return;
194 }
195
196 if(debug_thread.joinable())
197 debug_thread.join();
198 for(auto &handler : on_start)
199 handler(*process);
200
201 for(auto &command : startup_commands) {
202 lldb::SBCommandReturnObject command_return_object;
203 debugger->GetCommandInterpreter().HandleCommand(command.c_str(), command_return_object, false);
204 }
205
206 debug_thread = std::thread([this]() {
207 lldb::SBEvent event;
208 while(true) {
209 LockGuard lock(mutex);
210 if(listener->GetNextEvent(event)) {
211 if((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged) > 0) {
212 auto state = process->GetStateFromEvent(event);
213 this->state = state;
214
215 if(state == lldb::StateType::eStateStopped) {
216 for(uint32_t c = 0; c < process->GetNumThreads(); c++) {
217 auto thread = process->GetThreadAtIndex(c);
218 if(thread.GetStopReason() >= 2) {
219 process->SetSelectedThreadByIndexID(thread.GetIndexID());
220 break;
221 }
222 }
223 }
224
225 lock.unlock();
226 for(auto &handler : on_event)
227 handler(event);
228 lock.lock();
229
230 if(state == lldb::StateType::eStateExited || state == lldb::StateType::eStateCrashed) {
231 auto exit_status = state == lldb::StateType::eStateCrashed ? -1 : process->GetExitStatus();
232 lock.unlock();
233 for(auto &handler : on_exit)
234 handler(exit_status);
235 lock.lock();
236 process.reset();
237 this->state = lldb::StateType::eStateInvalid;
238 return;
239 }
240 }
241 if((event.GetType() & lldb::SBProcess::eBroadcastBitSTDOUT) > 0) {
242 char buffer[buffer_size];
243 size_t n;
244 while((n = process->GetSTDOUT(buffer, buffer_size)) != 0)
245 Terminal::get().async_print(std::string(buffer, n));
246 }
247 //TODO: for some reason stderr is redirected to stdout
248 if((event.GetType() & lldb::SBProcess::eBroadcastBitSTDERR) > 0) {
249 char buffer[buffer_size];
250 size_t n;
251 while((n = process->GetSTDERR(buffer, buffer_size)) != 0)
252 Terminal::get().async_print(std::string(buffer, n), true);
253 }
254 }
255 lock.unlock();
256 std::this_thread::sleep_for(std::chrono::milliseconds(200));
257 }
258 });
259 }
260
continue_debug()261 void Debug::LLDB::continue_debug() {
262 LockGuard lock(mutex);
263 if(state == lldb::StateType::eStateStopped)
264 process->Continue();
265 }
266
stop()267 void Debug::LLDB::stop() {
268 LockGuard lock(mutex);
269 if(state == lldb::StateType::eStateRunning) {
270 auto error = process->Stop();
271 if(error.Fail())
272 Terminal::get().async_print(std::string("\e[31mError (debug)\e[m: ") + error.GetCString() + '\n', true);
273 }
274 }
275
kill()276 void Debug::LLDB::kill() {
277 LockGuard lock(mutex);
278 if(process) {
279 auto error = process->Kill();
280 if(error.Fail())
281 Terminal::get().async_print(std::string("\e[31mError (debug)\e[m: ") + error.GetCString() + '\n', true);
282 }
283 }
284
step_over()285 void Debug::LLDB::step_over() {
286 LockGuard lock(mutex);
287 if(state == lldb::StateType::eStateStopped) {
288 process->GetSelectedThread().StepOver();
289 }
290 }
291
step_into()292 void Debug::LLDB::step_into() {
293 LockGuard lock(mutex);
294 if(state == lldb::StateType::eStateStopped) {
295 process->GetSelectedThread().StepInto();
296 }
297 }
298
step_out()299 void Debug::LLDB::step_out() {
300 LockGuard lock(mutex);
301 if(state == lldb::StateType::eStateStopped) {
302 process->GetSelectedThread().StepOut();
303 }
304 }
305
run_command(const std::string & command)306 std::pair<std::string, std::string> Debug::LLDB::run_command(const std::string &command) {
307 LockGuard lock(mutex);
308 std::pair<std::string, std::string> command_return;
309 if(state == lldb::StateType::eStateStopped || state == lldb::StateType::eStateRunning) {
310 lldb::SBCommandReturnObject command_return_object;
311 debugger->GetCommandInterpreter().HandleCommand(command.c_str(), command_return_object, true);
312 auto output = command_return_object.GetOutput();
313 if(output)
314 command_return.first = output;
315 auto error = command_return_object.GetError();
316 if(error)
317 command_return.second = error;
318 }
319 return command_return;
320 }
321
get_backtrace()322 std::vector<Debug::LLDB::Frame> Debug::LLDB::get_backtrace() {
323 LockGuard lock(mutex);
324 std::vector<Frame> backtrace;
325 if(state == lldb::StateType::eStateStopped) {
326 auto thread = process->GetSelectedThread();
327 for(uint32_t c_f = 0; c_f < thread.GetNumFrames(); c_f++) {
328 Frame backtrace_frame;
329 auto frame = thread.GetFrameAtIndex(c_f);
330
331 backtrace_frame.index = c_f;
332
333 if(auto function_name = frame.GetFunctionName())
334 backtrace_frame.function_name = function_name;
335
336 if(auto module_filename = frame.GetModule().GetFileSpec().GetFilename())
337 backtrace_frame.module_filename = module_filename;
338
339 auto line_entry = frame.GetLineEntry();
340 if(line_entry.IsValid()) {
341 lldb::SBStream stream;
342 line_entry.GetFileSpec().GetDescription(stream);
343 auto column = line_entry.GetColumn();
344 if(column == 0)
345 column = 1;
346 backtrace_frame.file_path = filesystem::get_normal_path(stream.GetData());
347 backtrace_frame.line_nr = line_entry.GetLine();
348 backtrace_frame.line_index = column;
349 }
350 backtrace.emplace_back(backtrace_frame);
351 }
352 }
353 return backtrace;
354 }
355
get_variables()356 std::vector<Debug::LLDB::Variable> Debug::LLDB::get_variables() {
357 LockGuard lock(mutex);
358 std::vector<Debug::LLDB::Variable> variables;
359 if(state == lldb::StateType::eStateStopped) {
360 for(uint32_t c_t = 0; c_t < process->GetNumThreads(); c_t++) {
361 auto thread = process->GetThreadAtIndex(c_t);
362 for(uint32_t c_f = 0; c_f < thread.GetNumFrames(); c_f++) {
363 auto frame = thread.GetFrameAtIndex(c_f);
364 auto values = frame.GetVariables(true, true, true, false);
365 for(uint32_t value_index = 0; value_index < values.GetSize(); value_index++) {
366 auto value = std::make_shared<lldb::SBValue>(values.GetValueAtIndex(value_index));
367
368 Debug::LLDB::Variable variable;
369 variable.thread_index_id = thread.GetIndexID();
370 variable.frame_index = c_f;
371 if(auto name = value->GetName())
372 variable.name = name;
373
374 variable.get_value = [value]() {
375 lldb::SBStream stream;
376 value->GetDescription(stream);
377 return std::string(stream.GetData());
378 };
379
380 auto declaration = value->GetDeclaration();
381 if(declaration.IsValid()) {
382 variable.declaration_found = true;
383 variable.line_nr = declaration.GetLine();
384 variable.line_index = declaration.GetColumn();
385 if(variable.line_index == 0)
386 variable.line_index = 1;
387
388 auto file_spec = declaration.GetFileSpec();
389 variable.file_path = filesystem::get_normal_path(file_spec.GetDirectory());
390 variable.file_path /= file_spec.GetFilename();
391 }
392 else {
393 variable.declaration_found = false;
394 auto line_entry = frame.GetLineEntry();
395 if(line_entry.IsValid()) {
396 variable.line_nr = line_entry.GetLine();
397 variable.line_index = line_entry.GetColumn();
398 if(variable.line_index == 0)
399 variable.line_index = 1;
400
401 auto file_spec = line_entry.GetFileSpec();
402 variable.file_path = filesystem::get_normal_path(file_spec.GetDirectory());
403 variable.file_path /= file_spec.GetFilename();
404 }
405 }
406 variables.emplace_back(variable);
407 }
408 }
409 }
410 }
411 return variables;
412 }
413
select_frame(uint32_t frame_index,uint32_t thread_index_id)414 void Debug::LLDB::select_frame(uint32_t frame_index, uint32_t thread_index_id) {
415 LockGuard lock(mutex);
416 if(state == lldb::StateType::eStateStopped) {
417 if(thread_index_id != 0)
418 process->SetSelectedThreadByIndexID(thread_index_id);
419 process->GetSelectedThread().SetSelectedFrame(frame_index);
420 ;
421 }
422 }
423
get_value(const std::string & variable,const boost::filesystem::path & file_path,unsigned int line_nr,unsigned int line_index)424 std::string Debug::LLDB::get_value(const std::string &variable, const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) {
425 LockGuard lock(mutex);
426 if(state == lldb::StateType::eStateStopped) {
427 auto frame = process->GetSelectedThread().GetSelectedFrame();
428
429 auto values = frame.GetVariables(true, true, true, false);
430 //First try to find variable based on name, file and line number
431 if(!file_path.empty()) {
432 for(uint32_t value_index = 0; value_index < values.GetSize(); value_index++) {
433 lldb::SBStream stream;
434 auto value = values.GetValueAtIndex(value_index);
435
436 if(auto name = value.GetName()) {
437 if(name == variable) {
438 auto declaration = value.GetDeclaration();
439 if(declaration.IsValid()) {
440 if(declaration.GetLine() == line_nr && (declaration.GetColumn() == 0 || declaration.GetColumn() == line_index)) {
441 auto file_spec = declaration.GetFileSpec();
442 auto value_decl_path = filesystem::get_normal_path(file_spec.GetDirectory());
443 value_decl_path /= file_spec.GetFilename();
444 if(value_decl_path == file_path) {
445 value.GetDescription(stream);
446 std::string variable_value = stream.GetData();
447 if(ends_with(variable_value, "\n\n"))
448 variable_value.pop_back(); // Remove newline at end of string
449 return variable_value;
450 }
451 }
452 }
453 }
454 }
455 }
456 }
457 }
458 return {};
459 }
460
get_value(const std::string & expression)461 std::string Debug::LLDB::get_value(const std::string &expression) {
462 LockGuard lock(mutex);
463 std::string variable_value;
464 if(state == lldb::StateType::eStateStopped) {
465 auto frame = process->GetSelectedThread().GetSelectedFrame();
466 if(variable_value.empty()) {
467 // Attempt to get variable from variable expression, using the faster GetValueForVariablePath
468 auto value = frame.GetValueForVariablePath(expression.c_str());
469 if(value.IsValid()) {
470 lldb::SBStream stream;
471 value.GetDescription(stream);
472 variable_value = stream.GetData();
473 }
474 }
475 if(variable_value.empty()) {
476 // Attempt to get variable from variable expression, using the slower EvaluateExpression
477 auto value = frame.EvaluateExpression(expression.c_str());
478 if(value.IsValid() && !value.GetError().Fail()) {
479 lldb::SBStream stream;
480 value.GetDescription(stream);
481 variable_value = stream.GetData();
482 }
483 }
484 }
485 return variable_value;
486 }
487
get_return_value(const boost::filesystem::path & file_path,unsigned int line_nr,unsigned int line_index)488 std::string Debug::LLDB::get_return_value(const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) {
489 LockGuard lock(mutex);
490 std::string return_value;
491 if(state == lldb::StateType::eStateStopped) {
492 auto thread = process->GetSelectedThread();
493 auto thread_return_value = thread.GetStopReturnValue();
494 if(thread_return_value.IsValid()) {
495 auto line_entry = thread.GetSelectedFrame().GetLineEntry();
496 if(line_entry.IsValid()) {
497 lldb::SBStream stream;
498 line_entry.GetFileSpec().GetDescription(stream);
499 if(filesystem::get_normal_path(stream.GetData()) == file_path && line_entry.GetLine() == line_nr &&
500 (line_entry.GetColumn() == 0 || line_entry.GetColumn() == line_index)) {
501 lldb::SBStream stream;
502 thread_return_value.GetDescription(stream);
503 return_value = stream.GetData();
504 }
505 }
506 }
507 }
508 return return_value;
509 }
510
is_invalid()511 bool Debug::LLDB::is_invalid() {
512 LockGuard lock(mutex);
513 return state == lldb::StateType::eStateInvalid;
514 }
515
is_stopped()516 bool Debug::LLDB::is_stopped() {
517 LockGuard lock(mutex);
518 return state == lldb::StateType::eStateStopped;
519 }
520
is_running()521 bool Debug::LLDB::is_running() {
522 LockGuard lock(mutex);
523 return state == lldb::StateType::eStateRunning;
524 }
525
add_breakpoint(const boost::filesystem::path & file_path,int line_nr)526 void Debug::LLDB::add_breakpoint(const boost::filesystem::path &file_path, int line_nr) {
527 LockGuard lock(mutex);
528 if(state == lldb::eStateStopped || state == lldb::eStateRunning) {
529 if(!(process->GetTarget().BreakpointCreateByLocation(file_path.string().c_str(), line_nr)).IsValid())
530 Terminal::get().async_print("\e[31mError (debug)\e[m: Could not create breakpoint at: " + file_path.string() + ":" + std::to_string(line_nr) + '\n', true);
531 }
532 }
533
remove_breakpoint(const boost::filesystem::path & file_path,int line_nr,int line_count)534 void Debug::LLDB::remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count) {
535 LockGuard lock(mutex);
536 if(state == lldb::eStateStopped || state == lldb::eStateRunning) {
537 auto target = process->GetTarget();
538 for(int line_nr_try = line_nr; line_nr_try < line_count; line_nr_try++) {
539 for(uint32_t b_index = 0; b_index < target.GetNumBreakpoints(); b_index++) {
540 auto breakpoint = target.GetBreakpointAtIndex(b_index);
541 for(uint32_t l_index = 0; l_index < breakpoint.GetNumLocations(); l_index++) {
542 auto line_entry = breakpoint.GetLocationAtIndex(l_index).GetAddress().GetLineEntry();
543 if(line_entry.GetLine() == static_cast<uint32_t>(line_nr_try)) {
544 auto file_spec = line_entry.GetFileSpec();
545 auto breakpoint_path = filesystem::get_normal_path(file_spec.GetDirectory());
546 breakpoint_path /= file_spec.GetFilename();
547 if(breakpoint_path == file_path) {
548 if(!target.BreakpointDelete(breakpoint.GetID()))
549 Terminal::get().async_print("\e[31mError (debug)\e[m: Could not delete breakpoint at: " + file_path.string() + ":" + std::to_string(line_nr) + '\n', true);
550 return;
551 }
552 }
553 }
554 }
555 }
556 }
557 }
558
write(const std::string & buffer)559 void Debug::LLDB::write(const std::string &buffer) {
560 LockGuard lock(mutex);
561 if(state == lldb::StateType::eStateRunning) {
562 process->PutSTDIN(buffer.c_str(), buffer.size());
563 }
564 }
565