Add basics of Chrome Devtools debugger support

- Use routing table for request dispatch in DebuggerHttpRouter, for
  handling choosing which debugger stack to use based on the http
  upgrade request:
    - DebuggerEscargot for the python debugger and VSCode
    - DebuggerDevtools for connecting to Chrome Devtools
- Parse mesasges with 16bit message size
- Reply to the first few messages chrome sends
- Refactor Debugger:
    - Rename DebuggerRemote to DebuggerEscargot
    - DebuggerEscargot and DebuggerDevtools inherit from
      DebuggerTcp which inherits from Debugger
- Add debugger info to README.md

Signed-off-by: Máté Tokodi <mate.tokodi@szteszoftver.hu>
This commit is contained in:
Máté Tokodi 2026-03-20 18:03:32 +01:00 committed by Patrick Kim
commit 97e8115ab1
21 changed files with 2767 additions and 1613 deletions

View file

@ -60,6 +60,7 @@ The following build options are supported when generating build rules using cmak
| **SHADOWREALM** | Enable ShadowRealm support | -ESCARGOT_SHADOWREALM | ON/OFF | OFF | | **SHADOWREALM** | Enable ShadowRealm support | -ESCARGOT_SHADOWREALM | ON/OFF | OFF |
| **SMALL_CONFIG** | Enable aggressive memory optimizations for tiny devices | -DESCARGOT_SMALL_CONFIG | ON/OFF | OFF | | **SMALL_CONFIG** | Enable aggressive memory optimizations for tiny devices | -DESCARGOT_SMALL_CONFIG | ON/OFF | OFF |
| **TEST** | Enable additional features used only for testing | -DESCARGOT_TEST | ON/OFF | OFF | | **TEST** | Enable additional features used only for testing | -DESCARGOT_TEST | ON/OFF | OFF |
| **DEBUGGER** | Enable Debug server | -DESCARGOT_DEBUGGER | ON/OFF | OFF |
### Linux ### Linux
@ -135,6 +136,27 @@ cd out
msbuild ESCARGOT.sln /property:Configuration=Release /p:platform=[ Win32 | x64 ] msbuild ESCARGOT.sln /property:Configuration=Release /p:platform=[ Win32 | x64 ]
``` ```
## Debugger
Make sure Escargot is built with the `-DESCARGOT_DEBUGGER=1` flag (off by default) enabled;
then start Escargot with the `--start-debug-server` option.
### Connect using a debugger client
- Escargot python debugger
- run `./tools/debugger/debugger.py`; It will automatically connect to a debug server on the default port `6501`
- run `./tools/debugger/debugger.py --help` for a list of options
- [Visual Studio Code extension](https://github.com/Samsung/escargot-vscode-extension/?tab=readme-ov-file#how-to-use)
- Chrome Devtools `⚠️ Early in development ⚠️`
- Initial setup:
- Navigate to [chrome://inspect](chrome://inspect)
- Make sure *Discover network targets* is enabled; click configure
- Add `localhost:6501` as a target; click Done
- Usage:
- The started debug server will be listed in the *Remote Target* list (If it is not, the page may need to be reloaded using the browser reload button)
- Click `inspect`
- A new window with the Chrome Devtools debugger UI will open
## Testing ✅ ## Testing ✅
Escargot supports various benchmark sets, which can be run using the [tools/run-tests.py](https://github.com/Samsung/escargot/blob/master/tools/run-tests.py) script. Escargot supports various benchmark sets, which can be run using the [tools/run-tests.py](https://github.com/Samsung/escargot/blob/master/tools/run-tests.py) script.

View file

@ -1984,6 +1984,7 @@ bool DebuggerOperationsRef::updateBreakpoint(WeakCodeRef* weakCodeRef, uint32_t
class DebuggerC : public Debugger { class DebuggerC : public Debugger {
public: public:
virtual void init(const char* options, Context* context) override {}
virtual void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) override; virtual void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) override;
virtual void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) override; virtual void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) override;
virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) override; virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) override;
@ -3385,7 +3386,7 @@ void ContextRef::throwException(ValueRef* exceptionValue)
toImpl(this)->throwException(s, toImpl(exceptionValue)); toImpl(this)->throwException(s, toImpl(exceptionValue));
} }
bool ContextRef::initDebugger(DebuggerOperationsRef::DebuggerClient* debuggerClient) bool ContextRef::initDebuggerClient(DebuggerOperationsRef::DebuggerClient* debuggerClient)
{ {
#ifdef ESCARGOT_DEBUGGER #ifdef ESCARGOT_DEBUGGER
Context* context = toImpl(this); Context* context = toImpl(this);
@ -3414,10 +3415,10 @@ bool ContextRef::disableDebugger()
#endif #endif
} }
bool ContextRef::initDebuggerRemote(const char* options) bool ContextRef::initDebugger(const char* options)
{ {
#ifdef ESCARGOT_DEBUGGER #ifdef ESCARGOT_DEBUGGER
return toImpl(this)->initDebuggerRemote(options); return toImpl(this)->initDebugger(options);
#else /* !ESCARGOT_DEBUGGER */ #else /* !ESCARGOT_DEBUGGER */
return false; return false;
#endif /* ESCARGOT_DEBUGGER */ #endif /* ESCARGOT_DEBUGGER */

View file

@ -950,11 +950,11 @@ public:
bool canThrowException(); bool canThrowException();
void throwException(ValueRef* exceptionValue); void throwException(ValueRef* exceptionValue);
bool initDebugger(DebuggerOperationsRef::DebuggerClient* debuggerClient); bool initDebuggerClient(DebuggerOperationsRef::DebuggerClient* debuggerClient);
bool disableDebugger(); bool disableDebugger();
// available options(separator is ';') // available options(separator is ';')
// "--port=6501", default for TCP debugger // "--port=6501", default for TCP debugger
bool initDebuggerRemote(const char* options); bool initDebugger(const char* options);
bool isDebuggerRunning(); bool isDebuggerRunning();
bool isWaitBeforeExit(); bool isWaitBeforeExit();
void printDebugger(StringRef* output); void printDebugger(StringRef* output);

File diff suppressed because it is too large Load diff

View file

@ -28,14 +28,11 @@ namespace Escargot {
#define ESCARGOT_DEBUGGER_MAX_STACK_TRACE_LENGTH 8 #define ESCARGOT_DEBUGGER_MAX_STACK_TRACE_LENGTH 8
/* WebSocket max length encoded in one byte. */ /* WebSocket max length encoded in one byte. */
#define ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH 125
#define ESCARGOT_DEBUGGER_VERSION 1
#define ESCARGOT_DEBUGGER_MESSAGE_PROCESS_DELAY 10 #define ESCARGOT_DEBUGGER_MESSAGE_PROCESS_DELAY 10
#define ESCARGOT_DEBUGGER_IN_WAIT_MODE (nullptr) #define ESCARGOT_DEBUGGER_IN_WAIT_MODE (nullptr)
#define ESCARGOT_DEBUGGER_IN_EVAL_MODE (reinterpret_cast<ExecutionState*>(0x1)) #define ESCARGOT_DEBUGGER_IN_EVAL_MODE (reinterpret_cast<ExecutionState*>(0x1))
#define ESCARGOT_DEBUGGER_ALWAYS_STOP (reinterpret_cast<ExecutionState*>(0x2)) #define ESCARGOT_DEBUGGER_ALWAYS_STOP (reinterpret_cast<ExecutionState*>(0x2))
#define ESCARGOT_DEBUGGER_NO_STACK_TRACE_RESTORE (reinterpret_cast<ExecutionState*>(0x1)) #define ESCARGOT_DEBUGGER_NO_STACK_TRACE_RESTORE (reinterpret_cast<ExecutionState*>(0x1))
#define ESCARGOT_DEBUGGER_MAX_VARIABLE_LENGTH 128
class Context; class Context;
class Object; class Object;
@ -158,8 +155,7 @@ public:
m_stopState = stopState; m_stopState = stopState;
} }
static void createDebuggerRemote(const char* options, Context* context); virtual void init(const char* options, Context* context) = 0;
virtual void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) = 0; virtual void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) = 0;
virtual void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) = 0; virtual void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) = 0;
virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) = 0; virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) = 0;
@ -176,6 +172,11 @@ public:
static SavedStackTraceDataVector* saveStackTrace(ExecutionState& state); static SavedStackTraceDataVector* saveStackTrace(ExecutionState& state);
void pumpDebuggerEvents(ExecutionState* state); void pumpDebuggerEvents(ExecutionState* state);
static void createDebugger(const char* options, Context* context);
void enable(Context* context);
Vector<Object*, GCUtil::gc_malloc_allocator<Object*>> m_activeObjects;
protected: protected:
Debugger() Debugger()
@ -193,7 +194,6 @@ protected:
return m_context != nullptr; return m_context != nullptr;
} }
void enable(Context* context);
void disable(); void disable();
virtual bool processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest = true) = 0; virtual bool processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest = true) = 0;
@ -201,6 +201,7 @@ protected:
uint32_t m_delay; uint32_t m_delay;
ExecutionState* m_stopState; ExecutionState* m_stopState;
std::vector<BreakpointLocationsInfo*> m_breakpointLocationsVector; std::vector<BreakpointLocationsInfo*> m_breakpointLocationsVector;
Vector<uintptr_t, GCUtil::gc_malloc_atomic_allocator<uintptr_t>> m_releasedFunctions;
private: private:
Context* m_context; Context* m_context;
@ -212,246 +213,6 @@ private:
bool m_inDebuggingCodeMode; bool m_inDebuggingCodeMode;
}; };
class DebuggerRemote : public Debugger {
public:
// Messages sent by Escargot to the debugger client
enum {
ESCARGOT_MESSAGE_VERSION = 0,
ESCARGOT_MESSAGE_CONFIGURATION = 1,
ESCARGOT_MESSAGE_CLOSE_CONNECTION = 2,
ESCARGOT_MESSAGE_RELEASE_FUNCTION = 3,
ESCARGOT_MESSAGE_PARSE_DONE = 4,
ESCARGOT_MESSAGE_PARSE_ERROR = 5,
// These four must be in the same order.
ESCARGOT_MESSAGE_SOURCE_8BIT = 6,
ESCARGOT_MESSAGE_SOURCE_8BIT_END = 7,
ESCARGOT_MESSAGE_SOURCE_16BIT = 8,
ESCARGOT_MESSAGE_SOURCE_16BIT_END = 9,
// These four must be in the same order.
ESCARGOT_MESSAGE_FILE_NAME_8BIT = 10,
ESCARGOT_MESSAGE_FILE_NAME_8BIT_END = 11,
ESCARGOT_MESSAGE_FILE_NAME_16BIT = 12,
ESCARGOT_MESSAGE_FILE_NAME_16BIT_END = 13,
// These four must be in the same order.
ESCARGOT_MESSAGE_FUNCTION_NAME_8BIT = 14,
ESCARGOT_MESSAGE_FUNCTION_NAME_8BIT_END = 15,
ESCARGOT_MESSAGE_FUNCTION_NAME_16BIT = 16,
ESCARGOT_MESSAGE_FUNCTION_NAME_16BIT_END = 17,
ESCARGOT_MESSAGE_BREAKPOINT_LOCATION = 18,
ESCARGOT_MESSAGE_FUNCTION_PTR = 19,
ESCARGOT_MESSAGE_BREAKPOINT_HIT = 20,
ESCARGOT_MESSAGE_EXCEPTION_HIT = 21,
// These four must be in the same order.
ESCARGOT_MESSAGE_EVAL_RESULT_8BIT = 22,
ESCARGOT_MESSAGE_EVAL_RESULT_8BIT_END = 23,
ESCARGOT_MESSAGE_EVAL_RESULT_16BIT = 24,
ESCARGOT_MESSAGE_EVAL_RESULT_16BIT_END = 25,
// These four must be in the same order.
ESCARGOT_MESSAGE_EVAL_FAILED_8BIT = 26,
ESCARGOT_MESSAGE_EVAL_FAILED_8BIT_END = 27,
ESCARGOT_MESSAGE_EVAL_FAILED_16BIT = 28,
ESCARGOT_MESSAGE_EVAL_FAILED_16BIT_END = 29,
// These four must be in the same order.
ESCARGOT_MESSAGE_WATCH_RESULT_8BIT = 30,
ESCARGOT_MESSAGE_WATCH_RESULT_8BIT_END = 31,
ESCARGOT_MESSAGE_WATCH_RESULT_16BIT = 32,
ESCARGOT_MESSAGE_WATCH_RESULT_16BIT_END = 33,
ESCARGOT_MESSAGE_BACKTRACE_TOTAL = 34,
ESCARGOT_MESSAGE_BACKTRACE = 35,
ESCARGOT_MESSAGE_BACKTRACE_END = 36,
ESCARGOT_MESSAGE_SCOPE_CHAIN = 37,
ESCARGOT_MESSAGE_SCOPE_CHAIN_END = 38,
// These four must be in the same order.
ESCARGOT_MESSAGE_STRING_8BIT = 39,
ESCARGOT_MESSAGE_STRING_8BIT_END = 40,
ESCARGOT_MESSAGE_STRING_16BIT = 41,
ESCARGOT_MESSAGE_STRING_16BIT_END = 42,
ESCARGOT_MESSAGE_VARIABLE = 43,
ESCARGOT_MESSAGE_PRINT = 44,
ESCARGOT_MESSAGE_EXCEPTION = 45,
ESCARGOT_MESSAGE_EXCEPTION_BACKTRACE = 46,
ESCARGOT_DEBUGGER_WAIT_FOR_SOURCE = 47,
ESCARGOT_DEBUGGER_WAITING_AFTER_PENDING = 48,
ESCARGOT_DEBUGGER_WAIT_FOR_WAIT_EXIT = 49
};
// Messages sent by the debugger client to Escargot
enum {
ESCARGOT_MESSAGE_FUNCTION_RELEASED = 0,
ESCARGOT_MESSAGE_UPDATE_BREAKPOINT = 1,
ESCARGOT_MESSAGE_CONTINUE = 2,
ESCARGOT_MESSAGE_STEP = 3,
ESCARGOT_MESSAGE_NEXT = 4,
ESCARGOT_MESSAGE_FINISH = 5,
// These four must be in the same order.
ESCARGOT_MESSAGE_EVAL_8BIT_START = 6,
ESCARGOT_MESSAGE_EVAL_8BIT = 7,
ESCARGOT_MESSAGE_EVAL_16BIT_START = 8,
ESCARGOT_MESSAGE_EVAL_16BIT = 9,
// These four must be in the same order.
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_8BIT_START = 10,
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_8BIT = 11,
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_16BIT_START = 12,
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_16BIT = 13,
// These four must be in the same order.
ESCARGOT_MESSAGE_WATCH_8BIT_START = 14,
ESCARGOT_MESSAGE_WATCH_8BIT = 15,
ESCARGOT_MESSAGE_WATCH_16BIT_START = 16,
ESCARGOT_MESSAGE_WATCH_16BIT = 17,
ESCARGOT_MESSAGE_GET_BACKTRACE = 18,
ESCARGOT_MESSAGE_GET_SCOPE_CHAIN = 19,
ESCARGOT_MESSAGE_GET_SCOPE_VARIABLES = 20,
ESCARGOT_MESSAGE_GET_OBJECT = 21,
// These four must be in the same order.
ESCARGOT_DEBUGGER_CLIENT_SOURCE_8BIT_START = 22,
ESCARGOT_DEBUGGER_CLIENT_SOURCE_8BIT = 23,
ESCARGOT_DEBUGGER_CLIENT_SOURCE_16BIT_START = 24,
ESCARGOT_DEBUGGER_CLIENT_SOURCE_16BIT = 25,
ESCARGOT_DEBUGGER_THERE_WAS_NO_SOURCE = 26,
ESCARGOT_DEBUGGER_PENDING_CONFIG = 27,
ESCARGOT_DEBUGGER_PENDING_RESUME = 28,
ESCARGOT_DEBUGGER_WAIT_BEFORE_EXIT = 29,
ESCARGOT_DEBUGGER_STOP = 30
};
// Environment record types
enum {
ESCARGOT_RECORD_GLOBAL_ENVIRONMENT = 0,
ESCARGOT_RECORD_FUNCTION_ENVIRONMENT = 1,
ESCARGOT_RECORD_DECLARATIVE_ENVIRONMENT = 2,
ESCARGOT_RECORD_OBJECT_ENVIRONMENT = 3,
ESCARGOT_RECORD_MODULE_ENVIRONMENT = 4,
ESCARGOT_RECORD_UNKNOWN_ENVIRONMENT = 5,
};
// Variable types
enum {
ESCARGOT_VARIABLE_END = 0,
ESCARGOT_VARIABLE_UNACCESSIBLE = 1,
ESCARGOT_VARIABLE_UNDEFINED = 2,
ESCARGOT_VARIABLE_NULL = 3,
ESCARGOT_VARIABLE_TRUE = 4,
ESCARGOT_VARIABLE_FALSE = 5,
ESCARGOT_VARIABLE_NUMBER = 6,
ESCARGOT_VARIABLE_STRING = 7,
ESCARGOT_VARIABLE_SYMBOL = 8,
ESCARGOT_VARIABLE_BIGINT = 9,
// Only object types should be defined after this point.
ESCARGOT_VARIABLE_OBJECT = 10,
ESCARGOT_VARIABLE_ARRAY = 11,
ESCARGOT_VARIABLE_FUNCTION = 12,
ESCARGOT_VARIABLE_TYPE_MASK = 0x3f,
ESCARGOT_VARIABLE_LONG_NAME = 0x40,
ESCARGOT_VARIABLE_LONG_VALUE = 0x80,
};
inline bool pendingWait(void)
{
return m_pendingWait;
}
inline bool connected(void)
{
return enabled();
}
void sendType(uint8_t type);
void sendSubtype(uint8_t type, uint8_t subType);
void sendString(uint8_t type, const String* string);
void sendPointer(uint8_t type, const void* ptr);
virtual void init(const char* options, Context* context) = 0;
virtual void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) override;
virtual void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) override;
virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) override;
virtual void exceptionCaught(String* message, SavedStackTraceDataVector& exceptionTrace) override;
virtual void consoleOut(String* output) override;
virtual String* getClientSource(String** sourceName) override;
virtual bool getWaitBeforeExitClient() override;
void sendBacktraceInfo(uint8_t type, ByteCodeBlock* byteCodeBlock, uint32_t line, uint32_t column, uint32_t executionStateDepth);
void sendVariableObjectInfo(uint8_t subType, Object* object);
void waitForResolvingPendingBreakpoints();
protected:
enum CloseReason {
CloseEndConnection,
CloseAbortConnection,
CloseProtocolUnsupported,
CloseProtocolError,
};
DebuggerRemote()
: m_exitClient(false)
, m_pendingWait(false)
, m_waitForResume(false)
, m_watchEval(false)
, m_clientSourceData(nullptr)
, m_clientSourceName(nullptr)
{
}
virtual bool processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest = true) override;
virtual bool send(uint8_t type, const void* buffer, size_t length) = 0;
virtual bool receive(uint8_t* buffer, size_t& length) = 0;
virtual bool isThereAnyEvent() = 0;
virtual void close(CloseReason reason) = 0;
private:
// Packed structure definitions to reduce network traffic
struct MessageVersion {
uint8_t littleEndian;
uint8_t version[sizeof(uint32_t)];
};
struct MessageConfiguration {
uint8_t maxMessageSize;
uint8_t pointerSize;
};
struct FunctionInfo {
uint8_t byteCodeStart[sizeof(void*)];
uint8_t startLine[sizeof(uint32_t)];
uint8_t startColumn[sizeof(uint32_t)];
};
struct BreakpointOffset {
uint8_t byteCodeStart[sizeof(void*)];
uint8_t offset[sizeof(uint32_t)];
};
struct BacktraceInfo {
uint8_t byteCode[sizeof(void*)];
uint8_t line[sizeof(uint32_t)];
uint8_t column[sizeof(uint32_t)];
uint8_t executionStateDepth[sizeof(uint32_t)];
};
struct VariableObjectInfo {
uint8_t subType;
uint8_t index[sizeof(uint32_t)];
};
uint32_t appendToActiveObjects(Object* object);
bool doEval(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, uint8_t* buffer, size_t length);
void getBacktrace(ExecutionState* state, uint32_t minDepth, uint32_t maxDepth, bool getTotal);
void getScopeChain(ExecutionState* state, uint32_t stateIndex);
void getScopeVariables(ExecutionState* state, uint32_t stateIndex, uint32_t index);
bool m_exitClient : 1;
bool m_pendingWait : 1;
bool m_waitForResume : 1;
bool m_watchEval : 1;
String* m_clientSourceData;
String* m_clientSourceName;
Vector<uintptr_t, GCUtil::gc_malloc_atomic_allocator<uintptr_t>> m_releasedFunctions;
Vector<Object*, GCUtil::gc_malloc_allocator<Object*>> m_activeObjects;
};
} // namespace Escargot } // namespace Escargot
#endif /* ESCARGOT_DEBUGGER */ #endif /* ESCARGOT_DEBUGGER */

View file

@ -0,0 +1,410 @@
/*
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include "Escargot.h"
#include "DebuggerTcp.h"
#include "DebuggerDevtools.h"
#include "DebuggerHttpRouter.h"
#include "DebuggerDevtoolsMessageBuilder.h"
#include "interpreter/ByteCode.h"
#include "rapidjson/document.h"
#include "rapidjson/prettywriter.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/error/en.h"
#ifdef ESCARGOT_DEBUGGER
namespace Escargot {
bool DebuggerDevtools::sendMessage(const std::string& msg, const int length)
{
if (UNLIKELY(!m_networkEnabled || !m_debuggerEnabled || !m_runtimeEnabled)) {
m_pendingMessages.emplace_back(msg);
return true;
}
ESCARGOT_LOG_INFO("Sending message: %s\n", msg.c_str());
return send(0, msg.c_str(), length == -1 ? msg.length() : length);
}
void DebuggerDevtools::init(const char* options, Context* context)
{
// ESCARGOT_LOG_INFO("Implement this: DebuggerDevtools::init\n");
const char* msg = "{\"method\":\"Runtime.executionContextCreated\",\"params\":{"
"\"context\":{"
"\"id\":1,"
"\"origin\":\"\","
"\"name\":\"escargot\","
"\"auxData\":{\"isDefault\":true}"
"}"
"}}";
sendMessage(msg);
}
bool DebuggerDevtools::skipSourceCode(String* srcName) const
{
ESCARGOT_LOG_INFO("Implement this: DebuggerDevtools::skipSourceCode\n");
return false;
}
uint8_t DebuggerDevtools::registerScript(String* source, String* srcName)
{
std::string url(reinterpret_cast<const char*>(srcName->characters8()), srcName->length());
auto it = m_scriptIdByUrl.find(url);
if (it != m_scriptIdByUrl.end()) {
return it->second;
}
uint8_t newId = m_nextScriptId++;
m_scriptsById.emplace(newId, ScriptInfo{ newId, srcName, source });
m_scriptIdByUrl.emplace(url, newId);
return newId;
}
void DebuggerDevtools::parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error)
{
if (!enabled()) {
return;
}
if (!source || !srcName || !source->is8Bit() || !srcName->is8Bit()) {
ESCARGOT_LOG_ERROR("Only 8 bit characters are supported right now...");
return;
}
uint8_t scriptId = registerScript(source, srcName);
sendMessage(DebuggerDevtoolsMessageBuilder::buildScriptParsedMessage(scriptId, source, srcName));
}
void DebuggerDevtools::sendPausedEvent(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state)
{
// TODO: Placeholder info
const char* msg = "{\"method\":\"Debugger.paused\","
"\"params\":{"
"\"callFrames\":[{"
"\"callFrameId\":\"frame:0\","
"\"functionName\":\"\","
"\"location\":{"
"\"scriptId\":\"1\","
"\"lineNumber\":0,"
"\"columnNumber\":0"
"},"
"\"url\":\"hello.js\","
"\"scopeChain\":[{"
"\"type\":\"global\","
"\"object\":{"
"\"type\":\"object\","
"\"className\":\"global\","
"\"description\":\"global\","
"\"objectId\":\"global:1\""
"}"
"}],"
"\"this\":{"
"\"type\":\"undefined\""
"}"
"}],"
"\"reason\":\"breakpoint\","
"\"hitBreakpoints\":[\"breakpoint:1\"]"
"}"
"}";
sendMessage(msg);
}
void DebuggerDevtools::stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state)
{
if (m_stopState == ESCARGOT_DEBUGGER_IN_EVAL_MODE) {
m_delay--;
if (m_delay == 0) {
processEvents(state, byteCodeBlock);
}
return;
}
uint8_t* byteCodeStart = byteCodeBlock->m_code.data();
sendPausedEvent(byteCodeBlock, offset, state);
if (!enabled()) {
return;
}
ASSERT(m_activeObjects.empty());
m_stopState = ESCARGOT_DEBUGGER_IN_WAIT_MODE;
while (processEvents(state, byteCodeBlock))
;
m_activeObjects.clear();
m_delay = ESCARGOT_DEBUGGER_MESSAGE_PROCESS_DELAY;
}
void DebuggerDevtools::byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock)
{
}
void DebuggerDevtools::exceptionCaught(String* message, SavedStackTraceDataVector& exceptionTrace)
{
}
void DebuggerDevtools::consoleOut(String* output)
{
}
String* DebuggerDevtools::getClientSource(String** sourceName)
{
return nullptr;
}
bool DebuggerDevtools::getWaitBeforeExitClient()
{
while (processEvents(nullptr, nullptr))
;
return true;
}
template <size_t N>
constexpr MessageType messageType(const char (&methodName)[N], const MessageHandler handler)
{
return MessageType{ methodName, handler };
}
bool DebuggerDevtools::resume(rapidjson::Document& jsonMessage)
{
replyOK(jsonMessage);
if (m_stopState != ESCARGOT_DEBUGGER_IN_WAIT_MODE) {
return true;
}
m_stopState = nullptr;
return false;
}
bool DebuggerDevtools::sendProperties(rapidjson::Document& jsonMessage)
{
uint32_t requestId = jsonMessage["id"].GetUint();
char buffer[4096];
// TODO: placeholder info
snprintf(buffer, sizeof(buffer),
"{\"id\":%u,\"result\":{"
"\"result\":["
"{"
"\"name\":\"print\","
"\"configurable\":true,"
"\"enumerable\":true,"
"\"value\":{"
"\"type\":\"function\","
"\"description\":\"function print()\""
"}"
"},"
"{"
"\"name\":\"c\","
"\"configurable\":true,"
"\"enumerable\":true,"
"\"value\":{"
"\"type\":\"number\","
"\"value\":8,"
"\"description\":\"8\""
"}"
"},"
"{"
"\"name\":\"globalVar\","
"\"configurable\":true,"
"\"enumerable\":true,"
"\"value\":{"
"\"type\":\"string\","
"\"value\":\"escargot\","
"\"description\":\"escargot\""
"}"
"}"
"]"
"}}",
requestId);
sendMessage(buffer);
return true;
}
bool DebuggerDevtools::sendSourceCode(rapidjson::Document& jsonMessage)
{
const uint32_t requestId = jsonMessage["id"].GetUint();
const uint32_t scriptId = std::stoi(jsonMessage["params"]["scriptId"].GetString());
auto it = m_scriptsById.find(scriptId);
if (it == m_scriptsById.end()) {
return false;
}
String* source = it->second.source;
if (!source->is8Bit()) {
ESCARGOT_LOG_ERROR("Only 8 bit characters are supported right now...");
return false;
}
std::string message = DebuggerDevtoolsMessageBuilder::buildSourceCodeMessage(requestId, source);
return sendMessage(message);
}
bool DebuggerDevtools::replyOK(rapidjson::Document& jsonMessage)
{
char reply[32];
const int jsonReplyStringLength = snprintf(reply, sizeof(reply),
R"({"id": %d, "result": {}})",
jsonMessage["id"].GetInt());
sendMessage(reply, jsonReplyStringLength);
return true;
}
bool DebuggerDevtools::enableNetwork(rapidjson::Document& jsonMessage)
{
this->m_networkEnabled = true;
return replyOK(jsonMessage);
}
bool DebuggerDevtools::enableDebugger(rapidjson::Document& jsonMessage)
{
this->m_debuggerEnabled = true;
return replyOK(jsonMessage);
}
bool DebuggerDevtools::enableRuntime(rapidjson::Document& jsonMessage)
{
this->m_runtimeEnabled = true;
return replyOK(jsonMessage);
}
bool DebuggerDevtools::enableProfiler(rapidjson::Document& jsonMessage)
{
this->m_profilerEnabled = true;
return replyOK(jsonMessage);
}
bool DebuggerDevtools::setPauseOnExceptions(rapidjson::Document& jsonMessage)
{
this->m_pauseOnExceptions = true;
return replyOK(jsonMessage);
}
bool DebuggerDevtools::replyMethodNotFound(rapidjson::Document& jsonMessage)
{
char reply[256];
const int jsonReplyStringLength = snprintf(reply, sizeof(reply),
R"({"id":%d,"error":{"code":-32601,"message":"'%s' wasn't found"}})",
jsonMessage["id"].GetInt(),
jsonMessage["method"].GetString());
sendMessage(reply, jsonReplyStringLength);
return true;
}
bool DebuggerDevtools::processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest)
{
uint8_t buffer[ESCARGOT_WS_MAX_MESSAGE_LENGTH];
size_t length;
// NOTE: keep sorted
static constexpr MessageType messageTypes[] = {
messageType("Debugger.enable", &DebuggerDevtools::enableDebugger),
messageType("Debugger.getScriptSource", &DebuggerDevtools::sendSourceCode),
messageType("Debugger.resume", &DebuggerDevtools::resume),
messageType("Debugger.setAsyncCallStackDepth", &DebuggerDevtools::replyOK), // we may be able to set something for this one
messageType("Debugger.setBlackboxPatterns", &DebuggerDevtools::replyOK), // we ignore this for now, but if needed set skipSourceName in DebuggerTcp
messageType("Debugger.setPauseOnExceptions", &DebuggerDevtools::setPauseOnExceptions),
messageType("Network.clearAcceptedEncodingsOverride", &DebuggerDevtools::replyMethodNotFound),
messageType("Network.emulateNetworkConditionsByRule", &DebuggerDevtools::replyMethodNotFound),
messageType("Network.enable", &DebuggerDevtools::enableNetwork),
messageType("Network.overrideNetworkState", &DebuggerDevtools::replyMethodNotFound),
messageType("Network.setAttachDebugStack", &DebuggerDevtools::replyMethodNotFound),
messageType("Network.setBlockedURLs", &DebuggerDevtools::replyMethodNotFound),
messageType("Profiler.enable", &DebuggerDevtools::enableProfiler),
messageType("Runtime.enable", &DebuggerDevtools::enableRuntime),
messageType("Runtime.getProperties", &DebuggerDevtools::sendProperties),
messageType("Runtime.runIfWaitingForDebugger", &DebuggerDevtools::resume),
};
while (true) {
if (isBlockingRequest) {
if (!receive(buffer, length)) {
break;
}
} else {
if (isThereAnyEvent()) {
if (!receive(buffer, length)) {
break;
}
} else {
return false;
}
}
printf("MESSAGE: %.*s\n", static_cast<int>(length), reinterpret_cast<const char*>(buffer));
rapidjson::Document document;
document.Parse(reinterpret_cast<const rapidjson::GenericDocument<rapidjson::UTF8<>>::Ch*>(buffer));
rapidjson::Document jsonMessage;
if (UNLIKELY(jsonMessage.Parse(reinterpret_cast<const char*>(buffer)).HasParseError())) {
ESCARGOT_LOG_ERROR("Json Message parsing error: %s at offset: %d\n", GetParseError_En(jsonMessage.GetParseError()), static_cast<int32_t>(jsonMessage.GetErrorOffset()));
return false;
}
const char* methodName = jsonMessage["method"].GetString();
if (UNLIKELY(methodName == nullptr)) {
ESCARGOT_LOG_ERROR("Debugger method not provided: %s\n", reinterpret_cast<const char*>(buffer));
return false;
}
const uint32_t methodNameLength = strlen(methodName);
for (const auto& message : messageTypes) {
// TODO: it would be better to calculate the lengths of each of the method name strings at compile time instead of using strlen()
if (!DebuggerHttpRouter::requestStartsWith(reinterpret_cast<const uint8_t*>(methodName), methodNameLength, message.methodName, strlen(message.methodName))) {
continue;
}
return (this->*message.handler)(jsonMessage);
}
ESCARGOT_LOG_ERROR("Debugger function not supported: %s\n", reinterpret_cast<const char*>(buffer));
this->replyMethodNotFound(jsonMessage); // maybe reply with not supported instead? if there is such a reply in the spec
}
if (!m_pendingMessages.empty() && m_networkEnabled && m_debuggerEnabled && m_runtimeEnabled) {
for (const std::string& msg : m_pendingMessages) {
sendMessage(msg);
}
m_pendingMessages.clear();
}
return enabled();
}
} // namespace Escargot
#endif /* ESCARGOT_DEBUGGER */

View file

@ -0,0 +1,105 @@
/*
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#ifndef __DebuggerDevtools__
#define __DebuggerDevtools__
#include "Debugger.h"
#include "DebuggerTcp.h"
#include "rapidjson/document.h"
#include "rapidjson/rapidjson.h"
#ifdef ESCARGOT_DEBUGGER
namespace Escargot {
#ifdef WIN32
#include <winsock2.h>
typedef SOCKET EscargotSocket;
#else /* !WIN32 */
typedef int EscargotSocket;
#endif /* WIN32 */
struct ScriptInfo {
uint8_t scriptId;
String* url;
String* source;
};
class DebuggerDevtools : public DebuggerTcp {
public:
DebuggerDevtools(EscargotSocket socket, String* skipSource)
: DebuggerTcp(socket, skipSource, ESCARGOT_WS_BUFFER_SIZE, ESCARGOT_DEBUGGER_WEBSOCKET_TEXT_FRAME)
{
}
bool sendMessage(const std::string& msg, const int length = -1);
void init(const char* options, Context* context) override;
bool skipSourceCode(String* srcName) const override;
void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) override;
void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) override;
void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) override;
void exceptionCaught(String* message, SavedStackTraceDataVector& exceptionTrace) override;
void consoleOut(String* output) override;
String* getClientSource(String** sourceName) override;
bool getWaitBeforeExitClient() override;
void sendPausedEvent(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state);
protected:
bool processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest = true) override;
private:
bool sendProperties(rapidjson::Document& jsonMessage);
bool resume(rapidjson::Document& jsonMessage);
bool sendSourceCode(rapidjson::Document& jsonMessage);
bool replyOK(rapidjson::Document& jsonMessage);
bool enableNetwork(rapidjson::Document& jsonMessage);
bool enableDebugger(rapidjson::Document& jsonMessage);
bool enableRuntime(rapidjson::Document& jsonMessage);
bool enableProfiler(rapidjson::Document& jsonMessage);
bool setPauseOnExceptions(rapidjson::Document& jsonMessage);
bool replyMethodNotFound(rapidjson::Document& jsonMessage);
uint8_t registerScript(String* url, String* source);
bool m_networkEnabled = false;
bool m_debuggerEnabled = false;
bool m_runtimeEnabled = false;
bool m_profilerEnabled = false;
bool m_pauseOnExceptions = false;
std::unordered_map<uint8_t, ScriptInfo> m_scriptsById;
std::unordered_map<std::string, uint8_t> m_scriptIdByUrl;
uint8_t m_nextScriptId = 1;
std::vector<std::string> m_pendingMessages;
};
using MessageHandler = bool (DebuggerDevtools::*)(rapidjson::Document&);
struct MessageType {
const char* methodName;
MessageHandler handler;
};
} // namespace Escargot
#endif /* ESCARGOT_DEBUGGER */
#endif

View file

@ -0,0 +1,164 @@
/*
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include "DebuggerDevtoolsMessageBuilder.h"
#ifdef ESCARGOT_DEBUGGER
#include "Escargot.h"
#include "runtime/String.h" // for split function
#include "interpreter/ByteCode.h"
#include "rapidjson/document.h"
#include "rapidjson/prettywriter.h"
#include "rapidjson/stringbuffer.h"
namespace Escargot {
static std::string jsonEscape(const LChar* src, size_t len)
{
std::string out;
out.reserve(len + 16);
for (size_t i = 0; i < len; ++i) {
LChar c = src[i];
switch (c) {
case '\"':
out += "\\\"";
break;
case '\\':
out += "\\\\";
break;
case '\n':
out += "\\n";
break;
case '\r':
out += "\\r";
break;
case '\t':
out += "\\t";
break;
default:
out += c;
break;
}
}
return out;
}
static void computeEndLocation(const LChar* src, size_t length, uint32_t& endLine, uint32_t& endColumn)
{
for (size_t i = 0; i < length; i++) {
if (src[i] == '\n') {
endLine++;
endColumn = 0;
} else {
endColumn++;
}
}
}
std::string DebuggerDevtoolsMessageBuilder::buildEmptyMessage(const uint32_t id)
{
char buffer[64];
const int written = snprintf(buffer, sizeof(buffer),
R"({"id":%u,"result":{}})",
id);
if (written < 0) {
return {};
}
return { buffer, static_cast<size_t>(written) };
}
std::string DebuggerDevtoolsMessageBuilder::buildScriptParsedMessage(const uint8_t scriptId, const String* source, const String* srcName)
{
if (!source || !srcName || !source->is8Bit() || !srcName->is8Bit()) {
return {};
}
const LChar* sourceName = srcName->characters8();
const LChar* sourceCode = source->characters8();
size_t sourceNameLength = srcName->length();
size_t sourceCodeLength = source->length();
uint32_t endLine = 0;
uint32_t endColumn = 0;
computeEndLocation(sourceCode, sourceCodeLength, endLine, endColumn);
char buffer[512];
int written = snprintf(buffer, sizeof(buffer),
"{\"method\":\"Debugger.scriptParsed\","
"\"params\":{"
"\"scriptId\":\"%u\","
"\"url\":\"%.*s\","
"\"startLine\":0,"
"\"startColumn\":0,"
"\"endLine\":%u,"
"\"endColumn\":%u,"
"\"executionContextId\":1"
"}"
"}",
scriptId,
static_cast<int>(sourceNameLength),
reinterpret_cast<const char*>(sourceName),
endLine,
endColumn);
if (written < 0 || static_cast<size_t>(written) >= sizeof(buffer)) {
return {};
}
return { buffer, static_cast<size_t>(written) };
}
std::string DebuggerDevtoolsMessageBuilder::buildSourceCodeMessage(const uint8_t requestId, const String* source)
{
if (!source || !source->is8Bit()) {
return {};
}
const LChar* sourceCode = source->characters8();
size_t sourceCodeLength = source->length();
// Special characters in source code must be escaped.
std::string escapedSource = jsonEscape(sourceCode, sourceCodeLength);
// FIXME: buffer size depends on length of source.
char buffer[4096];
int written = snprintf(buffer, sizeof(buffer),
"{\"id\":%u,"
"\"result\":{"
"\"scriptSource\":\"%s\""
"}"
"}",
requestId,
escapedSource.c_str());
if (written < 0 || static_cast<size_t>(written) >= sizeof(buffer)) {
return {};
}
return { buffer, static_cast<size_t>(written) };
}
} // namespace Escargot
#endif /* ESCARGOT_DEBUGGER */

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#ifndef __DebuggerDevtoolsMessageBuilder__
#define __DebuggerDevtoolsMessageBuilder__
#include "rapidjson/document.h"
#include "rapidjson/rapidjson.h"
#ifdef ESCARGOT_DEBUGGER
namespace Escargot {
class String;
class DebuggerDevtoolsMessageBuilder {
public:
static std::string buildEmptyMessage(uint32_t id);
static std::string buildScriptParsedMessage(uint8_t scriptId, const String* source, const String* srcName);
static std::string buildSourceCodeMessage(uint8_t requestId, const String* source);
};
} // namespace Escargot
#endif /* ESCARGOT_DEBUGGER */
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,267 @@
/*
* Copyright (c) 2016-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#ifndef __DebuggerEscargot__
#define __DebuggerEscargot__
#include "Debugger.h"
#include "DebuggerTcp.h"
#ifdef ESCARGOT_DEBUGGER
#define ESCARGOT_DEBUGGER_VERSION 1
#define ESCARGOT_DEBUGGER_MAX_VARIABLE_LENGTH 128
namespace Escargot {
class Context;
class Object;
class String;
class ExecutionState;
class ByteCodeBlock;
class InterpretedCodeBlock;
class DebuggerEscargot : public DebuggerTcp {
public:
// Messages sent by Escargot to the debugger client
enum {
ESCARGOT_MESSAGE_VERSION = 0,
ESCARGOT_MESSAGE_CONFIGURATION = 1,
ESCARGOT_MESSAGE_CLOSE_CONNECTION = 2,
ESCARGOT_MESSAGE_RELEASE_FUNCTION = 3,
ESCARGOT_MESSAGE_PARSE_DONE = 4,
ESCARGOT_MESSAGE_PARSE_ERROR = 5,
// These four must be in the same order.
ESCARGOT_MESSAGE_SOURCE_8BIT = 6,
ESCARGOT_MESSAGE_SOURCE_8BIT_END = 7,
ESCARGOT_MESSAGE_SOURCE_16BIT = 8,
ESCARGOT_MESSAGE_SOURCE_16BIT_END = 9,
// These four must be in the same order.
ESCARGOT_MESSAGE_FILE_NAME_8BIT = 10,
ESCARGOT_MESSAGE_FILE_NAME_8BIT_END = 11,
ESCARGOT_MESSAGE_FILE_NAME_16BIT = 12,
ESCARGOT_MESSAGE_FILE_NAME_16BIT_END = 13,
// These four must be in the same order.
ESCARGOT_MESSAGE_FUNCTION_NAME_8BIT = 14,
ESCARGOT_MESSAGE_FUNCTION_NAME_8BIT_END = 15,
ESCARGOT_MESSAGE_FUNCTION_NAME_16BIT = 16,
ESCARGOT_MESSAGE_FUNCTION_NAME_16BIT_END = 17,
ESCARGOT_MESSAGE_BREAKPOINT_LOCATION = 18,
ESCARGOT_MESSAGE_FUNCTION_PTR = 19,
ESCARGOT_MESSAGE_BREAKPOINT_HIT = 20,
ESCARGOT_MESSAGE_EXCEPTION_HIT = 21,
// These four must be in the same order.
ESCARGOT_MESSAGE_EVAL_RESULT_8BIT = 22,
ESCARGOT_MESSAGE_EVAL_RESULT_8BIT_END = 23,
ESCARGOT_MESSAGE_EVAL_RESULT_16BIT = 24,
ESCARGOT_MESSAGE_EVAL_RESULT_16BIT_END = 25,
// These four must be in the same order.
ESCARGOT_MESSAGE_EVAL_FAILED_8BIT = 26,
ESCARGOT_MESSAGE_EVAL_FAILED_8BIT_END = 27,
ESCARGOT_MESSAGE_EVAL_FAILED_16BIT = 28,
ESCARGOT_MESSAGE_EVAL_FAILED_16BIT_END = 29,
// These four must be in the same order.
ESCARGOT_MESSAGE_WATCH_RESULT_8BIT = 30,
ESCARGOT_MESSAGE_WATCH_RESULT_8BIT_END = 31,
ESCARGOT_MESSAGE_WATCH_RESULT_16BIT = 32,
ESCARGOT_MESSAGE_WATCH_RESULT_16BIT_END = 33,
ESCARGOT_MESSAGE_BACKTRACE_TOTAL = 34,
ESCARGOT_MESSAGE_BACKTRACE = 35,
ESCARGOT_MESSAGE_BACKTRACE_END = 36,
ESCARGOT_MESSAGE_SCOPE_CHAIN = 37,
ESCARGOT_MESSAGE_SCOPE_CHAIN_END = 38,
// These four must be in the same order.
ESCARGOT_MESSAGE_STRING_8BIT = 39,
ESCARGOT_MESSAGE_STRING_8BIT_END = 40,
ESCARGOT_MESSAGE_STRING_16BIT = 41,
ESCARGOT_MESSAGE_STRING_16BIT_END = 42,
ESCARGOT_MESSAGE_VARIABLE = 43,
ESCARGOT_MESSAGE_PRINT = 44,
ESCARGOT_MESSAGE_EXCEPTION = 45,
ESCARGOT_MESSAGE_EXCEPTION_BACKTRACE = 46,
ESCARGOT_DEBUGGER_WAIT_FOR_SOURCE = 47,
ESCARGOT_DEBUGGER_WAITING_AFTER_PENDING = 48,
ESCARGOT_DEBUGGER_WAIT_FOR_WAIT_EXIT = 49
};
// Messages sent by the debugger client to Escargot
enum {
ESCARGOT_MESSAGE_FUNCTION_RELEASED = 0,
ESCARGOT_MESSAGE_UPDATE_BREAKPOINT = 1,
ESCARGOT_MESSAGE_CONTINUE = 2,
ESCARGOT_MESSAGE_STEP = 3,
ESCARGOT_MESSAGE_NEXT = 4,
ESCARGOT_MESSAGE_FINISH = 5,
// These four must be in the same order.
ESCARGOT_MESSAGE_EVAL_8BIT_START = 6,
ESCARGOT_MESSAGE_EVAL_8BIT = 7,
ESCARGOT_MESSAGE_EVAL_16BIT_START = 8,
ESCARGOT_MESSAGE_EVAL_16BIT = 9,
// These four must be in the same order.
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_8BIT_START = 10,
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_8BIT = 11,
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_16BIT_START = 12,
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_16BIT = 13,
// These four must be in the same order.
ESCARGOT_MESSAGE_WATCH_8BIT_START = 14,
ESCARGOT_MESSAGE_WATCH_8BIT = 15,
ESCARGOT_MESSAGE_WATCH_16BIT_START = 16,
ESCARGOT_MESSAGE_WATCH_16BIT = 17,
ESCARGOT_MESSAGE_GET_BACKTRACE = 18,
ESCARGOT_MESSAGE_GET_SCOPE_CHAIN = 19,
ESCARGOT_MESSAGE_GET_SCOPE_VARIABLES = 20,
ESCARGOT_MESSAGE_GET_OBJECT = 21,
// These four must be in the same order.
ESCARGOT_DEBUGGER_CLIENT_SOURCE_8BIT_START = 22,
ESCARGOT_DEBUGGER_CLIENT_SOURCE_8BIT = 23,
ESCARGOT_DEBUGGER_CLIENT_SOURCE_16BIT_START = 24,
ESCARGOT_DEBUGGER_CLIENT_SOURCE_16BIT = 25,
ESCARGOT_DEBUGGER_THERE_WAS_NO_SOURCE = 26,
ESCARGOT_DEBUGGER_PENDING_CONFIG = 27,
ESCARGOT_DEBUGGER_PENDING_RESUME = 28,
ESCARGOT_DEBUGGER_WAIT_BEFORE_EXIT = 29,
ESCARGOT_DEBUGGER_STOP = 30
};
// Environment record types
enum {
ESCARGOT_RECORD_GLOBAL_ENVIRONMENT = 0,
ESCARGOT_RECORD_FUNCTION_ENVIRONMENT = 1,
ESCARGOT_RECORD_DECLARATIVE_ENVIRONMENT = 2,
ESCARGOT_RECORD_OBJECT_ENVIRONMENT = 3,
ESCARGOT_RECORD_MODULE_ENVIRONMENT = 4,
ESCARGOT_RECORD_UNKNOWN_ENVIRONMENT = 5,
};
// Variable types
enum {
ESCARGOT_VARIABLE_END = 0,
ESCARGOT_VARIABLE_UNACCESSIBLE = 1,
ESCARGOT_VARIABLE_UNDEFINED = 2,
ESCARGOT_VARIABLE_NULL = 3,
ESCARGOT_VARIABLE_TRUE = 4,
ESCARGOT_VARIABLE_FALSE = 5,
ESCARGOT_VARIABLE_NUMBER = 6,
ESCARGOT_VARIABLE_STRING = 7,
ESCARGOT_VARIABLE_SYMBOL = 8,
ESCARGOT_VARIABLE_BIGINT = 9,
// Only object types should be defined after this point.
ESCARGOT_VARIABLE_OBJECT = 10,
ESCARGOT_VARIABLE_ARRAY = 11,
ESCARGOT_VARIABLE_FUNCTION = 12,
ESCARGOT_VARIABLE_TYPE_MASK = 0x3f,
ESCARGOT_VARIABLE_LONG_NAME = 0x40,
ESCARGOT_VARIABLE_LONG_VALUE = 0x80,
};
inline bool pendingWait(void)
{
return m_pendingWait;
}
inline bool connected(void)
{
return enabled();
}
void sendType(uint8_t type);
void sendSubtype(uint8_t type, uint8_t subType);
void sendString(uint8_t type, const String* string);
void sendPointer(uint8_t type, const void* ptr);
void init(const char* options, Context* context) override;
void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) override;
void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) override;
void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) override;
void exceptionCaught(String* message, SavedStackTraceDataVector& exceptionTrace) override;
void consoleOut(String* output) override;
String* getClientSource(String** sourceName) override;
bool getWaitBeforeExitClient() override;
void sendBacktraceInfo(uint8_t type, ByteCodeBlock* byteCodeBlock, uint32_t line, uint32_t column, uint32_t executionStateDepth);
void sendVariableObjectInfo(uint8_t subType, Object* object);
void waitForResolvingPendingBreakpoints();
DebuggerEscargot(EscargotSocket socket, String* skipSource)
: DebuggerTcp(socket, skipSource, ESCARGOT_WS_HEADER_BASE_SIZE + ESCARGOT_WS_MASK_SIZE + ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH, ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME)
, m_exitClient(false)
, m_pendingWait(false)
, m_waitForResume(false)
, m_watchEval(false)
, m_clientSourceData(nullptr)
, m_clientSourceName(nullptr)
{
}
bool processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest = true) override;
private:
// Packed structure definitions to reduce network traffic
struct MessageVersion {
uint8_t littleEndian;
uint8_t version[sizeof(uint32_t)];
};
struct MessageConfiguration {
uint8_t maxMessageSize;
uint8_t pointerSize;
};
struct FunctionInfo {
uint8_t byteCodeStart[sizeof(void*)];
uint8_t startLine[sizeof(uint32_t)];
uint8_t startColumn[sizeof(uint32_t)];
};
struct BreakpointOffset {
uint8_t byteCodeStart[sizeof(void*)];
uint8_t offset[sizeof(uint32_t)];
};
struct BacktraceInfo {
uint8_t byteCode[sizeof(void*)];
uint8_t line[sizeof(uint32_t)];
uint8_t column[sizeof(uint32_t)];
uint8_t executionStateDepth[sizeof(uint32_t)];
};
struct VariableObjectInfo {
uint8_t subType;
uint8_t index[sizeof(uint32_t)];
};
bool doEval(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, uint8_t* buffer, size_t length);
void getBacktrace(ExecutionState* state, uint32_t minDepth, uint32_t maxDepth, bool getTotal);
void getScopeChain(ExecutionState* state, uint32_t stateIndex);
void getScopeVariables(ExecutionState* state, uint32_t stateIndex, uint32_t index);
bool m_exitClient : 1;
bool m_pendingWait : 1;
bool m_waitForResume : 1;
bool m_watchEval : 1;
String* m_clientSourceData;
String* m_clientSourceName;
};
} // namespace Escargot
#endif /* ESCARGOT_DEBUGGER */
#endif

View file

@ -0,0 +1,321 @@
/*
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include "Escargot.h"
#include "DebuggerHttpRouter.h"
#include "DebuggerTcp.h"
#ifdef ESCARGOT_DEBUGGER
namespace Escargot {
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
static uint8_t toBase64Character(uint8_t value)
{
if (value < 26) {
return static_cast<uint8_t>(value + 'A');
}
if (value < 52) {
return static_cast<uint8_t>(value - 26 + 'a');
}
if (value < 62) {
return static_cast<uint8_t>(value - 52 + '0');
}
if (value == 62) {
return '+';
}
return '/';
}
/**
* Encode a byte sequence into Base64 string.
*/
static void toBase64(const uint8_t* source, uint8_t* destination, size_t length)
{
while (length >= 3) {
uint8_t value = (source[0] >> 2);
destination[0] = toBase64Character(value);
value = static_cast<uint8_t>(((source[0] << 4) | (source[1] >> 4)) & 0x3f);
destination[1] = toBase64Character(value);
value = static_cast<uint8_t>(((source[1] << 2) | (source[2] >> 6)) & 0x3f);
destination[2] = toBase64Character(value);
value = static_cast<uint8_t>(source[2] & 0x3f);
destination[3] = toBase64Character(value);
source += 3;
destination += 4;
length -= 3;
}
}
bool DebuggerHttpRouter::requestStartsWith(const uint8_t* buffer, const size_t length, const char* prefix, const size_t prefixLength)
{
if (length < prefixLength)
return false;
return memcmp(buffer, prefix, prefixLength) == 0;
}
static bool buildHttpResponse(const char* body, uint8_t* buffer, size_t bufferSize, size_t& outLen)
{
size_t bodyLen = strlen(body);
int len = snprintf(reinterpret_cast<char*>(buffer), bufferSize,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json; charset=UTF-8\r\n"
"Cache-Control: no-cache\r\n"
"Content-Length: %zu\r\n"
"Connection: close\r\n"
"\r\n"
"%s",
bodyLen,
body);
if (len < 0 || static_cast<size_t>(len) >= bufferSize)
return false;
outLen = static_cast<size_t>(len);
return true;
}
static bool buildVersionResponse(uint8_t* buffer, size_t bufferSize, size_t& outLen)
{
static constexpr const char body[] = "{"
"\"Browser\":\"Escargot/1.0\","
"\"Protocol-Version\":\"1.3\""
"}";
return buildHttpResponse(body, buffer, bufferSize, outLen);
}
static bool buildListResponse(uint8_t* buffer, size_t bufferSize, const char* ip, uint16_t port, size_t& outLen)
{
char body[512];
int bodyLen = snprintf(body, sizeof(body),
"[{"
"\"description\":\"Escargot CDP target\","
"\"devtoolsFrontendUrl\":\"/devtools/inspector.html?ws=%s:%u/devtools/page/1\","
"\"id\":\"1\","
"\"title\":\"Escargot\","
"\"type\":\"node\","
"\"url\":\"file:///\","
"\"webSocketDebuggerUrl\":\"ws://%s:%u/devtools/page/1\""
"}]",
ip, static_cast<unsigned>(port),
ip, static_cast<unsigned>(port));
if (bodyLen < 0 || static_cast<size_t>(bodyLen) >= sizeof(body)) {
return false;
}
return buildHttpResponse(body, buffer, bufferSize, outLen);
}
static bool webSocketHandshake(EscargotSocket socket, uint8_t* buffer, size_t length)
{
uint8_t* websocketKey = buffer;
constexpr char expectedWebsocketKey[] = "Sec-WebSocket-Key:";
constexpr size_t expectedWebsocketKeyLength = sizeof(expectedWebsocketKey) - 1;
while (true) {
if (length < expectedWebsocketKeyLength) {
ESCARGOT_LOG_ERROR("Sec-WebSocket-Key not found.\n");
return false;
}
if (websocketKey[0] == 'S'
&& websocketKey[-1] == '\n'
&& websocketKey[-2] == '\r'
&& memcmp(websocketKey, expectedWebsocketKey, expectedWebsocketKeyLength) == 0) {
websocketKey += expectedWebsocketKeyLength;
break;
}
websocketKey++;
}
/* String terminated by double newlines. */
while (*websocketKey == ' ') {
websocketKey++;
}
uint8_t* websocketKeyEnd = websocketKey;
while (*websocketKeyEnd > ' ') {
websocketKeyEnd++;
}
/* Since the buffer is not needed anymore it can
* be reused for storing the SHA-1 key and Base64 string. */
constexpr size_t sha1Length = 20;
DebuggerTcp::computeSha1(websocketKey,
static_cast<size_t>(websocketKeyEnd - websocketKey),
reinterpret_cast<const uint8_t*>("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"),
36,
buffer);
/* The SHA-1 key is 20 bytes long but toBase64 expects a length
* divisible by 3 so an extra 0 is appended at the end. */
buffer[sha1Length] = 0;
toBase64(buffer, buffer + sha1Length + 1, sha1Length + 1);
/* Last value must be replaced by equal sign. */
constexpr uint8_t responsePrefix[] = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ";
if (!DebuggerTcp::tcpSend(socket, responsePrefix, sizeof(responsePrefix) - 1)
|| !DebuggerTcp::tcpSend(socket, buffer + sha1Length + 1, 27)) {
return false;
}
constexpr uint8_t responseSuffix[] = "=\r\n\r\n";
return DebuggerTcp::tcpSend(socket, responseSuffix, sizeof(responseSuffix) - 1);
}
bool DebuggerHttpRouter::webSocketEstablished() const
{
return m_client != DebuggerClient::None;
}
DebuggerClient DebuggerHttpRouter::client() const
{
return m_client;
}
bool handleWebSocketRequest(const RequestContext& ctx)
{
return webSocketHandshake(ctx.socket, ctx.request, ctx.requestLength);
}
bool handleJsonVersion(const RequestContext& ctx)
{
uint8_t buffer[1024];
size_t responseLength = 0;
if (!buildVersionResponse(buffer, sizeof(buffer), responseLength)) {
return false;
}
return DebuggerTcp::tcpSend(ctx.socket, buffer, responseLength);
}
bool handleJsonList(const RequestContext& ctx)
{
struct sockaddr_in addr{};
socklen_t len = sizeof(addr);
getsockname(ctx.socket, reinterpret_cast<struct sockaddr*>(&addr), &len);
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr.sin_addr, ip, sizeof(ip));
const uint16_t port = ntohs(addr.sin_port);
uint8_t buffer[1024];
size_t responseLength = 0;
if (!buildListResponse(buffer, sizeof(buffer), ip, port, responseLength)) {
return false;
}
return DebuggerTcp::tcpSend(ctx.socket, buffer, responseLength);
}
static bool readHttpMessage(EscargotSocket socket, uint8_t* buffer, size_t capacity, size_t& messageLength)
{
size_t remainingLength = capacity;
uint8_t* message = buffer;
while (true) {
size_t receivedLength = 0;
if (!DebuggerTcp::tcpReceive(socket, message, remainingLength, &receivedLength)) {
return false;
}
message += receivedLength;
remainingLength -= receivedLength;
if (message > (buffer + 4) && memcmp(message - 4, "\r\n\r\n", 4) == 0) {
messageLength = static_cast<size_t>(message - buffer);
return true;
}
if (remainingLength == 0) {
ESCARGOT_LOG_ERROR("WebSocket Error: Request too long\n");
return false;
}
}
}
template <size_t N>
constexpr Route route(const char (&prefix)[N], DebuggerClient client, RouteHandler handler)
{
return Route{ prefix, N - 1, client, handler };
}
bool DebuggerHttpRouter::handleHttpRequest(EscargotSocket socket)
{
uint8_t buffer[1024];
size_t messageLength = 0;
if (!readHttpMessage(socket, buffer, sizeof(buffer), messageLength)) {
return false;
}
static constexpr Route routes[] = {
route("GET /escargot-debugger", DebuggerClient::Escargot, handleWebSocketRequest),
route("GET /devtools/page/1", DebuggerClient::DevTools, handleWebSocketRequest),
route("GET /json/version", DebuggerClient::None, handleJsonVersion),
route("GET /json/list", DebuggerClient::None, handleJsonList),
route("GET /json", DebuggerClient::None, handleJsonList)
};
for (const auto& route : routes) {
if (!requestStartsWith(buffer, messageLength, route.prefix, route.prefixLength)) {
continue;
}
m_client = route.client;
// Skip the matched route prefix
uint8_t* remainder = buffer + route.prefixLength;
size_t remainderLength = messageLength - route.prefixLength;
return route.handler(RequestContext{ socket, remainder, remainderLength });
}
ESCARGOT_LOG_ERROR("Unsupported http request\n");
return false;
}
} // namespace Escargot
#endif /* ESCARGOT_DEBUGGER */

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#ifndef __DebuggerHttpRouter__
#define __DebuggerHttpRouter__
#ifdef ESCARGOT_DEBUGGER
namespace Escargot {
#ifdef WIN32
#include <winsock2.h>
typedef SOCKET EscargotSocket;
#else /* !WIN32 */
typedef int EscargotSocket;
#endif /* WIN32 */
enum class DebuggerClient : uint8_t {
None,
Escargot,
DevTools
};
struct RequestContext {
EscargotSocket socket;
uint8_t* request;
size_t requestLength;
};
using RouteHandler = bool (*)(const RequestContext&);
struct Route {
const char* prefix;
size_t prefixLength;
DebuggerClient client;
RouteHandler handler;
};
class DebuggerHttpRouter {
public:
DebuggerHttpRouter() = default;
static bool requestStartsWith(const uint8_t* buffer, size_t length, const char* prefix, size_t prefixLength);
bool handleHttpRequest(EscargotSocket socket);
bool webSocketEstablished() const;
DebuggerClient client() const;
private:
DebuggerClient m_client{ DebuggerClient::None };
};
} // namespace Escargot
#endif /* ESCARGOT_DEBUGGER */
#endif

View file

@ -19,6 +19,9 @@
#include "Escargot.h" #include "Escargot.h"
#include "DebuggerTcp.h" #include "DebuggerTcp.h"
#include "DebuggerDevtools.h"
#include "DebuggerEscargot.h"
#include "DebuggerHttpRouter.h"
#include "runtime/String.h" // for split function #include "runtime/String.h" // for split function
#ifdef ESCARGOT_DEBUGGER #ifdef ESCARGOT_DEBUGGER
@ -110,7 +113,7 @@ static inline void tcpCloseSocket(EscargotSocket socket)
#endif /* WIN32 */ #endif /* WIN32 */
} }
static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messageLength) bool DebuggerTcp::tcpSend(EscargotSocket socket, const uint8_t* message, size_t messageLength)
{ {
do { do {
#ifdef OS_POSIX #ifdef OS_POSIX
@ -122,7 +125,7 @@ static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messag
} }
#endif /* OS_POSIX */ #endif /* OS_POSIX */
ssize_t sentBytes = send(socket, message, messageLength, 0); ssize_t sentBytes = Escargot::send(socket, message, messageLength, 0);
if (sentBytes < 0) { if (sentBytes < 0) {
int errorNumber = tcpGetErrno(); int errorNumber = tcpGetErrno();
@ -142,7 +145,7 @@ static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messag
return true; return true;
} }
static bool tcpReceive(EscargotSocket socket, uint8_t* message, size_t maxLength, size_t* receivedLength) bool DebuggerTcp::tcpReceive(EscargotSocket socket, uint8_t* message, size_t maxLength, size_t* receivedLength)
{ {
*receivedLength = 0; *receivedLength = 0;
@ -162,152 +165,169 @@ static bool tcpReceive(EscargotSocket socket, uint8_t* message, size_t maxLength
return true; return true;
} }
static uint8_t toBase64Character(uint8_t value) bool DebuggerTcp::send(const uint8_t type, const void* buffer, const size_t length)
{ {
if (value < 26) { ASSERT(enabled());
return (uint8_t)(value + 'A'); ASSERT(m_websocketMessageType == ESCARGOT_DEBUGGER_WEBSOCKET_TEXT_FRAME
} || (m_websocketMessageType == ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME && length <= ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH));
if (value < 52) { if (length > ESCARGOT_WS_MAX_MESSAGE_LENGTH) {
return (uint8_t)(value - 26 + 'a'); ESCARGOT_LOG_ERROR("Cannot send WebSocket payload: 64-bit payload length is not supported.\n");
} close(CloseAbortConnection);
if (value < 62) {
return (uint8_t)(value - 52 + '0');
}
if (value == 62) {
return (uint8_t)'+';
}
return (uint8_t)'/';
}
/**
* Encode a byte sequence into Base64 string.
*/
static void toBase64(const uint8_t* source, uint8_t* destination, size_t length)
{
while (length >= 3) {
uint8_t value = (source[0] >> 2);
destination[0] = toBase64Character(value);
value = (uint8_t)(((source[0] << 4) | (source[1] >> 4)) & 0x3f);
destination[1] = toBase64Character(value);
value = (uint8_t)(((source[1] << 2) | (source[2] >> 6)) & 0x3f);
destination[2] = toBase64Character(value);
value = (uint8_t)(source[2] & 0x3f);
destination[3] = toBase64Character(value);
source += 3;
destination += 4;
length -= 3;
}
}
static bool webSocketHandshake(EscargotSocket socket)
{
uint8_t buffer[1024];
size_t remainingLength = sizeof(buffer);
uint8_t* message = buffer;
while (true) {
size_t receivedLength;
if (!tcpReceive(socket, message, remainingLength, &receivedLength)) {
return false;
}
message += receivedLength;
remainingLength -= receivedLength;
if (message > (buffer + 4) && memcmp(message - 4, "\r\n\r\n", 4) == 0) {
break;
}
if (remainingLength == 0) {
ESCARGOT_LOG_ERROR("WebSocket Error: Request too long\n");
return false;
}
}
/* Check protocol. */
const char expectedProtocol[] = "GET /escargot-debugger";
const size_t expectedProtocolLength = sizeof(expectedProtocol) - 1;
if ((size_t)(message - buffer) < expectedProtocolLength
|| memcmp(buffer, expectedProtocol, expectedProtocolLength) != 0) {
ESCARGOT_LOG_ERROR("WebSocket Error: Invalid handshake format.\n");
return false; return false;
} }
uint8_t* websocketKey = buffer + expectedProtocolLength; size_t headerLength = 0;
uint8_t message[ESCARGOT_WS_HEADER_BASE_SIZE + ESCARGOT_WS_EXT_LEN16_SIZE + length];
message[0] = ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT | m_websocketMessageType;
const char expectedWebsocketKey[] = "Sec-WebSocket-Key:"; // Server-to-client WebSocket frames are not masked,
const size_t expectedWebsocketKeyLength = sizeof(expectedWebsocketKey) - 1; // therefore the masking key is not included in the header.
if (length <= ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH) {
const uint8_t type_byte = (m_websocketMessageType == ESCARGOT_DEBUGGER_WEBSOCKET_TEXT_FRAME ? 0 : 1);
headerLength = ESCARGOT_WS_HEADER_SIZE - ESCARGOT_WS_MASK_SIZE + type_byte;
while (true) { message[1] = static_cast<uint8_t>(length + type_byte);
if ((size_t)(message - websocketKey) < expectedWebsocketKeyLength) { if (type_byte) {
ESCARGOT_LOG_ERROR("Sec-WebSocket-Key not found.\n"); message[2] = type;
return false;
} }
} else {
headerLength = ESCARGOT_WS_HEADER_LEN16_SIZE - ESCARGOT_WS_MASK_SIZE;
if (websocketKey[0] == 'S' message[1] = ESCARGOT_WS_MESSAGE_16BIT_LENGTH_MARKER;
&& websocketKey[-1] == '\n' message[2] = static_cast<uint8_t>((length >> 8) & 0xFF);
&& websocketKey[-2] == '\r' message[3] = static_cast<uint8_t>(length & 0xFF);
&& memcmp(websocketKey, expectedWebsocketKey, expectedWebsocketKeyLength) == 0) {
websocketKey += expectedWebsocketKeyLength;
break;
}
websocketKey++;
} }
/* String terminated by double newlines. */ memcpy(message + headerLength, buffer, length);
while (*websocketKey == ' ') {
websocketKey++;
}
uint8_t* websocketKeyEnd = websocketKey; if (!tcpSend(m_socket, message, headerLength + length)) {
ESCARGOT_LOG_ERROR("Failed to send data via WebSocket connection.\n");
while (*websocketKeyEnd > ' ') { close(CloseAbortConnection);
websocketKeyEnd++;
}
/* Since the buffer is not needed anymore it can
* be reused for storing the SHA-1 key and Base64 string. */
const size_t sha1Length = 20;
DebuggerTcp::computeSha1(websocketKey,
(size_t)(websocketKeyEnd - websocketKey),
(const uint8_t*)"258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
36,
buffer);
/* The SHA-1 key is 20 bytes long but toBase64 expects a length
* divisible by 3 so an extra 0 is appended at the end. */
buffer[sha1Length] = 0;
toBase64(buffer, buffer + sha1Length + 1, sha1Length + 1);
/* Last value must be replaced by equal sign. */
const uint8_t responsePrefix[] = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ";
if (!tcpSend(socket, responsePrefix, sizeof(responsePrefix) - 1)
|| !tcpSend(socket, buffer + sha1Length + 1, 27)) {
return false; return false;
} }
const uint8_t responseSuffix[] = "=\r\n\r\n"; // ESCARGOT_LOG_INFO("Sent message: %s\n", static_cast<const char*>(buffer));
return tcpSend(socket, responseSuffix, sizeof(responseSuffix) - 1); return true;
} }
void DebuggerTcp::init(const char* options, Context* context) bool DebuggerTcp::receive(uint8_t* buffer, size_t& length)
{
size_t receivedLength = 0;
if (m_payloadLength == 0 || m_receiveBufferFill < m_headerLength + m_payloadLength) {
/* Cannot extract a whole message from the buffer. */
if (!tcpReceive(m_socket,
m_receiveBuffer + m_receiveBufferFill,
m_bufferSize - m_receiveBufferFill,
&receivedLength)) {
ESCARGOT_LOG_ERROR("Failed to receive data from WebSocket connection.\n");
close(CloseAbortConnection);
return false;
}
if (receivedLength == 0 && m_receiveBufferFill < m_headerLength) {
// ESCARGOT_LOG_INFO("Incomplete WebSocket frame header, waiting for more data.\n");
return false;
}
m_receiveBufferFill = static_cast<uint32_t>(m_receiveBufferFill + receivedLength);
}
if (m_payloadLength == 0) {
if (m_receiveBufferFill < ESCARGOT_WS_HEADER_BASE_SIZE) {
return false;
}
if ((m_receiveBuffer[0] & ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) != m_websocketMessageType) {
if ((m_receiveBuffer[0] & ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) == ESCARGOT_DEBUGGER_WEBSOCKET_CLOSE_FRAME) {
close(CloseEndConnection);
return false;
}
ESCARGOT_LOG_ERROR("Unsupported Websocket opcode.\n");
close(CloseProtocolUnsupported);
return false;
}
if ((m_receiveBuffer[0] & ~ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) != ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT
|| !(m_receiveBuffer[1] & ESCARGOT_DEBUGGER_WEBSOCKET_MASK_BIT)) {
ESCARGOT_LOG_ERROR("Unsupported Websocket message.\n");
close(CloseProtocolUnsupported);
return false;
}
uint8_t payloadLengthField = m_receiveBuffer[1] & ESCARGOT_DEBUGGER_WEBSOCKET_LENGTH_MASK;
if (payloadLengthField > ESCARGOT_WS_MESSAGE_16BIT_LENGTH_MARKER) {
ESCARGOT_LOG_ERROR("64-bit WebSocket payload length is not supported.\n");
close(CloseProtocolUnsupported);
return false;
}
if (payloadLengthField <= ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH) {
m_payloadLength = payloadLengthField;
m_headerLength = ESCARGOT_WS_HEADER_SIZE;
} else {
ASSERT(payloadLengthField == ESCARGOT_WS_MESSAGE_16BIT_LENGTH_MARKER);
// Ensure that the extended payload length field is available
if (m_receiveBufferFill < ESCARGOT_WS_HEADER_LEN16_SIZE) {
return false;
}
m_headerLength = ESCARGOT_WS_HEADER_LEN16_SIZE;
m_payloadLength = (static_cast<uint16_t>(m_receiveBuffer[2]) << 8) | static_cast<uint16_t>(m_receiveBuffer[3]);
}
if (m_payloadLength == 0) {
ESCARGOT_LOG_ERROR("Invalid WebSocket payload length: zero-length messages are not supported.\n");
close(CloseProtocolUnsupported);
return false;
}
}
const size_t totalSize = m_headerLength + m_payloadLength;
if (m_receiveBufferFill < totalSize) {
return false;
}
const uint8_t* mask = m_receiveBuffer + m_headerLength - ESCARGOT_WS_MASK_SIZE;
const uint8_t* mask_end = mask + ESCARGOT_WS_MASK_SIZE;
const uint8_t* source = mask_end;
uint8_t* buffer_end = buffer + m_payloadLength;
while (buffer < buffer_end) {
*buffer++ = *source++ ^ *mask++;
if (mask >= mask_end) {
mask -= 4;
}
}
*buffer_end = 0;
length = m_payloadLength;
m_headerLength = ESCARGOT_WS_HEADER_SIZE;
m_payloadLength = 0;
if (m_receiveBufferFill == totalSize) {
m_receiveBufferFill = 0;
return true;
}
m_receiveBufferFill = static_cast<uint32_t>(m_receiveBufferFill - totalSize);
memmove(m_receiveBuffer, m_receiveBuffer + totalSize, m_receiveBufferFill);
return true;
}
Debugger* DebuggerTcp::createDebugger(const char* options, Context* context)
{ {
uint16_t port = 6501; uint16_t port = 6501;
int timeout = -1; int timeout = -1;
String* skipSourceName = nullptr;
EscargotSocket clientSocket = 0;
if (options) { if (options) {
auto v = split(options, ';'); auto v = split(options, ';');
const char portOption[] = "--port="; const char portOption[] = "--port=";
@ -325,31 +345,29 @@ void DebuggerTcp::init(const char* options, Context* context)
} else if (s.find(skipOption) == 0) { } else if (s.find(skipOption) == 0) {
const char* skipStr = const_cast<const char*>(s.data() + sizeof(skipOption) - 1); const char* skipStr = const_cast<const char*>(s.data() + sizeof(skipOption) - 1);
size_t skipLen = strlen(skipStr); size_t skipLen = strlen(skipStr);
m_skipSourceName = String::fromASCII(skipStr, skipLen); skipSourceName = String::fromASCII(skipStr, skipLen);
} }
} }
} }
ASSERT(enabled() == false);
#ifdef WIN32 #ifdef WIN32
WSADATA wsaData; WSADATA wsaData;
int wsa_init_status = WSAStartup(MAKEWORD(2, 2), &wsaData); int wsa_init_status = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (wsa_init_status != NO_ERROR) { if (wsa_init_status != NO_ERROR) {
return; return nullptr;
} }
#endif /* WIN32*/ #endif /* WIN32*/
EscargotSocket serverSocket = socket(AF_INET, SOCK_STREAM, 0); EscargotSocket serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (m_socket == ESCARGOT_INVALID_SOCKET) { if (serverSocket == ESCARGOT_INVALID_SOCKET) {
return; return nullptr;
} }
if (!tcpConfigureSocket(serverSocket, port)) { if (!tcpConfigureSocket(serverSocket, port)) {
int error = tcpGetErrno(); int error = tcpGetErrno();
tcpCloseSocket(serverSocket); tcpCloseSocket(serverSocket);
tcpLogError(error); tcpLogError(error);
return; return nullptr;
} }
ESCARGOT_LOG_INFO("Waiting for client connection 0.0.0.0:%hd\n", port); ESCARGOT_LOG_INFO("Waiting for client connection 0.0.0.0:%hd\n", port);
@ -372,58 +390,72 @@ void DebuggerTcp::init(const char* options, Context* context)
if (timeout < 0) { if (timeout < 0) {
ESCARGOT_LOG_ERROR("Waiting for client connection error: timeout reached\n"); ESCARGOT_LOG_ERROR("Waiting for client connection error: timeout reached\n");
tcpCloseSocket(serverSocket); tcpCloseSocket(serverSocket);
return; return nullptr;
} }
} }
sockaddr_in addr; sockaddr_in addr;
socklen_t sinSize = sizeof(sockaddr_in); socklen_t sinSize = sizeof(sockaddr_in);
DebuggerHttpRouter httpRouter;
m_socket = accept(serverSocket, (sockaddr*)&addr, &sinSize); while (true) {
clientSocket = accept(serverSocket, (sockaddr*)&addr, &sinSize);
if (clientSocket == ESCARGOT_INVALID_SOCKET) {
tcpLogError(tcpGetErrno());
return nullptr;
}
#ifdef WIN32
u_long nonblockingEnabled = 1;
/* Set non-blocking mode. */
if (ioctlsocket(clientSocket, FIONBIO, &nonblockingEnabled) != NO_ERROR) {
tcpCloseSocket(clientSocket);
return nullptr;
}
#else /* !WIN32 */
int socketFlags = fcntl(clientSocket, F_GETFL, 0);
if (socketFlags < 0) {
tcpCloseSocket(clientSocket);
return nullptr;
}
/* Set non-blocking mode. */
if (fcntl(clientSocket, F_SETFL, socketFlags | O_NONBLOCK) == -1) {
tcpCloseSocket(clientSocket);
return nullptr;
}
#endif /* WIN32 */
ESCARGOT_LOG_INFO("Connected from: %s\n", inet_ntoa(addr.sin_addr));
if (!httpRouter.handleHttpRequest(clientSocket)) {
tcpCloseSocket(clientSocket);
return nullptr;
}
if (httpRouter.webSocketEstablished()) {
break;
}
// Close connection, waiting for the next request
tcpCloseSocket(clientSocket);
}
tcpCloseSocket(serverSocket); tcpCloseSocket(serverSocket);
if (m_socket == ESCARGOT_INVALID_SOCKET) { Debugger* client;
tcpLogError(tcpGetErrno());
return; if (httpRouter.client() == DebuggerClient::Escargot) {
client = new DebuggerEscargot(clientSocket, skipSourceName);
} else {
client = new DebuggerDevtools(clientSocket, skipSourceName);
} }
#ifdef WIN32 client->enable(context);
u_long nonblockingEnabled = 1; return client;
/* Set non-blocking mode. */
if (ioctlsocket(m_socket, FIONBIO, &nonblockingEnabled) != NO_ERROR) {
tcpCloseSocket(m_socket);
return;
}
#else /* !WIN32 */
int socketFlags = fcntl(m_socket, F_GETFL, 0);
if (socketFlags < 0) {
tcpCloseSocket(m_socket);
return;
}
/* Set non-blocking mode. */
if (fcntl(m_socket, F_SETFL, socketFlags | O_NONBLOCK) == -1) {
tcpCloseSocket(m_socket);
return;
}
#endif /* WIN32 */
ESCARGOT_LOG_INFO("Connected from: %s\n", inet_ntoa(addr.sin_addr));
if (!webSocketHandshake(m_socket)) {
tcpCloseSocket(m_socket);
return;
}
m_receiveBufferFill = 0;
m_messageLength = 0;
enable(context);
return DebuggerRemote::init(nullptr, context);
} }
bool DebuggerTcp::skipSourceCode(String* srcName) const bool DebuggerTcp::skipSourceCode(String* srcName) const
@ -436,34 +468,6 @@ bool DebuggerTcp::skipSourceCode(String* srcName) const
return srcName->contains(m_skipSourceName); return srcName->contains(m_skipSourceName);
} }
#define ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT 0x80
#define ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME 2
#define ESCARGOT_DEBUGGER_WEBSOCKET_CLOSE_FRAME 8
#define ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK 0x0f
#define ESCARGOT_DEBUGGER_WEBSOCKET_LENGTH_MASK 0x7f
#define ESCARGOT_DEBUGGER_WEBSOCKET_ONE_BYTE_LEN_MAX 125
#define ESCARGOT_DEBUGGER_WEBSOCKET_MASK_BIT 0x80
bool DebuggerTcp::send(uint8_t type, const void* buffer, size_t length)
{
ASSERT(enabled());
ASSERT(length < ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH);
uint8_t message[ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH + 2];
message[0] = ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT | ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME;
message[1] = (uint8_t)(length + 1);
message[2] = type;
memcpy(message + 3, buffer, length);
if (tcpSend(m_socket, message, length + 3)) {
return true;
}
close(CloseAbortConnection);
return false;
}
bool DebuggerTcp::isThereAnyEvent() bool DebuggerTcp::isThereAnyEvent()
{ {
// if there is remained receive buffer data, // if there is remained receive buffer data,
@ -484,91 +488,6 @@ bool DebuggerTcp::isThereAnyEvent()
return true; return true;
} }
bool DebuggerTcp::receive(uint8_t* buffer, size_t& length)
{
size_t receivedLength;
if (m_messageLength == 0 || m_receiveBufferFill < 2 + sizeof(uint32_t) + m_messageLength) {
/* Cannot extract a whole message from the buffer. */
if (!tcpReceive(m_socket,
m_receiveBuffer + m_receiveBufferFill,
ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH + 2 + sizeof(uint32_t) - m_receiveBufferFill,
&receivedLength)) {
close(CloseAbortConnection);
return false;
}
if (receivedLength == 0 && m_receiveBufferFill < (2 + sizeof(uint32_t))) {
return false;
}
m_receiveBufferFill = (uint8_t)(m_receiveBufferFill + receivedLength);
}
if (m_messageLength == 0) {
if (m_receiveBufferFill < 3) {
return false;
}
if ((m_receiveBuffer[0] & ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) != ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME) {
if ((m_receiveBuffer[0] & ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) == ESCARGOT_DEBUGGER_WEBSOCKET_CLOSE_FRAME) {
close(CloseEndConnection);
return false;
}
ESCARGOT_LOG_ERROR("Unsupported Websocket opcode.\n");
close(CloseProtocolUnsupported);
return false;
}
if ((m_receiveBuffer[0] & ~ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) != ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT
|| !(m_receiveBuffer[1] & ESCARGOT_DEBUGGER_WEBSOCKET_MASK_BIT)) {
ESCARGOT_LOG_ERROR("Unsupported Websocket message.\n");
close(CloseProtocolUnsupported);
return false;
}
m_messageLength = (uint8_t)(m_receiveBuffer[1] & ESCARGOT_DEBUGGER_WEBSOCKET_LENGTH_MASK);
if (m_messageLength == 0 || m_messageLength > ESCARGOT_DEBUGGER_WEBSOCKET_ONE_BYTE_LEN_MAX) {
ESCARGOT_LOG_ERROR("Unsupported Websocket message size.\n");
close(CloseProtocolUnsupported);
return false;
}
}
size_t totalSize = 2 + sizeof(uint32_t) + m_messageLength;
if (m_receiveBufferFill < totalSize) {
return false;
}
uint8_t* mask = m_receiveBuffer + 2;
uint8_t* mask_end = mask + sizeof(uint32_t);
uint8_t* source = mask_end;
uint8_t* buffer_end = buffer + m_messageLength;
while (buffer < buffer_end) {
*buffer++ = *source++ ^ *mask++;
if (mask >= mask_end) {
mask -= 4;
}
}
length = m_messageLength;
m_messageLength = 0;
if (m_receiveBufferFill == totalSize) {
m_receiveBufferFill = 0;
return true;
}
m_receiveBufferFill = (uint8_t)(m_receiveBufferFill - totalSize);
memmove(m_receiveBuffer, m_receiveBuffer + totalSize, m_receiveBufferFill);
return true;
}
void DebuggerTcp::close(CloseReason reason) void DebuggerTcp::close(CloseReason reason)
{ {
if (!enabled()) { if (!enabled()) {

View file

@ -31,39 +31,75 @@ typedef SOCKET EscargotSocket;
#else /* !WIN32 */ #else /* !WIN32 */
typedef int EscargotSocket; typedef int EscargotSocket;
#endif /* WIN32 */ #endif /* WIN32 */
#define ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH 125
class DebuggerTcp : public DebuggerRemote { #define ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT 0x80
#define ESCARGOT_DEBUGGER_WEBSOCKET_TEXT_FRAME 1
#define ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME 2
#define ESCARGOT_DEBUGGER_WEBSOCKET_CLOSE_FRAME 8
#define ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK 0x0f
#define ESCARGOT_DEBUGGER_WEBSOCKET_LENGTH_MASK 0x7f
#define ESCARGOT_DEBUGGER_WEBSOCKET_ONE_BYTE_LEN_MAX 125
#define ESCARGOT_DEBUGGER_WEBSOCKET_MASK_BIT 0x80
#define ESCARGOT_WS_HEADER_BASE_SIZE 2
#define ESCARGOT_WS_EXT_LEN16_SIZE 2
#define ESCARGOT_WS_MASK_SIZE 4
#define ESCARGOT_WS_MESSAGE_16BIT_LENGTH_MARKER 126
#define ESCARGOT_WS_MAX_MESSAGE_LENGTH 65535
#define ESCARGOT_WS_HEADER_SIZE (ESCARGOT_WS_HEADER_BASE_SIZE + ESCARGOT_WS_MASK_SIZE)
#define ESCARGOT_WS_HEADER_LEN16_SIZE (ESCARGOT_WS_HEADER_SIZE + ESCARGOT_WS_EXT_LEN16_SIZE)
#define ESCARGOT_WS_BUFFER_SIZE (ESCARGOT_WS_HEADER_LEN16_SIZE + ESCARGOT_WS_MAX_MESSAGE_LENGTH)
class DebuggerTcp : public Debugger {
public: public:
DebuggerTcp() DebuggerTcp(const EscargotSocket socket, String* skipSource, const uint32_t bufferSize, const uint8_t type)
: m_socket(0) : m_socket(socket)
, m_receiveBuffer{} , m_bufferSize(bufferSize)
, m_receiveBuffer(new uint8_t[bufferSize])
, m_payloadLength(0)
, m_receiveBufferFill(0) , m_receiveBufferFill(0)
, m_messageLength(0) , m_headerLength(0)
, m_skipSourceName(nullptr) , m_websocketMessageType(type)
, m_skipSourceName(skipSource)
{ {
} }
virtual void init(const char* options, Context* context) override; static Debugger* createDebugger(const char* options, Context* context);
void init(const char* options, Context* context) override {}
virtual bool skipSourceCode(String* srcName) const override; bool skipSourceCode(String* srcName) const override;
static void computeSha1(const uint8_t* source1, size_t source1Length, static void computeSha1(const uint8_t* source1, size_t source1Length,
const uint8_t* source2, size_t source2Length, const uint8_t* source2, size_t source2Length,
uint8_t destination[20]); uint8_t destination[20]);
protected: static bool tcpReceive(EscargotSocket socket, uint8_t* message, size_t maxLength, size_t* receivedLength);
virtual bool send(uint8_t type, const void* buffer, size_t length) override; static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messageLength);
virtual bool receive(uint8_t* buffer, size_t& length) override;
virtual bool isThereAnyEvent() override;
virtual void close(CloseReason reason) override;
private: protected:
void receiveData(); enum CloseReason {
CloseEndConnection,
CloseAbortConnection,
CloseProtocolUnsupported,
CloseProtocolError,
};
bool send(uint8_t type, const void* buffer, size_t length);
bool receive(uint8_t* buffer, size_t& length);
bool isThereAnyEvent();
void close(CloseReason reason);
EscargotSocket m_socket; EscargotSocket m_socket;
uint8_t m_receiveBuffer[2 + sizeof(uint32_t) + ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH]; uint32_t m_bufferSize;
uint8_t* m_receiveBuffer;
uint16_t m_payloadLength;
uint8_t m_receiveBufferFill; uint8_t m_receiveBufferFill;
uint8_t m_messageLength; uint8_t m_headerLength;
uint8_t m_websocketMessageType;
// skip generating debugging bytecode for source code whose name contains m_skipSourceName // skip generating debugging bytecode for source code whose name contains m_skipSourceName
String* m_skipSourceName; String* m_skipSourceName;

View file

@ -158,14 +158,14 @@ ASTAllocator& Context::astAllocator()
#ifdef ESCARGOT_DEBUGGER #ifdef ESCARGOT_DEBUGGER
bool Context::initDebuggerRemote(const char* options) bool Context::initDebugger(const char* options)
{ {
if (debuggerEnabled()) { if (debuggerEnabled()) {
// debugger cannot be re-initialized // debugger cannot be re-initialized
return false; return false;
} }
Debugger::createDebuggerRemote(options, this); Debugger::createDebugger(options, this);
return m_debugger != nullptr; return m_debugger != nullptr;
} }

View file

@ -323,7 +323,7 @@ public:
return m_debugger; return m_debugger;
} }
bool initDebuggerRemote(const char* options); bool initDebugger(const char* options);
void initDebugger(Debugger* debugger); void initDebugger(Debugger* debugger);
void removeDebugger(); void removeDebugger();
bool debuggerEnabled() const; bool debuggerEnabled() const;

View file

@ -294,7 +294,8 @@ private:
// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-global-environment-records // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-global-environment-records
class GlobalEnvironmentRecord : public EnvironmentRecord { class GlobalEnvironmentRecord : public EnvironmentRecord {
#ifdef ESCARGOT_DEBUGGER #ifdef ESCARGOT_DEBUGGER
friend class DebuggerRemote; friend class DebuggerEscargot;
friend class DebuggerDevtools;
friend class DebuggerAPI; friend class DebuggerAPI;
#endif /* ESCARGOT_DEBUGGER */ #endif /* ESCARGOT_DEBUGGER */
public: public:
@ -587,7 +588,8 @@ public:
// DeclarativeEnvironmentRecordNotIndexed record does not create binding self likes FunctionEnvironmentRecord // DeclarativeEnvironmentRecordNotIndexed record does not create binding self likes FunctionEnvironmentRecord
class DeclarativeEnvironmentRecordNotIndexed : public DeclarativeEnvironmentRecord { class DeclarativeEnvironmentRecordNotIndexed : public DeclarativeEnvironmentRecord {
#ifdef ESCARGOT_DEBUGGER #ifdef ESCARGOT_DEBUGGER
friend class DebuggerRemote; friend class DebuggerEscargot;
friend class DebuggerDevtools;
friend class DebuggerAPI; friend class DebuggerAPI;
#endif /* ESCARGOT_DEBUGGER */ #endif /* ESCARGOT_DEBUGGER */
public: public:
@ -1202,7 +1204,8 @@ public:
class ModuleEnvironmentRecord : public DeclarativeEnvironmentRecord { class ModuleEnvironmentRecord : public DeclarativeEnvironmentRecord {
#ifdef ESCARGOT_DEBUGGER #ifdef ESCARGOT_DEBUGGER
friend class DebuggerRemote; friend class DebuggerEscargot;
friend class DebuggerDevtools;
friend class DebuggerAPI; friend class DebuggerAPI;
#endif /* ESCARGOT_DEBUGGER */ #endif /* ESCARGOT_DEBUGGER */
public: public:

View file

@ -1202,7 +1202,7 @@ int main(int argc, char* argv[])
continue; continue;
} }
if (strcmp(argv[i], "--start-debug-server") == 0) { if (strcmp(argv[i], "--start-debug-server") == 0) {
context->initDebuggerRemote(nullptr); context->initDebugger(nullptr);
continue; continue;
} }
if (strcmp(argv[i], "--debugger-wait-source") == 0) { if (strcmp(argv[i], "--debugger-wait-source") == 0) {

View file

@ -2752,7 +2752,7 @@ TEST(Debugger, Basic)
DebuggerTest* debuggerTest = new DebuggerTest(); DebuggerTest* debuggerTest = new DebuggerTest();
StringRef* fileName = StringRef::createFromUTF8(debuggerFileNameString, sizeof(debuggerFileNameString) - 1); StringRef* fileName = StringRef::createFromUTF8(debuggerFileNameString, sizeof(debuggerFileNameString) - 1);
context->initDebugger(debuggerTest); context->initDebuggerClient(debuggerTest);
StringRef* source = StringRef::createFromUTF8(debuggerSourceString1, sizeof(debuggerSourceString1) - 1); StringRef* source = StringRef::createFromUTF8(debuggerSourceString1, sizeof(debuggerSourceString1) - 1);
evalScript(context, source, fileName, false); evalScript(context, source, fileName, false);
@ -2778,7 +2778,7 @@ TEST(Debugger, RemoteOption)
PersistentRefHolder<VMInstanceRef> instance = VMInstanceRef::create(); PersistentRefHolder<VMInstanceRef> instance = VMInstanceRef::create();
PersistentRefHolder<ContextRef> context = createEscargotContext(instance.get()); PersistentRefHolder<ContextRef> context = createEscargotContext(instance.get());
// 100ms // 100ms
EXPECT_FALSE(context->initDebuggerRemote("--accept-timeout=100")); EXPECT_FALSE(context->initDebugger("--accept-timeout=100"));
EXPECT_FALSE(context->isDebuggerRunning()); EXPECT_FALSE(context->isDebuggerRunning());
EXPECT_FALSE(context->isWaitBeforeExit()); EXPECT_FALSE(context->isWaitBeforeExit());
@ -2873,7 +2873,7 @@ TEST(Debugger, ObjectStore)
DebuggerTest* debuggerTest = new DebuggerTest(); DebuggerTest* debuggerTest = new DebuggerTest();
StringRef* fileName = StringRef::createFromUTF8(debuggerFileNameString, sizeof(debuggerFileNameString) - 1); StringRef* fileName = StringRef::createFromUTF8(debuggerFileNameString, sizeof(debuggerFileNameString) - 1);
context->initDebugger(debuggerTest); context->initDebuggerClient(debuggerTest);
StringRef* source = StringRef::createFromUTF8(debuggerSourceString1, sizeof(debuggerSourceString1) - 1); StringRef* source = StringRef::createFromUTF8(debuggerSourceString1, sizeof(debuggerSourceString1) - 1);
evalScript(context, source, fileName, false); evalScript(context, source, fileName, false);

View file

@ -2001,7 +2001,7 @@ public:
\param stackCapacity Optional initial capacity of stack in bytes. \param stackCapacity Optional initial capacity of stack in bytes.
\param stackAllocator Optional allocator for allocating memory for stack. \param stackAllocator Optional allocator for allocating memory for stack.
*/ */
#if defined(ESCARGOT) && !defined(COMPILER_MSVC) #if defined(ESCARGOT) && (defined(COMPILER_GCC) || defined(COMPILER_CLANG))
__attribute__((__noinline__)) GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) __attribute__((__noinline__)) GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0)
: :
#else #else