#include "debug_lldb.hpp" #include "config.hpp" #include "filesystem.hpp" #include "process.hpp" #include "terminal.hpp" #include "utility.hpp" #include #include extern char **environ; bool Debug::LLDB::initialized = false; void log(const char *msg, void *) { std::cout << "debugger log: " << msg << std::endl; } Debug::LLDB::LLDB() : state(lldb::StateType::eStateInvalid), buffer_size(131072) { #ifndef __APPLE__ auto debug_server_path = filesystem::get_executable("lldb-server").string(); if(debug_server_path != "lldb-server") Glib::setenv("LLDB_DEBUGSERVER_PATH", debug_server_path, false); #endif } void Debug::LLDB::destroy_() { { LockGuard lock(mutex); if(process) process->Kill(); } if(debug_thread.joinable()) debug_thread.join(); LockGuard lock(mutex); if(debugger) lldb::SBDebugger::Destroy(*debugger); } std::tuple, std::string, std::vector> Debug::LLDB::parse_run_arguments(const std::string &command) { std::vector environment; std::string executable; std::vector arguments; size_t start_pos = std::string::npos; bool quote = false; bool double_quote = false; size_t backslash_count = 0; for(size_t c = 0; c <= command.size(); c++) { if(c == command.size() || (!quote && !double_quote && backslash_count % 2 == 0 && command[c] == ' ')) { if(c > 0 && start_pos != std::string::npos) { auto argument = command.substr(start_pos, c - start_pos); if(executable.empty()) { // Check for environment variable bool env_arg = false; for(size_t c = 0; c < argument.size(); ++c) { if((argument[c] >= 'a' && argument[c] <= 'z') || (argument[c] >= 'A' && argument[c] <= 'Z') || (argument[c] >= '0' && argument[c] <= '9') || argument[c] == '_') continue; else if(argument[c] == '=' && c + 1 < argument.size()) { environment.emplace_back(argument.substr(0, c + 1) + filesystem::unescape_argument(argument.substr(c + 1))); env_arg = true; break; } else break; } if(!env_arg) executable = filesystem::unescape_argument(argument); } else arguments.emplace_back(filesystem::unescape_argument(argument)); start_pos = std::string::npos; } } else if(command[c] == '\'' && backslash_count % 2 == 0 && !double_quote) quote = !quote; else if(command[c] == '"' && backslash_count % 2 == 0 && !quote) double_quote = !double_quote; else if(command[c] == '\\' && !quote && !double_quote) ++backslash_count; else backslash_count = 0; if(c < command.size() && start_pos == std::string::npos && command[c] != ' ') start_pos = c; } return std::make_tuple(environment, executable, arguments); } void Debug::LLDB::start(const std::string &command, const boost::filesystem::path &path, const std::vector> &breakpoints, const std::vector &startup_commands, const std::string &remote_host) { LockGuard lock(mutex); if(!debugger) { initialized = true; lldb::SBDebugger::Initialize(); debugger = std::make_unique(lldb::SBDebugger::Create(true, log, nullptr)); listener = std::make_unique("juCi++ lldb listener"); } //Create executable string and argument array auto parsed_run_arguments = parse_run_arguments(command); auto &environment_from_arguments = std::get<0>(parsed_run_arguments); auto &executable = std::get<1>(parsed_run_arguments); #ifdef _WIN32 if(remote_host.empty()) executable += ".exe"; #endif auto &arguments = std::get<2>(parsed_run_arguments); std::vector argv; argv.reserve(arguments.size()); for(auto &argument : arguments) argv.emplace_back(argument.c_str()); argv.emplace_back(nullptr); executable = filesystem::get_absolute_path(executable, path).string(); auto target = debugger->CreateTarget(executable.c_str()); if(!target.IsValid()) { Terminal::get().async_print("\e[31mError (debug)\e[m: Could not create debug target to: " + executable + '\n', true); for(auto &handler : on_exit) handler(-1); return; } //Set breakpoints for(auto &breakpoint : breakpoints) { if(!(target.BreakpointCreateByLocation(breakpoint.first.string().c_str(), breakpoint.second)).IsValid()) { Terminal::get().async_print("\e[31mError (debug)\e[m: Could not create breakpoint at: " + breakpoint.first.string() + ":" + std::to_string(breakpoint.second) + '\n', true); for(auto &handler : on_exit) handler(-1); return; } } lldb::SBError error; if(!remote_host.empty()) { auto connect_string = "connect://" + remote_host; process = std::make_unique(target.ConnectRemote(*listener, connect_string.c_str(), "gdb-remote", error)); if(error.Fail()) { Terminal::get().async_print(std::string("\e[31mError (debug)\e[m: ") + error.GetCString() + '\n', true); for(auto &handler : on_exit) handler(-1); return; } lldb::SBEvent event; while(true) { if(listener->GetNextEvent(event)) { if((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged) > 0) { auto state = process->GetStateFromEvent(event); this->state = state; if(state == lldb::StateType::eStateConnected) break; } } } // Create environment array std::vector environment; environment.reserve(environment_from_arguments.size()); for(auto &e : environment_from_arguments) environment.emplace_back(e.c_str()); environment.emplace_back(nullptr); process->RemoteLaunch(argv.data(), environment.data(), nullptr, nullptr, nullptr, nullptr, lldb::eLaunchFlagNone, false, error); if(!error.Fail()) process->Continue(); } else { // Create environment array std::vector environment; environment.reserve(environment_from_arguments.size()); for(auto &e : environment_from_arguments) environment.emplace_back(e.c_str()); size_t environ_size = 0; while(environ[environ_size]) ++environ_size; for(size_t c = 0; c < environ_size; ++c) environment.emplace_back(environ[c]); environment.emplace_back(nullptr); process = std::make_unique(target.Launch(*listener, argv.data(), environment.data(), nullptr, nullptr, nullptr, path.string().c_str(), lldb::eLaunchFlagNone, false, error)); } if(error.Fail()) { Terminal::get().async_print(std::string("\e[31mError (debug)\e[m: ") + error.GetCString() + '\n', true); for(auto &handler : on_exit) handler(-1); return; } if(debug_thread.joinable()) debug_thread.join(); for(auto &handler : on_start) handler(*process); for(auto &command : startup_commands) { lldb::SBCommandReturnObject command_return_object; debugger->GetCommandInterpreter().HandleCommand(command.c_str(), command_return_object, false); } debug_thread = std::thread([this]() { lldb::SBEvent event; while(true) { LockGuard lock(mutex); if(listener->GetNextEvent(event)) { if((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged) > 0) { auto state = process->GetStateFromEvent(event); this->state = state; if(state == lldb::StateType::eStateStopped) { for(uint32_t c = 0; c < process->GetNumThreads(); c++) { auto thread = process->GetThreadAtIndex(c); if(thread.GetStopReason() >= 2) { process->SetSelectedThreadByIndexID(thread.GetIndexID()); break; } } } lock.unlock(); for(auto &handler : on_event) handler(event); lock.lock(); if(state == lldb::StateType::eStateExited || state == lldb::StateType::eStateCrashed) { auto exit_status = state == lldb::StateType::eStateCrashed ? -1 : process->GetExitStatus(); lock.unlock(); for(auto &handler : on_exit) handler(exit_status); lock.lock(); process.reset(); this->state = lldb::StateType::eStateInvalid; return; } } if((event.GetType() & lldb::SBProcess::eBroadcastBitSTDOUT) > 0) { char buffer[buffer_size]; size_t n; while((n = process->GetSTDOUT(buffer, buffer_size)) != 0) Terminal::get().async_print(std::string(buffer, n)); } //TODO: for some reason stderr is redirected to stdout if((event.GetType() & lldb::SBProcess::eBroadcastBitSTDERR) > 0) { char buffer[buffer_size]; size_t n; while((n = process->GetSTDERR(buffer, buffer_size)) != 0) Terminal::get().async_print(std::string(buffer, n), true); } } lock.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(200)); } }); } void Debug::LLDB::continue_debug() { LockGuard lock(mutex); if(state == lldb::StateType::eStateStopped) process->Continue(); } void Debug::LLDB::stop() { LockGuard lock(mutex); if(state == lldb::StateType::eStateRunning) { auto error = process->Stop(); if(error.Fail()) Terminal::get().async_print(std::string("\e[31mError (debug)\e[m: ") + error.GetCString() + '\n', true); } } void Debug::LLDB::kill() { LockGuard lock(mutex); if(process) { auto error = process->Kill(); if(error.Fail()) Terminal::get().async_print(std::string("\e[31mError (debug)\e[m: ") + error.GetCString() + '\n', true); } } void Debug::LLDB::step_over() { LockGuard lock(mutex); if(state == lldb::StateType::eStateStopped) { process->GetSelectedThread().StepOver(); } } void Debug::LLDB::step_into() { LockGuard lock(mutex); if(state == lldb::StateType::eStateStopped) { process->GetSelectedThread().StepInto(); } } void Debug::LLDB::step_out() { LockGuard lock(mutex); if(state == lldb::StateType::eStateStopped) { process->GetSelectedThread().StepOut(); } } std::pair Debug::LLDB::run_command(const std::string &command) { LockGuard lock(mutex); std::pair command_return; if(state == lldb::StateType::eStateStopped || state == lldb::StateType::eStateRunning) { lldb::SBCommandReturnObject command_return_object; debugger->GetCommandInterpreter().HandleCommand(command.c_str(), command_return_object, true); auto output = command_return_object.GetOutput(); if(output) command_return.first = output; auto error = command_return_object.GetError(); if(error) command_return.second = error; } return command_return; } std::vector Debug::LLDB::get_backtrace() { LockGuard lock(mutex); std::vector backtrace; if(state == lldb::StateType::eStateStopped) { auto thread = process->GetSelectedThread(); for(uint32_t c_f = 0; c_f < thread.GetNumFrames(); c_f++) { Frame backtrace_frame; auto frame = thread.GetFrameAtIndex(c_f); backtrace_frame.index = c_f; if(auto function_name = frame.GetFunctionName()) backtrace_frame.function_name = function_name; if(auto module_filename = frame.GetModule().GetFileSpec().GetFilename()) backtrace_frame.module_filename = module_filename; auto line_entry = frame.GetLineEntry(); if(line_entry.IsValid()) { lldb::SBStream stream; line_entry.GetFileSpec().GetDescription(stream); auto column = line_entry.GetColumn(); if(column == 0) column = 1; backtrace_frame.file_path = filesystem::get_normal_path(stream.GetData()); backtrace_frame.line_nr = line_entry.GetLine(); backtrace_frame.line_index = column; } backtrace.emplace_back(backtrace_frame); } } return backtrace; } std::vector Debug::LLDB::get_variables() { LockGuard lock(mutex); std::vector variables; if(state == lldb::StateType::eStateStopped) { for(uint32_t c_t = 0; c_t < process->GetNumThreads(); c_t++) { auto thread = process->GetThreadAtIndex(c_t); for(uint32_t c_f = 0; c_f < thread.GetNumFrames(); c_f++) { auto frame = thread.GetFrameAtIndex(c_f); auto values = frame.GetVariables(true, true, true, false); for(uint32_t value_index = 0; value_index < values.GetSize(); value_index++) { auto value = std::make_shared(values.GetValueAtIndex(value_index)); Debug::LLDB::Variable variable; variable.thread_index_id = thread.GetIndexID(); variable.frame_index = c_f; if(auto name = value->GetName()) variable.name = name; variable.get_value = [value]() { lldb::SBStream stream; value->GetDescription(stream); return std::string(stream.GetData()); }; auto declaration = value->GetDeclaration(); if(declaration.IsValid()) { variable.declaration_found = true; variable.line_nr = declaration.GetLine(); variable.line_index = declaration.GetColumn(); if(variable.line_index == 0) variable.line_index = 1; auto file_spec = declaration.GetFileSpec(); variable.file_path = filesystem::get_normal_path(file_spec.GetDirectory()); variable.file_path /= file_spec.GetFilename(); } else { variable.declaration_found = false; auto line_entry = frame.GetLineEntry(); if(line_entry.IsValid()) { variable.line_nr = line_entry.GetLine(); variable.line_index = line_entry.GetColumn(); if(variable.line_index == 0) variable.line_index = 1; auto file_spec = line_entry.GetFileSpec(); variable.file_path = filesystem::get_normal_path(file_spec.GetDirectory()); variable.file_path /= file_spec.GetFilename(); } } variables.emplace_back(variable); } } } } return variables; } void Debug::LLDB::select_frame(uint32_t frame_index, uint32_t thread_index_id) { LockGuard lock(mutex); if(state == lldb::StateType::eStateStopped) { if(thread_index_id != 0) process->SetSelectedThreadByIndexID(thread_index_id); process->GetSelectedThread().SetSelectedFrame(frame_index); ; } } std::string Debug::LLDB::get_value(const std::string &variable, const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) { LockGuard lock(mutex); if(state == lldb::StateType::eStateStopped) { auto frame = process->GetSelectedThread().GetSelectedFrame(); auto values = frame.GetVariables(true, true, true, false); //First try to find variable based on name, file and line number if(!file_path.empty()) { for(uint32_t value_index = 0; value_index < values.GetSize(); value_index++) { lldb::SBStream stream; auto value = values.GetValueAtIndex(value_index); if(auto name = value.GetName()) { if(name == variable) { auto declaration = value.GetDeclaration(); if(declaration.IsValid()) { if(declaration.GetLine() == line_nr && (declaration.GetColumn() == 0 || declaration.GetColumn() == line_index)) { auto file_spec = declaration.GetFileSpec(); auto value_decl_path = filesystem::get_normal_path(file_spec.GetDirectory()); value_decl_path /= file_spec.GetFilename(); if(value_decl_path == file_path) { value.GetDescription(stream); std::string variable_value = stream.GetData(); if(ends_with(variable_value, "\n\n")) variable_value.pop_back(); // Remove newline at end of string return variable_value; } } } } } } } } return {}; } std::string Debug::LLDB::get_value(const std::string &expression) { LockGuard lock(mutex); std::string variable_value; if(state == lldb::StateType::eStateStopped) { auto frame = process->GetSelectedThread().GetSelectedFrame(); if(variable_value.empty()) { // Attempt to get variable from variable expression, using the faster GetValueForVariablePath auto value = frame.GetValueForVariablePath(expression.c_str()); if(value.IsValid()) { lldb::SBStream stream; value.GetDescription(stream); variable_value = stream.GetData(); } } if(variable_value.empty()) { // Attempt to get variable from variable expression, using the slower EvaluateExpression auto value = frame.EvaluateExpression(expression.c_str()); if(value.IsValid() && !value.GetError().Fail()) { lldb::SBStream stream; value.GetDescription(stream); variable_value = stream.GetData(); } } } return variable_value; } std::string Debug::LLDB::get_return_value(const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) { LockGuard lock(mutex); std::string return_value; if(state == lldb::StateType::eStateStopped) { auto thread = process->GetSelectedThread(); auto thread_return_value = thread.GetStopReturnValue(); if(thread_return_value.IsValid()) { auto line_entry = thread.GetSelectedFrame().GetLineEntry(); if(line_entry.IsValid()) { lldb::SBStream stream; line_entry.GetFileSpec().GetDescription(stream); if(filesystem::get_normal_path(stream.GetData()) == file_path && line_entry.GetLine() == line_nr && (line_entry.GetColumn() == 0 || line_entry.GetColumn() == line_index)) { lldb::SBStream stream; thread_return_value.GetDescription(stream); return_value = stream.GetData(); } } } } return return_value; } bool Debug::LLDB::is_invalid() { LockGuard lock(mutex); return state == lldb::StateType::eStateInvalid; } bool Debug::LLDB::is_stopped() { LockGuard lock(mutex); return state == lldb::StateType::eStateStopped; } bool Debug::LLDB::is_running() { LockGuard lock(mutex); return state == lldb::StateType::eStateRunning; } void Debug::LLDB::add_breakpoint(const boost::filesystem::path &file_path, int line_nr) { LockGuard lock(mutex); if(state == lldb::eStateStopped || state == lldb::eStateRunning) { if(!(process->GetTarget().BreakpointCreateByLocation(file_path.string().c_str(), line_nr)).IsValid()) Terminal::get().async_print("\e[31mError (debug)\e[m: Could not create breakpoint at: " + file_path.string() + ":" + std::to_string(line_nr) + '\n', true); } } void Debug::LLDB::remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count) { LockGuard lock(mutex); if(state == lldb::eStateStopped || state == lldb::eStateRunning) { auto target = process->GetTarget(); for(int line_nr_try = line_nr; line_nr_try < line_count; line_nr_try++) { for(uint32_t b_index = 0; b_index < target.GetNumBreakpoints(); b_index++) { auto breakpoint = target.GetBreakpointAtIndex(b_index); for(uint32_t l_index = 0; l_index < breakpoint.GetNumLocations(); l_index++) { auto line_entry = breakpoint.GetLocationAtIndex(l_index).GetAddress().GetLineEntry(); if(line_entry.GetLine() == static_cast(line_nr_try)) { auto file_spec = line_entry.GetFileSpec(); auto breakpoint_path = filesystem::get_normal_path(file_spec.GetDirectory()); breakpoint_path /= file_spec.GetFilename(); if(breakpoint_path == file_path) { if(!target.BreakpointDelete(breakpoint.GetID())) Terminal::get().async_print("\e[31mError (debug)\e[m: Could not delete breakpoint at: " + file_path.string() + ":" + std::to_string(line_nr) + '\n', true); return; } } } } } } } void Debug::LLDB::write(const std::string &buffer) { LockGuard lock(mutex); if(state == lldb::StateType::eStateRunning) { process->PutSTDIN(buffer.c_str(), buffer.size()); } }