mirror of
https://github.com/polserver/polserver
synced 2026-08-13 08:23:08 -04:00
952 lines
No EOL
26 KiB
C++
952 lines
No EOL
26 KiB
C++
#include "clientthread.h"
|
|
|
|
#include "../../bscript/bstruct.h"
|
|
#include "../../bscript/dict.h"
|
|
#include "../../bscript/eprog.h"
|
|
#include "../../bscript/impstr.h"
|
|
#include "../../clib/esignal.h"
|
|
#include "../../clib/fileutil.h"
|
|
#include "../../clib/logfacility.h"
|
|
#include "../../plib/pkg.h"
|
|
#include "../../plib/systemstate.h"
|
|
#include "../module/uomod.h"
|
|
#include "../polsem.h"
|
|
#include "../scrdef.h"
|
|
#include "../scrsched.h"
|
|
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace Pol
|
|
{
|
|
using namespace Core;
|
|
using namespace Bscript;
|
|
using namespace EscriptGrammar;
|
|
using namespace Compiler;
|
|
|
|
namespace Network
|
|
{
|
|
namespace DAP
|
|
{
|
|
namespace fs = std::filesystem;
|
|
|
|
unsigned int DebugClientThread::_instance_counter = 0;
|
|
|
|
DebugClientThread::DebugClientThread( const std::shared_ptr<dap::ReaderWriter>& rw )
|
|
: _instance( ++_instance_counter ),
|
|
_rw( rw ),
|
|
_session( dap::Session::create() ),
|
|
_uoexec_wptr( nullptr ),
|
|
_expression_evaluator(),
|
|
_global_scope_handle( 0 ),
|
|
_was_launch_requested( false ),
|
|
_exit_sent( false )
|
|
{
|
|
}
|
|
|
|
void DebugClientThread::on_halt()
|
|
{
|
|
dap::StoppedEvent event;
|
|
event.reason = "pause";
|
|
event.threadId = 1;
|
|
_session->send( event );
|
|
}
|
|
|
|
void DebugClientThread::on_destroy()
|
|
{
|
|
POLLOG_INFOLN( "Debugger#{} script destroyed.", _instance );
|
|
dap::ExitedEvent event;
|
|
event.exitCode = 0;
|
|
_session->send( event );
|
|
_exit_sent = true;
|
|
}
|
|
|
|
void DebugClientThread::on_print( const std::string& output )
|
|
{
|
|
dap::OutputEvent event;
|
|
event.category = "stdout";
|
|
event.output = output + "\n";
|
|
|
|
// The `ExecutorDebugListener` methods are called from the script thread
|
|
// (which runs inside a PolLock), so no need to lock. Additionally, the
|
|
// `UOExecutor` should always exist, as these methods are all called within an
|
|
// `Executor` instance method, but check just in case. Not a real performance
|
|
// hit, since this is only called when printing with the debugger is attached.
|
|
if ( _uoexec_wptr.exists() )
|
|
{
|
|
UOExecutor* exec = _uoexec_wptr.get_weakptr();
|
|
dap::Source source;
|
|
source.path = _script->dbg_filenames[_script->dbg_filenum[exec->PC]];
|
|
event.source = source;
|
|
event.line = _script->dbg_linenum[exec->PC];
|
|
}
|
|
|
|
_session->send( event );
|
|
}
|
|
|
|
dap::ConfigurationDoneResponse DebugClientThread::handle_configurationDone(
|
|
const dap::ConfigurationDoneRequest& )
|
|
{
|
|
return dap::ConfigurationDoneResponse{};
|
|
}
|
|
|
|
dap::ResponseOrError<dap::AttachResponse> DebugClientThread::handle_attach(
|
|
const dap::PolAttachRequest& request )
|
|
{
|
|
PolLock lock;
|
|
UOExecutor* uoexec;
|
|
|
|
if ( _uoexec_wptr.exists() )
|
|
{
|
|
uoexec = _uoexec_wptr.get_weakptr();
|
|
if ( uoexec->in_debugger_holdlist() )
|
|
uoexec->revive_debugged();
|
|
|
|
_variable_handles.reset();
|
|
_global_scope_handle = 0;
|
|
uoexec->detach_debugger();
|
|
_uoexec_wptr.clear();
|
|
}
|
|
|
|
auto pid = static_cast<unsigned int>( request.pid );
|
|
if ( find_uoexec( pid, &uoexec ) )
|
|
{
|
|
EScriptProgram* prog = const_cast<EScriptProgram*>( uoexec->prog() );
|
|
|
|
if ( prog->read_dbg_file() == 0 )
|
|
{
|
|
if ( !uoexec->attach_debugger( weak_from_this() ) )
|
|
return dap::Error( "Debugger already attached." );
|
|
|
|
_uoexec_wptr = uoexec->weakptr;
|
|
_script.set( prog );
|
|
|
|
return dap::AttachResponse{};
|
|
}
|
|
return dap::Error( "No debug information available." );
|
|
}
|
|
return dap::Error( "Unknown process id '%d'", int( pid ) );
|
|
}
|
|
|
|
dap::ResponseOrError<dap::LaunchResponse> DebugClientThread::handle_launch(
|
|
const dap::PolLaunchRequest& request )
|
|
{
|
|
PolLock lock;
|
|
ScriptDef sd;
|
|
|
|
fs::path p( request.script );
|
|
|
|
if ( p.extension().u8string() == ".src" )
|
|
p.replace_extension( fs::path( ".ecl" ) );
|
|
|
|
bool config_result;
|
|
|
|
// A package-path
|
|
if ( request.script.find( ':' ) == 0 )
|
|
{
|
|
config_result = sd.config_nodie( p.u8string(), nullptr, "scripts/" );
|
|
}
|
|
else
|
|
{
|
|
std::string relative = ( p.is_absolute() ? fs::relative( p ) : p ).u8string();
|
|
|
|
if ( relative.find( "scripts/" ) == 0 )
|
|
{
|
|
config_result = sd.config_nodie( relative.substr( 8 ), nullptr, "scripts/" );
|
|
}
|
|
else
|
|
{
|
|
const Plib::Package* package = nullptr;
|
|
for ( const auto* pkg : Plib::systemstate.packages )
|
|
{
|
|
if ( relative.find( pkg->dir() ) == 0 )
|
|
{
|
|
package = pkg;
|
|
break;
|
|
}
|
|
}
|
|
if ( package )
|
|
{
|
|
config_result =
|
|
sd.config_nodie( relative.substr( package->dir().length() ), package, "scripts/" );
|
|
}
|
|
else
|
|
{
|
|
config_result = sd.config_nodie( request.script, nullptr, "scripts/" );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !config_result )
|
|
return dap::Error( "Error in script name." );
|
|
if ( !sd.exists() )
|
|
return dap::Error( "Script " + sd.name() + " does not exist." );
|
|
|
|
Module::UOExecutorModule* new_uoemod;
|
|
|
|
if ( request.arg.has_value() && request.arg->length() > 0 )
|
|
{
|
|
new_uoemod = Core::start_script( sd, BObjectImp::unpack( request.arg->c_str() ) );
|
|
}
|
|
else
|
|
{
|
|
new_uoemod = Core::start_script( sd, nullptr );
|
|
}
|
|
|
|
if ( new_uoemod == nullptr )
|
|
{
|
|
return dap::Error( "Unable to start script" );
|
|
}
|
|
|
|
UOExecutor* uoexec = static_cast<UOExecutor*>( &new_uoemod->exec );
|
|
EScriptProgram* prog = const_cast<EScriptProgram*>( uoexec->prog() );
|
|
|
|
if ( prog->read_dbg_file() == 0 )
|
|
{
|
|
bool stopAtEntry = request.stopAtEntry.value( false );
|
|
if ( !uoexec->attach_debugger( weak_from_this(), stopAtEntry ) )
|
|
return dap::Error( "Debugger already attached." );
|
|
|
|
_uoexec_wptr = uoexec->weakptr;
|
|
_script.set( prog );
|
|
|
|
_was_launch_requested = true;
|
|
|
|
return dap::LaunchResponse{};
|
|
}
|
|
return dap::Error( "No debug information available." );
|
|
}
|
|
|
|
dap::ResponseOrError<dap::PolProcessesResponse> DebugClientThread::handle_processes(
|
|
const dap::PolProcessesRequest& request )
|
|
{
|
|
PolLock lock;
|
|
dap::PolProcessesResponse response;
|
|
|
|
for ( const auto& [pid, uoexec] : scriptScheduler.getPidlist() )
|
|
{
|
|
if ( !request.filter.has_value() ||
|
|
boost::icontains( uoexec->scriptname(), request.filter.value() ) )
|
|
{
|
|
dap::PolProcess entry;
|
|
entry.id = pid;
|
|
entry.script = uoexec->scriptname();
|
|
|
|
if ( uoexec->halt() )
|
|
entry.state = 2; // debugging
|
|
else if ( uoexec->in_hold_list() == NO_LIST )
|
|
entry.state = 1; // running
|
|
else
|
|
entry.state = 0; // sleeping
|
|
|
|
response.processes.push_back( entry );
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
dap::ResponseOrError<dap::ThreadsResponse> DebugClientThread::handle_threads(
|
|
const dap::ThreadsRequest& )
|
|
{
|
|
PolLock lock;
|
|
if ( !_uoexec_wptr.exists() )
|
|
return dap::Error( "No script attached." );
|
|
|
|
UOExecutor* exec = _uoexec_wptr.get_weakptr();
|
|
dap::ThreadsResponse response;
|
|
dap::Thread thread;
|
|
thread.id = 1;
|
|
thread.name = Clib::tostring( exec->pid() );
|
|
response.threads.push_back( thread );
|
|
return response;
|
|
}
|
|
|
|
dap::ResponseOrError<dap::SetExceptionBreakpointsResponse>
|
|
DebugClientThread::handle_setExceptionBreakpoints( const dap::SetExceptionBreakpointsRequest& )
|
|
{
|
|
return dap::SetExceptionBreakpointsResponse{};
|
|
}
|
|
|
|
dap::ResponseOrError<dap::ContinueResponse> DebugClientThread::handle_continue(
|
|
const dap::ContinueRequest& )
|
|
{
|
|
PolLock lock;
|
|
if ( !_uoexec_wptr.exists() )
|
|
{
|
|
return dap::Error( "No script attached." );
|
|
}
|
|
|
|
UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
|
|
if ( !uoexec->in_debugger_holdlist() )
|
|
{
|
|
return dap::Error( "Script not ready to trace." );
|
|
}
|
|
|
|
_variable_handles.reset();
|
|
_global_scope_handle = 0;
|
|
uoexec->dbg_run();
|
|
uoexec->revive_debugged();
|
|
return dap::ContinueResponse{};
|
|
}
|
|
|
|
dap::ResponseOrError<dap::PauseResponse> DebugClientThread::handle_pause( const dap::PauseRequest& )
|
|
{
|
|
PolLock lock;
|
|
if ( !_uoexec_wptr.exists() )
|
|
{
|
|
return dap::Error( "No script attached." );
|
|
}
|
|
|
|
UOExecutor* exec = _uoexec_wptr.get_weakptr();
|
|
exec->dbg_break();
|
|
return dap::PauseResponse{};
|
|
}
|
|
|
|
dap::ResponseOrError<dap::EvaluateResponse> DebugClientThread::handle_evaluate(
|
|
const dap::EvaluateRequest& request )
|
|
{
|
|
PolLock lock;
|
|
if ( !_uoexec_wptr.exists() )
|
|
{
|
|
return dap::Error( "No script attached." );
|
|
}
|
|
|
|
BObjectRef result;
|
|
try
|
|
{
|
|
result = _expression_evaluator.evaluate( _uoexec_wptr.get_weakptr(), _script.get(),
|
|
request.expression );
|
|
}
|
|
catch ( std::exception& ex )
|
|
{
|
|
return dap::Error( ex.what() );
|
|
}
|
|
|
|
dap::EvaluateResponse response;
|
|
_variable_handles.set_response_details( result, response );
|
|
return response;
|
|
}
|
|
|
|
dap::ResponseOrError<dap::SetVariableResponse> DebugClientThread::handle_setVariable(
|
|
const dap::SetVariableRequest& request )
|
|
{
|
|
PolLock lock;
|
|
if ( !_uoexec_wptr.exists() )
|
|
{
|
|
return dap::Error( "No script attached." );
|
|
}
|
|
|
|
BObjectRef value;
|
|
try
|
|
{
|
|
value =
|
|
_expression_evaluator.evaluate( _uoexec_wptr.get_weakptr(), _script.get(), request.value );
|
|
}
|
|
catch ( std::exception& ex )
|
|
{
|
|
return dap::Error( ex.what() );
|
|
}
|
|
|
|
dap::SetVariableResponse response;
|
|
|
|
auto reference_ptr = _variable_handles.get( static_cast<int>( request.variablesReference ) );
|
|
|
|
if ( reference_ptr == nullptr )
|
|
{
|
|
return dap::Error( "Invalid variablesReference id %d", int( request.variablesReference ) );
|
|
}
|
|
|
|
std::visit(
|
|
[&]( auto&& arg )
|
|
{
|
|
using T = std::decay_t<decltype( arg )>;
|
|
if constexpr ( (std::is_same_v<T, GlobalReference> || std::is_same_v<T, FrameReference>))
|
|
{
|
|
for ( auto& [key, member] : arg.contents )
|
|
{
|
|
if ( key == request.name )
|
|
{
|
|
*member = value;
|
|
_variable_handles.set_response_details( value, response );
|
|
}
|
|
}
|
|
}
|
|
else if constexpr ( std::is_same_v<T, VariableReference> )
|
|
{
|
|
auto value_set = _variable_handles.set_index_or_member( arg, request.name, value );
|
|
_variable_handles.set_response_details( value_set, response );
|
|
}
|
|
},
|
|
*reference_ptr );
|
|
|
|
return response;
|
|
}
|
|
|
|
dap::ResponseOrError<dap::NextResponse> DebugClientThread::handle_next( const dap::NextRequest& )
|
|
{
|
|
PolLock lock;
|
|
if ( !_uoexec_wptr.exists() )
|
|
{
|
|
return dap::Error( "No script attached." );
|
|
}
|
|
|
|
UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
|
|
if ( !uoexec->in_debugger_holdlist() )
|
|
{
|
|
return dap::Error( "Script not ready to trace." );
|
|
}
|
|
|
|
_variable_handles.reset();
|
|
_global_scope_handle = 0;
|
|
uoexec->dbg_step_over();
|
|
uoexec->revive_debugged();
|
|
return dap::NextResponse{};
|
|
}
|
|
|
|
dap::ResponseOrError<dap::StepInResponse> DebugClientThread::handle_stepIn(
|
|
const dap::StepInRequest& )
|
|
{
|
|
PolLock lock;
|
|
if ( !_uoexec_wptr.exists() )
|
|
{
|
|
return dap::Error( "No script attached." );
|
|
}
|
|
|
|
UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
|
|
if ( !uoexec->in_debugger_holdlist() )
|
|
{
|
|
return dap::Error( "Script not ready to trace." );
|
|
}
|
|
|
|
_variable_handles.reset();
|
|
_global_scope_handle = 0;
|
|
uoexec->dbg_step_into();
|
|
uoexec->revive_debugged();
|
|
return dap::StepInResponse{};
|
|
}
|
|
|
|
dap::ResponseOrError<dap::SetBreakpointsResponse> DebugClientThread::handle_setBreakpoints(
|
|
const dap::SetBreakpointsRequest& request )
|
|
{
|
|
PolLock lock;
|
|
if ( !_uoexec_wptr.exists() )
|
|
{
|
|
return dap::Error( "No script attached." );
|
|
}
|
|
|
|
UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
|
|
|
|
unsigned int filenum;
|
|
|
|
if ( request.source.sourceReference.has_value() )
|
|
{
|
|
filenum = static_cast<unsigned int>( request.source.sourceReference.value() );
|
|
}
|
|
else
|
|
{
|
|
if ( !request.source.path.has_value() )
|
|
{
|
|
return dap::Error( "No source location provided." );
|
|
}
|
|
|
|
auto filename_iter = std::find( _script->dbg_filenames.begin(), _script->dbg_filenames.end(),
|
|
request.source.path.value() );
|
|
|
|
if ( filename_iter == _script->dbg_filenames.end() )
|
|
{
|
|
return dap::Error( "File not in scope" );
|
|
}
|
|
|
|
filenum =
|
|
static_cast<unsigned int>( std::distance( _script->dbg_filenames.begin(), filename_iter ) );
|
|
}
|
|
|
|
if ( filenum >= _script->dbg_filenames.size() )
|
|
{
|
|
return dap::Error( "File %d out of range", filenum );
|
|
}
|
|
|
|
std::set<unsigned> remove_bps;
|
|
for ( unsigned i = 0; i < _script->dbg_filenum.size(); ++i )
|
|
{
|
|
if ( _script->dbg_filenum[i] == filenum )
|
|
{
|
|
remove_bps.insert( i );
|
|
}
|
|
}
|
|
uoexec->dbg_clrbps( remove_bps );
|
|
|
|
dap::SetBreakpointsResponse response;
|
|
|
|
auto breakpoints = request.breakpoints.value( {} );
|
|
response.breakpoints.resize( breakpoints.size() );
|
|
|
|
for ( size_t breakpoint_index = 0; breakpoint_index < breakpoints.size(); breakpoint_index++ )
|
|
{
|
|
// Conditional breakpoints are not supported.
|
|
if ( breakpoints[breakpoint_index].condition )
|
|
{
|
|
response.breakpoints[breakpoint_index].verified = false;
|
|
continue;
|
|
}
|
|
|
|
for ( unsigned i = 0; i < _script->dbg_filenum.size(); ++i )
|
|
{
|
|
if ( _script->dbg_filenum[i] == filenum &&
|
|
_script->dbg_linenum[i] == breakpoints[breakpoint_index].line )
|
|
{
|
|
response.breakpoints[breakpoint_index].verified = true;
|
|
uoexec->dbg_setbp( i );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
dap::ResponseOrError<dap::SourceResponse> DebugClientThread::handle_source(
|
|
const dap::SourceRequest& request )
|
|
{
|
|
PolLock lock;
|
|
if ( !_uoexec_wptr.exists() )
|
|
{
|
|
return dap::Error( "No script attached." );
|
|
}
|
|
|
|
size_t filenum = request.sourceReference;
|
|
if ( filenum >= _script->dbg_filenames.size() )
|
|
{
|
|
return dap::Error( "File out of range: '%d'", int( filenum ) );
|
|
}
|
|
|
|
auto filepath = _script->dbg_filenames[filenum];
|
|
|
|
std::ifstream ifs( filepath );
|
|
if ( !ifs.is_open() )
|
|
return dap::Error( "File # out of range" );
|
|
|
|
std::stringstream buffer;
|
|
buffer << ifs.rdbuf();
|
|
|
|
dap::SourceResponse response;
|
|
response.content = buffer.str();
|
|
return response;
|
|
}
|
|
|
|
dap::ResponseOrError<dap::StackTraceResponse> DebugClientThread::handle_stackTrace(
|
|
const dap::StackTraceRequest& )
|
|
{
|
|
PolLock lock;
|
|
if ( !_uoexec_wptr.exists() )
|
|
{
|
|
return dap::Error( "No script attached." );
|
|
}
|
|
|
|
UOExecutor* exec = _uoexec_wptr.get_weakptr();
|
|
|
|
if ( !exec->halt() )
|
|
{
|
|
return dap::Error( "Script must be halted to retrieve stack trace." );
|
|
}
|
|
|
|
std::vector<ReturnContext> stack = exec->ControlStack;
|
|
dap::StackTraceResponse response;
|
|
|
|
unsigned int PC;
|
|
|
|
{
|
|
ReturnContext rc;
|
|
rc.PC = exec->PC;
|
|
rc.ValueStackDepth = static_cast<unsigned int>( exec->ValueStack.size() );
|
|
stack.push_back( rc );
|
|
}
|
|
|
|
// Bottom frame starts at index 1
|
|
auto frameId = exec->ControlStack.size() + 1;
|
|
|
|
while ( !stack.empty() )
|
|
{
|
|
ReturnContext& rc = stack.back();
|
|
PC = rc.PC;
|
|
stack.pop_back();
|
|
|
|
dap::Source source;
|
|
|
|
auto filepath = _script->dbg_filenames[_script->dbg_filenum[PC]];
|
|
fs::path p( filepath );
|
|
std::string abs_path = ( p.is_relative() ? ( fs::current_path() / p ) : p ).u8string();
|
|
|
|
source.name = abs_path;
|
|
source.path = abs_path;
|
|
|
|
dap::StackFrame frame;
|
|
frame.line = _script->dbg_linenum[PC];
|
|
frame.column = 1;
|
|
auto dbgFunction = std::find_if( _script->dbg_functions.begin(), _script->dbg_functions.end(),
|
|
[&]( auto& i ) { return i.firstPC <= PC && PC <= i.lastPC; } );
|
|
|
|
if ( dbgFunction != _script->dbg_functions.end() )
|
|
{
|
|
frame.name = dbgFunction->name;
|
|
}
|
|
else
|
|
{
|
|
frame.name = "(program)";
|
|
}
|
|
|
|
frame.id = frameId--;
|
|
frame.source = source;
|
|
|
|
response.stackFrames.push_back( frame );
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
dap::ResponseOrError<dap::ScopesResponse> DebugClientThread::handle_scopes(
|
|
const dap::ScopesRequest& request )
|
|
{
|
|
PolLock lock;
|
|
if ( !_uoexec_wptr.exists() )
|
|
{
|
|
return dap::Error( "No script attached." );
|
|
}
|
|
|
|
UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
|
|
if ( !uoexec->halt() )
|
|
{
|
|
return dap::Error( "Script must be halted to retrieve scopes." );
|
|
}
|
|
|
|
size_t frameId = request.frameId;
|
|
|
|
// Frame IDs start a 1
|
|
if ( frameId > uoexec->ControlStack.size() + 1 )
|
|
{
|
|
return dap::Error( "Unknown frameId '%d'", frameId );
|
|
}
|
|
|
|
if ( !_global_scope_handle )
|
|
{
|
|
_global_scope_handle = _variable_handles.create( GlobalReference( uoexec, _script.get() ) );
|
|
}
|
|
|
|
dap::ScopesResponse response;
|
|
|
|
{
|
|
dap::Scope scope;
|
|
scope.name = "Locals @ " + Clib::tostring( frameId );
|
|
scope.presentationHint = "locals";
|
|
scope.variablesReference =
|
|
_variable_handles.create( FrameReference( uoexec, _script.get(), frameId - 1 ) );
|
|
response.scopes.push_back( scope );
|
|
}
|
|
|
|
{
|
|
dap::Scope scope;
|
|
scope.name = "Globals";
|
|
scope.variablesReference = _global_scope_handle;
|
|
response.scopes.push_back( scope );
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
dap::ResponseOrError<dap::VariablesResponse> DebugClientThread::handle_variables(
|
|
const dap::VariablesRequest& request )
|
|
{
|
|
PolLock lock;
|
|
if ( !_uoexec_wptr.exists() )
|
|
{
|
|
return dap::Error( "No script attached." );
|
|
}
|
|
|
|
UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
|
|
if ( !uoexec->halt() )
|
|
{
|
|
return dap::Error( "Script must be halted to retrieve variables." );
|
|
}
|
|
|
|
dap::VariablesResponse response;
|
|
|
|
auto reference_ptr = _variable_handles.get( static_cast<int>( request.variablesReference ) );
|
|
|
|
if ( reference_ptr == nullptr )
|
|
{
|
|
return dap::Error( "Invalid variablesReference id %d", int( request.variablesReference ) );
|
|
}
|
|
|
|
std::visit(
|
|
[&]( auto&& arg )
|
|
{
|
|
using T = std::decay_t<decltype( arg )>;
|
|
if constexpr ( (std::is_same_v<T, GlobalReference>) || (std::is_same_v<T, FrameReference>))
|
|
{
|
|
for ( auto const& [key, member] : arg.contents )
|
|
{
|
|
dap::Variable current_var;
|
|
current_var.name = key;
|
|
_variable_handles.set_response_details( *member, current_var );
|
|
response.variables.push_back( std::move( current_var ) );
|
|
}
|
|
}
|
|
else if constexpr ( std::is_same_v<T, VariableReference> )
|
|
{
|
|
response.variables = _variable_handles.to_variables( arg );
|
|
}
|
|
},
|
|
*reference_ptr );
|
|
|
|
return response;
|
|
}
|
|
|
|
dap::ResponseOrError<dap::InitializeResponse> DebugClientThread::handle_initialize(
|
|
const dap::PolInitializeRequest& request )
|
|
{
|
|
if ( !Plib::systemstate.config.debug_password.empty() &&
|
|
request.password.value( "" ) != Plib::systemstate.config.debug_password )
|
|
return dap::Error( "Password not recognized." );
|
|
|
|
dap::InitializeResponse response;
|
|
response.supportsSetVariable = true;
|
|
return response;
|
|
}
|
|
|
|
dap::ResponseOrError<dap::StepOutResponse> DebugClientThread::handle_stepOut(
|
|
const dap::StepOutRequest& )
|
|
{
|
|
PolLock lock;
|
|
if ( !_uoexec_wptr.exists() )
|
|
{
|
|
return dap::Error( "No script attached." );
|
|
}
|
|
|
|
UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
|
|
if ( !uoexec->in_debugger_holdlist() )
|
|
{
|
|
return dap::Error( "Script not ready to trace." );
|
|
}
|
|
|
|
_variable_handles.reset();
|
|
_global_scope_handle = 0;
|
|
uoexec->dbg_step_out();
|
|
uoexec->revive_debugged();
|
|
return dap::StepOutResponse{};
|
|
}
|
|
|
|
dap::ResponseOrError<dap::DisconnectResponse> DebugClientThread::handle_disconnect(
|
|
const dap::DisconnectRequest& )
|
|
{
|
|
PolLock lock;
|
|
if ( _uoexec_wptr.exists() )
|
|
{
|
|
UOExecutor* exec = _uoexec_wptr.get_weakptr();
|
|
|
|
if ( _was_launch_requested )
|
|
{
|
|
exec->seterror( true );
|
|
exec->revive();
|
|
}
|
|
|
|
if ( exec->in_debugger_holdlist() )
|
|
exec->revive_debugged();
|
|
|
|
_variable_handles.reset();
|
|
_global_scope_handle = 0;
|
|
exec->detach_debugger();
|
|
_uoexec_wptr.clear();
|
|
}
|
|
|
|
return dap::DisconnectResponse{};
|
|
}
|
|
|
|
void DebugClientThread::after_pause( const dap::ResponseOrError<dap::PauseResponse>& )
|
|
{
|
|
PolLock lock;
|
|
if ( !_uoexec_wptr.exists() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
|
|
if ( !uoexec->halt() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
on_halt();
|
|
}
|
|
|
|
void DebugClientThread::after_initialize(
|
|
const dap::ResponseOrError<dap::InitializeResponse>& response )
|
|
{
|
|
if ( response.error )
|
|
_rw->close();
|
|
else
|
|
_session->send( dap::InitializedEvent() );
|
|
}
|
|
|
|
void DebugClientThread::after_disconnect( const dap::ResponseOrError<dap::DisconnectResponse>& )
|
|
{
|
|
_rw->close();
|
|
}
|
|
|
|
void DebugClientThread::after_attach( const dap::ResponseOrError<dap::AttachResponse>& )
|
|
{
|
|
PolLock lock;
|
|
if ( _uoexec_wptr.exists() )
|
|
if ( _uoexec_wptr.get_weakptr()->halt() )
|
|
{
|
|
on_halt();
|
|
}
|
|
}
|
|
|
|
void DebugClientThread::on_error( const char* msg )
|
|
{
|
|
POLLOG_ERRORLN( "Debugger#{} session error: {}", _instance, msg );
|
|
_rw->close();
|
|
}
|
|
|
|
void DebugClientThread::run()
|
|
{
|
|
POLLOG_INFOLN( "Debugger#{} client thread started.", _instance );
|
|
|
|
// Session event handlers that are only attached once initialized with the password (if
|
|
// required).
|
|
auto attach_authorized_handlers = [this]()
|
|
{
|
|
_session->registerHandler( [this]( const dap::ConfigurationDoneRequest& request )
|
|
{ return handle_configurationDone( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::PolAttachRequest& request )
|
|
{ return handle_attach( request ); } );
|
|
|
|
// After sending an AttachResponse, check if executor is halted. If so, trigger the on_halt()
|
|
// event.
|
|
_session->registerSentHandler(
|
|
[this]( const dap::ResponseOrError<dap::AttachResponse>& response )
|
|
{ after_attach( response ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::PolLaunchRequest& request )
|
|
{ return handle_launch( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::PolProcessesRequest& request )
|
|
{ return handle_processes( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::ThreadsRequest& request )
|
|
{ return handle_threads( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::SetExceptionBreakpointsRequest& request )
|
|
{ return handle_setExceptionBreakpoints( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::ContinueRequest& request )
|
|
{ return handle_continue( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::PauseRequest& request )
|
|
{ return handle_pause( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::EvaluateRequest& request )
|
|
{ return handle_evaluate( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::SetVariableRequest& request )
|
|
{ return handle_setVariable( request ); } );
|
|
|
|
// After sending a PauseResponse, check if the script is paused. If so, send a StoppedEvent.
|
|
_session->registerSentHandler(
|
|
[this]( const dap::ResponseOrError<dap::PauseResponse>& response )
|
|
{ after_pause( response ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::NextRequest& request )
|
|
{ return handle_next( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::StepInRequest& request )
|
|
{ return handle_stepIn( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::SetBreakpointsRequest& request )
|
|
{ return handle_setBreakpoints( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::SourceRequest& request )
|
|
{ return handle_source( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::StackTraceRequest& request )
|
|
{ return handle_stackTrace( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::ScopesRequest& request )
|
|
{ return handle_scopes( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::VariablesRequest& request )
|
|
{ return handle_variables( request ); } );
|
|
|
|
_session->registerHandler( [this]( const dap::StepOutRequest& request )
|
|
{ return handle_stepOut( request ); } );
|
|
};
|
|
|
|
// Session event handlers added before initialization
|
|
_session->registerHandler(
|
|
[&]( const dap::PolInitializeRequest& request )
|
|
{
|
|
auto response = handle_initialize( request );
|
|
if ( !response.error )
|
|
{
|
|
attach_authorized_handlers();
|
|
}
|
|
return response;
|
|
} );
|
|
|
|
_session->registerHandler( [this]( const dap::DisconnectRequest& request )
|
|
{ return handle_disconnect( request ); } );
|
|
|
|
_session->registerSentHandler(
|
|
[this]( const dap::ResponseOrError<dap::DisconnectResponse>& response )
|
|
{ after_disconnect( response ); } );
|
|
|
|
_session->registerSentHandler(
|
|
[this]( const dap::ResponseOrError<dap::InitializeResponse>& response )
|
|
{ after_initialize( response ); } );
|
|
|
|
// Error handler
|
|
_session->onError( [this]( const char* msg ) { on_error( msg ); } );
|
|
|
|
// Attach the SocketReaderWriter to the Session and begin processing events.
|
|
_session->bind( _rw,
|
|
[&]() { POLLOG_INFOLN( "Debugger#{} session endpoint closed.", _instance ); } );
|
|
|
|
while ( !Clib::exit_signalled && !_exit_sent && _rw->isOpen() )
|
|
{
|
|
pol_sleep_ms( 1000 );
|
|
}
|
|
|
|
{
|
|
// Detach debugger in case a DisconnectRequest was not sent.
|
|
PolLock lock;
|
|
if ( _uoexec_wptr.exists() )
|
|
{
|
|
UOExecutor* exec = _uoexec_wptr.get_weakptr();
|
|
if ( exec->in_debugger_holdlist() )
|
|
exec->revive_debugged();
|
|
|
|
_variable_handles.reset();
|
|
_global_scope_handle = 0;
|
|
exec->detach_debugger();
|
|
_uoexec_wptr.clear();
|
|
}
|
|
}
|
|
|
|
_session.reset();
|
|
|
|
// Close the socket endpoint if necessary.
|
|
if ( _rw->isOpen() )
|
|
{
|
|
_rw->close();
|
|
}
|
|
|
|
POLLOG_INFOLN( "Debugger#{} client thread closing.", _instance );
|
|
}
|
|
} // namespace DAP
|
|
} // namespace Network
|
|
} // namespace Pol
|