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