#include "debug_lldb.h" #include #ifdef __APPLE__ #include #endif #include #include #include "terminal.h" #include "filesystem.h" #include "process.hpp" #include "config.h" extern char **environ; void log(const char *msg, void *) { std::cout << "debugger log: " << msg << std::endl; } Debug::LLDB::LLDB(): state(lldb::StateType::eStateInvalid), buffer_size(131072) { if(!getenv("LLDB_DEBUGSERVER_PATH")) { #ifdef __APPLE__ std::string debug_server_path("/usr/local/opt/llvm/bin/debugserver"); if(boost::filesystem::exists(debug_server_path)) setenv("LLDB_DEBUGSERVER_PATH", debug_server_path.c_str(), 0); #else auto debug_server_path = filesystem::get_executable("lldb-server").string(); if(debug_server_path != "lldb-server") setenv("LLDB_DEBUGSERVER_PATH", debug_server_path.c_str(), 0); #endif } } 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='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 > &breakpoints, const std::string &remote_host) { if(!debugger) { 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); auto &arguments=std::get<2>(parsed_run_arguments); std::vector argv; for(size_t c=0;cCreateTarget(executable.c_str()); if(!target.IsValid()) { Terminal::get().async_print("Error (debug): 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("Error (debug): 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("Error (debug): ")+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; 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; for(auto &e: environment_from_arguments) environment.emplace_back(e.c_str()); size_t environ_size=0; while(environ[environ_size]!=nullptr) ++environ_size; for(size_t c=0;c(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("Error (debug): ")+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); debug_thread=std::thread([this]() { lldb::SBEvent event; while(true) { std::unique_lock 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;cGetNumThreads();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() { std::unique_lock lock(mutex); if(state==lldb::StateType::eStateStopped) process->Continue(); } void Debug::LLDB::stop() { std::unique_lock lock(mutex); if(state==lldb::StateType::eStateRunning) { auto error=process->Stop(); if(error.Fail()) Terminal::get().async_print(std::string("Error (debug): ")+error.GetCString()+'\n', true); } } void Debug::LLDB::kill() { std::unique_lock lock(mutex); if(process) { auto error=process->Kill(); if(error.Fail()) Terminal::get().async_print(std::string("Error (debug): ")+error.GetCString()+'\n', true); } } void Debug::LLDB::step_over() { std::unique_lock lock(mutex); if(state==lldb::StateType::eStateStopped) { process->GetSelectedThread().StepOver(); } } void Debug::LLDB::step_into() { std::unique_lock lock(mutex); if(state==lldb::StateType::eStateStopped) { process->GetSelectedThread().StepInto(); } } void Debug::LLDB::step_out() { std::unique_lock lock(mutex); if(state==lldb::StateType::eStateStopped) { process->GetSelectedThread().StepOut(); } } std::pair Debug::LLDB::run_command(const std::string &command) { std::pair command_return; std::unique_lock lock(mutex); 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() { std::vector backtrace; std::unique_lock lock(mutex); if(state==lldb::StateType::eStateStopped) { auto thread=process->GetSelectedThread(); for(uint32_t c_f=0;c_f Debug::LLDB::get_variables() { std::vector variables; std::unique_lock lock(mutex); if(state==lldb::StateType::eStateStopped) { for(uint32_t c_t=0;c_tGetNumThreads();c_t++) { auto thread=process->GetThreadAtIndex(c_t); for(uint32_t c_f=0;c_f lock(mutex); if(state==lldb::StateType::eStateStopped) { if(thread_index_id!=0) process->SetSelectedThreadByIndexID(thread_index_id); process->GetSelectedThread().SetSelectedFrame(frame_index);; } } void Debug::LLDB::cancel() { kill(); if(debug_thread.joinable()) debug_thread.join(); } std::string Debug::LLDB::get_value(const std::string &variable, const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) { std::string variable_value; std::unique_lock 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 for(uint32_t value_index=0;value_index lock(mutex); 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() { std::unique_lock lock(mutex); return state==lldb::StateType::eStateInvalid; } bool Debug::LLDB::is_stopped() { std::unique_lock lock(mutex); return state==lldb::StateType::eStateStopped; } bool Debug::LLDB::is_running() { std::unique_lock lock(mutex); return state==lldb::StateType::eStateRunning; } void Debug::LLDB::add_breakpoint(const boost::filesystem::path &file_path, int line_nr) { std::unique_lock 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("Error (debug): 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) { std::unique_lock lock(mutex); if(state==lldb::eStateStopped || state==lldb::eStateRunning) { auto target=process->GetTarget(); for(int line_nr_try=line_nr;line_nr_try(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("Error (debug): Could not delete breakpoint at: "+file_path.string()+":"+std::to_string(line_nr)+'\n', true); return; } } } } } } } void Debug::LLDB::write(const std::string &buffer) { std::unique_lock lock(mutex); if(state==lldb::StateType::eStateRunning) { process->PutSTDIN(buffer.c_str(), buffer.size()); } }