diff --git a/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp b/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp index f2586904c..b44c763a9 100644 --- a/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp +++ b/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp @@ -1,9 +1,37 @@ #include "debuggerclientdap.hpp" +#include "messages.hpp" +#include +#include -namespace ecode { +using namespace EE::System; + +namespace ecode::dap { + +constexpr int MAX_HEADER_SIZE = 1 << 16; DebuggerClientDap::DebuggerClientDap( std::unique_ptr&& bus ) : mBus( std::move( bus ) ) {} +void DebuggerClientDap::makeRequest( const std::string& command, const nlohmann::json& arguments, + std::function onFinish ) { + nlohmann::json json_cmd = { + { "seq", mIdx.load() }, + { "type", "request" }, + { "command", command }, + { "arguments", arguments.empty() ? nlohmann::json::object() : arguments } }; + + std::string cmd = json_cmd.dump(); + + Log::instance()->writel( mDebug ? LogLevel::Info : LogLevel::Debug, cmd ); + + std::string msg( String::format( "Content-Length: %zu\r\n\r\n%s", cmd.size(), cmd ) ); + mBus->write( msg.data(), msg.size() ); + + if ( onFinish ) + mCommandQueue[mIdx] = onFinish; + + mIdx.fetch_add( 1, std::memory_order_relaxed ); +} + bool DebuggerClientDap::hasBreakpoint( const std::string& path, size_t line ) { return false; } @@ -17,7 +45,121 @@ bool DebuggerClientDap::removeBreakpoint( const std::string& path, size_t line ) } bool DebuggerClientDap::start() { - return mBus->start(); + bool started = mBus->start(); + if ( started ) + mBus->startAsyncRead( [this]( const char* bytes, size_t n ) { asyncRead( bytes, n ); } ); + mStarted = started; + return started; +} + +void DebuggerClientDap::asyncRead( const char* bytes, size_t n ) { + std::string_view read( bytes, n ); + mBuffer += read; + + while ( true ) { + const auto info = readHeader(); + if ( !info ) + break; + const auto data = mBuffer.substr( info->payloadStart, info->payloadLength ); + if ( data.size() < info->payloadLength ) + break; + mBuffer.erase( 0, info->payloadStart + info->payloadLength ); + +#ifndef EE_DEBUG + try { +#endif + auto message = json::parse( data ); + + if ( mDebug ) + Log::debug( message.dump() ); + + processProtocolMessage( message ); +#ifndef EE_DEBUG + } catch ( const json::exception& e ) { + Log::error( "DebuggerClientDap::asyncRead: JSON bad format: %s", e.what() ); + } +#endif + } +} + +void DebuggerClientDap::processProtocolMessage( const nlohmann::json& msg ) { + const auto type = msg.value( DAP_TYPE, "" ); + + if ( DAP_RESPONSE == type ) { + processResponse( msg ); + } else if ( DAP_EVENT == type ) { + processEvent( msg ); + } else { + Log::warning( "DebuggerClientDap::processProtocolMessage: unknown, empty or unexpected " + "ProtocolMessage::%s (%s)", + DAP_TYPE, type ); + } +} + +void DebuggerClientDap::processResponse( const nlohmann::json& msg ) {} + +void DebuggerClientDap::processEvent( const nlohmann::json& msg ) {} + +std::optional DebuggerClientDap::readHeader() { + Uint64 length = std::string::npos; + Uint64 start = 0; + Uint64 end = std::string::npos; + + auto discardExploredBuffer = [this, length, start, end]() mutable { + mBuffer.erase( 0, end ); + length = end = std::string::npos; + start = 0; + }; + + while ( true ) { + end = mBuffer.find( DAP_SEP, start ); + if ( end == std::string::npos ) { + if ( mBuffer.size() > MAX_HEADER_SIZE ) + mBuffer.clear(); + length = std::string::npos; + break; // PENDING + } + + const auto header = mBuffer.substr( start, end - start ); + end += DAP_SEP_SIZE; + + // header block separator + if ( header.size() == 0 ) { + if ( length < 0 ) { + // unexpected end of header + Log::error( "DebuggerClientDap::readHeader unexpected end of header block" ); + discardExploredBuffer(); + continue; + } + break; // END HEADER (length>0, end>0) + } + + // parse field + const auto sep = header.find_first_of( ":" ); + if ( sep == std::string::npos ) { + Log::error( "DebuggerClientDap::readHeader cannot parse header field: ", header ); + discardExploredBuffer(); + continue; // CONTINUE HEADER + } + + // parse content-length + if ( String::startsWith( header, DAP_CONTENT_LENGTH ) ) { + std::string lengthStr( header.substr( sep + 1, header.size() - sep ) ); + Uint64 length; + if ( !String::fromString( length, lengthStr ) ) { + Log::error( "DebuggerClientDap::readHeader invalid value: ", header ); + discardExploredBuffer(); + continue; // CONTINUE HEADER + } + } + + start = end; + } + + if ( length < 0 ) + return std::nullopt; + + return HeaderInfo{ .payloadStart = end, .payloadLength = length }; } bool DebuggerClientDap::attach() { @@ -25,22 +167,34 @@ bool DebuggerClientDap::attach() { } bool DebuggerClientDap::started() const { + return mStarted; +} + +bool DebuggerClientDap::cont( int threadId ) { return false; } -bool DebuggerClientDap::cont() { +bool DebuggerClientDap::pause( int threadId ) { return false; } -bool DebuggerClientDap::stepInto() { +bool DebuggerClientDap::next( int threadId ) { return false; } -bool DebuggerClientDap::stepOver() { +bool DebuggerClientDap::goTo( int threadId, int targetId ) { return false; } -bool DebuggerClientDap::stepOut() { +bool DebuggerClientDap::stepInto( int threadId ) { + return false; +} + +bool DebuggerClientDap::stepOver( int threadId ) { + return false; +} + +bool DebuggerClientDap::stepOut( int threadId ) { return false; } @@ -60,4 +214,4 @@ bool DebuggerClientDap::completed() { return false; } -} // namespace ecode +} // namespace ecode::dap diff --git a/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.hpp b/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.hpp index c2b1fe9a9..7e8b39158 100644 --- a/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.hpp +++ b/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.hpp @@ -2,8 +2,12 @@ #include "../bus.hpp" #include "../debuggerclient.hpp" +#include +#include -namespace ecode { +using namespace EE; + +namespace ecode::dap { class DebuggerClientDap : public DebuggerClient { public: @@ -21,13 +25,19 @@ class DebuggerClientDap : public DebuggerClient { bool started() const; - bool cont(); + bool cont( int threadId ); - bool stepInto(); + bool pause( int threadId ) = 0; - bool stepOver(); + bool next( int threadId ) = 0; - bool stepOut(); + bool goTo( int threadId, int targetId ) = 0; + + bool stepInto( int threadId ); + + bool stepOver( int threadId ); + + bool stepOut( int threadId ); bool halt(); @@ -39,6 +49,30 @@ class DebuggerClientDap : public DebuggerClient { protected: std::unique_ptr mBus; + UnorderedMap> mBreakpoints; + std::atomic mIdx{ 1 }; + Uint64 mThreadId{ 1 }; + bool mDebug{ true }; + bool mStarted{ false }; + UnorderedMap> mCommandQueue; + std::string mBuffer; + + void makeRequest( const std::string& command, const nlohmann::json& arguments, + std::function onFinish = nullptr ); + + void asyncRead( const char* bytes, size_t n ); + + void processProtocolMessage( const nlohmann::json& msg ); + + void processResponse( const nlohmann::json& msg ); + + void processEvent( const nlohmann::json& msg ); + + struct HeaderInfo { + Uint64 payloadStart; + Uint64 payloadLength; + }; + std::optional readHeader(); }; -} // namespace ecode +} // namespace ecode::dap diff --git a/src/tools/ecode/plugins/debugger/debuggerclient.hpp b/src/tools/ecode/plugins/debugger/debuggerclient.hpp index 5c7be5ba4..5f8c58a52 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclient.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerclient.hpp @@ -18,13 +18,19 @@ class DebuggerClient { virtual bool started() const = 0; - virtual bool cont() = 0; + virtual bool cont( int threadId ) = 0; - virtual bool stepInto() = 0; + virtual bool pause( int threadId ) = 0; - virtual bool stepOver() = 0; + virtual bool next( int threadId ) = 0; - virtual bool stepOut() = 0; + virtual bool goTo( int threadId, int targetId ) = 0; + + virtual bool stepInto( int threadId ) = 0; + + virtual bool stepOver( int threadId ) = 0; + + virtual bool stepOut( int threadId ) = 0; virtual bool halt() = 0;