#include "debug_lldb.h" #include #ifdef __APPLE__ #include #endif #include #include #include "terminal.h" #include "filesystem.h" #include "process.hpp" #include "config.h" #include #include #include #include #include #include #include #include #include #include using namespace std; //TODO: remove 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) { #ifdef __APPLE__ auto debugserver_path=boost::filesystem::path("/usr/local/opt/llvm/bin/debugserver"); if(boost::filesystem::exists(debugserver_path)) setenv("LLDB_DEBUGSERVER_PATH", debugserver_path.string().c_str(), 0); #endif } void Debug::LLDB::start(const std::string &command, const boost::filesystem::path &path, const std::vector > &breakpoints, std::function callback, std::function status_callback, std::function stop_callback, 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 std::string executable; std::vector arguments; size_t start_pos=std::string::npos; bool quote=false; bool double_quote=false; bool symbol=false; for(size_t c=0;c<=command.size();c++) { if(c==command.size() || (!quote && !double_quote && !symbol && command[c]==' ')) { if(c>0 && start_pos!=std::string::npos) { if(executable.empty()) executable=filesystem::unescape(command.substr(start_pos, c-start_pos)); else arguments.emplace_back(filesystem::unescape(command.substr(start_pos, c-start_pos))); start_pos=std::string::npos; } } else if(command[c]=='\\' && !quote && !double_quote) symbol=true; else if(symbol) symbol=false; else if(command[c]=='\'' && !double_quote) quote=!quote; else if(command[c]=='"' && !quote) double_quote=!double_quote; if(cCreateTarget(executable.c_str()); if(!target.IsValid()) { Terminal::get().async_print("Error (debug): Could not create debug target to: "+executable+'\n', true); if(callback) callback(-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); if(callback) callback(-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); if(callback) callback(-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; } } } const char *empty_parameter[1]; empty_parameter[0]=nullptr; process->RemoteLaunch(argv, empty_parameter, nullptr, nullptr, nullptr, nullptr, lldb::eLaunchFlagNone, false, error); if(!error.Fail()) process->Continue(); } else process = std::make_unique(target.Launch(*listener, argv, const_cast(environ), 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); if(callback) callback(-1); return; } if(debug_thread.joinable()) debug_thread.join(); debug_thread=std::thread([this, callback, status_callback, stop_callback]() { lldb::SBEvent event; while(true) { std::unique_lock lock(event_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; } } } //Update debug status lldb::SBStream stream; event.GetDescription(stream); std::string event_desc=stream.GetData(); event_desc.pop_back(); auto pos=event_desc.rfind(" = "); if(status_callback && pos!=std::string::npos) { auto status=event_desc.substr(pos+3); if(state==lldb::StateType::eStateStopped) { char buffer[100]; auto thread=process->GetSelectedThread(); auto n=thread.GetStopDescription(buffer, 100); if(n>0) status+=" ("+std::string(buffer, n<=100?n:100)+")"; auto line_entry=thread.GetSelectedFrame().GetLineEntry(); if(line_entry.IsValid()) { lldb::SBStream stream; line_entry.GetFileSpec().GetDescription(stream); status +=" - "+boost::filesystem::path(stream.GetData()).filename().string()+":"+std::to_string(line_entry.GetLine()); } } status_callback(status); } if(state==lldb::StateType::eStateStopped) { if(stop_callback) { auto line_entry=process->GetSelectedThread().GetSelectedFrame().GetLineEntry(); if(line_entry.IsValid()) { lldb::SBStream stream; line_entry.GetFileSpec().GetDescription(stream); auto column=line_entry.GetColumn(); if(column==0) column=1; stop_callback(filesystem::get_canonical_path(stream.GetData()), line_entry.GetLine(), column); } else stop_callback("", 0, 0); } } else if(state==lldb::StateType::eStateRunning) { stop_callback("", 0, 0); } else if(state==lldb::StateType::eStateExited) { auto exit_status=process->GetExitStatus(); if(callback) callback(exit_status); if(status_callback) status_callback(""); if(stop_callback) stop_callback("", 0, 0); process.reset(); this->state=lldb::StateType::eStateInvalid; return; } else if(state==lldb::StateType::eStateCrashed) { if(callback) callback(-1); if(status_callback) status_callback(""); if(stop_callback) stop_callback("", 0, 0); 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(event_mutex); if(state==lldb::StateType::eStateStopped) process->Continue(); } void Debug::LLDB::stop() { std::unique_lock lock(event_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(event_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(event_mutex); if(state==lldb::StateType::eStateStopped) { process->GetSelectedThread().StepOver(); } } void Debug::LLDB::step_into() { std::unique_lock lock(event_mutex); if(state==lldb::StateType::eStateStopped) { process->GetSelectedThread().StepInto(); } } void Debug::LLDB::step_out() { std::unique_lock lock(event_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(event_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); command_return.first=command_return_object.GetOutput(); command_return.second=command_return_object.GetError(); } return command_return; } std::vector Debug::LLDB::get_backtrace() { std::vector backtrace; std::unique_lock lock(event_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(event_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(event_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(event_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(event_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_canonical_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(event_mutex); return state==lldb::StateType::eStateInvalid; } bool Debug::LLDB::is_stopped() { std::unique_lock lock(event_mutex); return state==lldb::StateType::eStateStopped; } bool Debug::LLDB::is_running() { std::unique_lock lock(event_mutex); return state==lldb::StateType::eStateRunning; } void Debug::LLDB::add_breakpoint(const boost::filesystem::path &file_path, int line_nr) { std::unique_lock lock(event_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(event_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_canonical_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(event_mutex); if(state==lldb::StateType::eStateRunning) { process->PutSTDIN(buffer.c_str(), buffer.size()); } }