mirror of
https://github.com/Samsung/escargot.git
synced 2026-06-22 10:01:50 +00:00
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:
parent
989e6922b6
commit
97e8115ab1
21 changed files with 2767 additions and 1613 deletions
22
README.md
22
README.md
|
|
@ -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 |
|
||||
| **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 |
|
||||
| **DEBUGGER** | Enable Debug server | -DESCARGOT_DEBUGGER | ON/OFF | OFF |
|
||||
|
||||
### Linux
|
||||
|
||||
|
|
@ -135,6 +136,27 @@ cd out
|
|||
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 ✅
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -1984,6 +1984,7 @@ bool DebuggerOperationsRef::updateBreakpoint(WeakCodeRef* weakCodeRef, uint32_t
|
|||
|
||||
class DebuggerC : public Debugger {
|
||||
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 stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) override;
|
||||
virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) override;
|
||||
|
|
@ -3385,7 +3386,7 @@ void ContextRef::throwException(ValueRef* exceptionValue)
|
|||
toImpl(this)->throwException(s, toImpl(exceptionValue));
|
||||
}
|
||||
|
||||
bool ContextRef::initDebugger(DebuggerOperationsRef::DebuggerClient* debuggerClient)
|
||||
bool ContextRef::initDebuggerClient(DebuggerOperationsRef::DebuggerClient* debuggerClient)
|
||||
{
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
Context* context = toImpl(this);
|
||||
|
|
@ -3414,10 +3415,10 @@ bool ContextRef::disableDebugger()
|
|||
#endif
|
||||
}
|
||||
|
||||
bool ContextRef::initDebuggerRemote(const char* options)
|
||||
bool ContextRef::initDebugger(const char* options)
|
||||
{
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
return toImpl(this)->initDebuggerRemote(options);
|
||||
return toImpl(this)->initDebugger(options);
|
||||
#else /* !ESCARGOT_DEBUGGER */
|
||||
return false;
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
|
|
|
|||
|
|
@ -950,11 +950,11 @@ public:
|
|||
bool canThrowException();
|
||||
void throwException(ValueRef* exceptionValue);
|
||||
|
||||
bool initDebugger(DebuggerOperationsRef::DebuggerClient* debuggerClient);
|
||||
bool initDebuggerClient(DebuggerOperationsRef::DebuggerClient* debuggerClient);
|
||||
bool disableDebugger();
|
||||
// available options(separator is ';')
|
||||
// "--port=6501", default for TCP debugger
|
||||
bool initDebuggerRemote(const char* options);
|
||||
bool initDebugger(const char* options);
|
||||
bool isDebuggerRunning();
|
||||
bool isWaitBeforeExit();
|
||||
void printDebugger(StringRef* output);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -28,14 +28,11 @@ namespace Escargot {
|
|||
#define ESCARGOT_DEBUGGER_MAX_STACK_TRACE_LENGTH 8
|
||||
|
||||
/* 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_IN_WAIT_MODE (nullptr)
|
||||
#define ESCARGOT_DEBUGGER_IN_EVAL_MODE (reinterpret_cast<ExecutionState*>(0x1))
|
||||
#define ESCARGOT_DEBUGGER_ALWAYS_STOP (reinterpret_cast<ExecutionState*>(0x2))
|
||||
#define ESCARGOT_DEBUGGER_NO_STACK_TRACE_RESTORE (reinterpret_cast<ExecutionState*>(0x1))
|
||||
#define ESCARGOT_DEBUGGER_MAX_VARIABLE_LENGTH 128
|
||||
|
||||
class Context;
|
||||
class Object;
|
||||
|
|
@ -158,8 +155,7 @@ public:
|
|||
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 stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) = 0;
|
||||
virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) = 0;
|
||||
|
|
@ -176,6 +172,11 @@ public:
|
|||
static SavedStackTraceDataVector* saveStackTrace(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:
|
||||
Debugger()
|
||||
|
|
@ -193,7 +194,6 @@ protected:
|
|||
return m_context != nullptr;
|
||||
}
|
||||
|
||||
void enable(Context* context);
|
||||
void disable();
|
||||
|
||||
virtual bool processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest = true) = 0;
|
||||
|
|
@ -201,6 +201,7 @@ protected:
|
|||
uint32_t m_delay;
|
||||
ExecutionState* m_stopState;
|
||||
std::vector<BreakpointLocationsInfo*> m_breakpointLocationsVector;
|
||||
Vector<uintptr_t, GCUtil::gc_malloc_atomic_allocator<uintptr_t>> m_releasedFunctions;
|
||||
|
||||
private:
|
||||
Context* m_context;
|
||||
|
|
@ -212,246 +213,6 @@ private:
|
|||
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
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
|
||||
|
|
|
|||
410
src/debugger/DebuggerDevtools.cpp
Normal file
410
src/debugger/DebuggerDevtools.cpp
Normal 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 */
|
||||
105
src/debugger/DebuggerDevtools.h
Normal file
105
src/debugger/DebuggerDevtools.h
Normal 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
|
||||
164
src/debugger/DebuggerDevtoolsMessageBuilder.cpp
Normal file
164
src/debugger/DebuggerDevtoolsMessageBuilder.cpp
Normal 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 */
|
||||
41
src/debugger/DebuggerDevtoolsMessageBuilder.h
Normal file
41
src/debugger/DebuggerDevtoolsMessageBuilder.h
Normal 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
|
||||
1112
src/debugger/DebuggerEscargot.cpp
Normal file
1112
src/debugger/DebuggerEscargot.cpp
Normal file
File diff suppressed because it is too large
Load diff
267
src/debugger/DebuggerEscargot.h
Normal file
267
src/debugger/DebuggerEscargot.h
Normal 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
|
||||
321
src/debugger/DebuggerHttpRouter.cpp
Normal file
321
src/debugger/DebuggerHttpRouter.cpp
Normal 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 */
|
||||
70
src/debugger/DebuggerHttpRouter.h
Normal file
70
src/debugger/DebuggerHttpRouter.h
Normal 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
|
||||
|
|
@ -19,6 +19,9 @@
|
|||
|
||||
#include "Escargot.h"
|
||||
#include "DebuggerTcp.h"
|
||||
#include "DebuggerDevtools.h"
|
||||
#include "DebuggerEscargot.h"
|
||||
#include "DebuggerHttpRouter.h"
|
||||
#include "runtime/String.h" // for split function
|
||||
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
|
|
@ -110,7 +113,7 @@ static inline void tcpCloseSocket(EscargotSocket socket)
|
|||
#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 {
|
||||
#ifdef OS_POSIX
|
||||
|
|
@ -122,7 +125,7 @@ static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messag
|
|||
}
|
||||
#endif /* OS_POSIX */
|
||||
|
||||
ssize_t sentBytes = send(socket, message, messageLength, 0);
|
||||
ssize_t sentBytes = Escargot::send(socket, message, messageLength, 0);
|
||||
|
||||
if (sentBytes < 0) {
|
||||
int errorNumber = tcpGetErrno();
|
||||
|
|
@ -142,7 +145,7 @@ static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messag
|
|||
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;
|
||||
|
||||
|
|
@ -162,152 +165,169 @@ static bool tcpReceive(EscargotSocket socket, uint8_t* message, size_t maxLength
|
|||
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) {
|
||||
return (uint8_t)(value + 'A');
|
||||
}
|
||||
ASSERT(enabled());
|
||||
ASSERT(m_websocketMessageType == ESCARGOT_DEBUGGER_WEBSOCKET_TEXT_FRAME
|
||||
|| (m_websocketMessageType == ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME && length <= ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH));
|
||||
|
||||
if (value < 52) {
|
||||
return (uint8_t)(value - 26 + 'a');
|
||||
}
|
||||
|
||||
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");
|
||||
if (length > ESCARGOT_WS_MAX_MESSAGE_LENGTH) {
|
||||
ESCARGOT_LOG_ERROR("Cannot send WebSocket payload: 64-bit payload length is not supported.\n");
|
||||
close(CloseAbortConnection);
|
||||
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:";
|
||||
const size_t expectedWebsocketKeyLength = sizeof(expectedWebsocketKey) - 1;
|
||||
// Server-to-client WebSocket frames are not masked,
|
||||
// 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) {
|
||||
if ((size_t)(message - websocketKey) < expectedWebsocketKeyLength) {
|
||||
ESCARGOT_LOG_ERROR("Sec-WebSocket-Key not found.\n");
|
||||
return false;
|
||||
message[1] = static_cast<uint8_t>(length + type_byte);
|
||||
if (type_byte) {
|
||||
message[2] = type;
|
||||
}
|
||||
} else {
|
||||
headerLength = ESCARGOT_WS_HEADER_LEN16_SIZE - ESCARGOT_WS_MASK_SIZE;
|
||||
|
||||
if (websocketKey[0] == 'S'
|
||||
&& websocketKey[-1] == '\n'
|
||||
&& websocketKey[-2] == '\r'
|
||||
&& memcmp(websocketKey, expectedWebsocketKey, expectedWebsocketKeyLength) == 0) {
|
||||
websocketKey += expectedWebsocketKeyLength;
|
||||
break;
|
||||
}
|
||||
|
||||
websocketKey++;
|
||||
message[1] = ESCARGOT_WS_MESSAGE_16BIT_LENGTH_MARKER;
|
||||
message[2] = static_cast<uint8_t>((length >> 8) & 0xFF);
|
||||
message[3] = static_cast<uint8_t>(length & 0xFF);
|
||||
}
|
||||
|
||||
/* String terminated by double newlines. */
|
||||
while (*websocketKey == ' ') {
|
||||
websocketKey++;
|
||||
}
|
||||
memcpy(message + headerLength, buffer, length);
|
||||
|
||||
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. */
|
||||
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)) {
|
||||
if (!tcpSend(m_socket, message, headerLength + length)) {
|
||||
ESCARGOT_LOG_ERROR("Failed to send data via WebSocket connection.\n");
|
||||
close(CloseAbortConnection);
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t responseSuffix[] = "=\r\n\r\n";
|
||||
return tcpSend(socket, responseSuffix, sizeof(responseSuffix) - 1);
|
||||
// ESCARGOT_LOG_INFO("Sent message: %s\n", static_cast<const char*>(buffer));
|
||||
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;
|
||||
int timeout = -1;
|
||||
|
||||
String* skipSourceName = nullptr;
|
||||
EscargotSocket clientSocket = 0;
|
||||
|
||||
if (options) {
|
||||
auto v = split(options, ';');
|
||||
const char portOption[] = "--port=";
|
||||
|
|
@ -325,31 +345,29 @@ void DebuggerTcp::init(const char* options, Context* context)
|
|||
} else if (s.find(skipOption) == 0) {
|
||||
const char* skipStr = const_cast<const char*>(s.data() + sizeof(skipOption) - 1);
|
||||
size_t skipLen = strlen(skipStr);
|
||||
m_skipSourceName = String::fromASCII(skipStr, skipLen);
|
||||
skipSourceName = String::fromASCII(skipStr, skipLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(enabled() == false);
|
||||
|
||||
#ifdef WIN32
|
||||
WSADATA wsaData;
|
||||
int wsa_init_status = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
if (wsa_init_status != NO_ERROR) {
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
#endif /* WIN32*/
|
||||
|
||||
EscargotSocket serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (m_socket == ESCARGOT_INVALID_SOCKET) {
|
||||
return;
|
||||
if (serverSocket == ESCARGOT_INVALID_SOCKET) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!tcpConfigureSocket(serverSocket, port)) {
|
||||
int error = tcpGetErrno();
|
||||
tcpCloseSocket(serverSocket);
|
||||
tcpLogError(error);
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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) {
|
||||
ESCARGOT_LOG_ERROR("Waiting for client connection error: timeout reached\n");
|
||||
tcpCloseSocket(serverSocket);
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
sockaddr_in addr;
|
||||
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);
|
||||
|
||||
if (m_socket == ESCARGOT_INVALID_SOCKET) {
|
||||
tcpLogError(tcpGetErrno());
|
||||
return;
|
||||
Debugger* client;
|
||||
|
||||
if (httpRouter.client() == DebuggerClient::Escargot) {
|
||||
client = new DebuggerEscargot(clientSocket, skipSourceName);
|
||||
} else {
|
||||
client = new DebuggerDevtools(clientSocket, skipSourceName);
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
u_long nonblockingEnabled = 1;
|
||||
|
||||
/* 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);
|
||||
client->enable(context);
|
||||
return client;
|
||||
}
|
||||
|
||||
bool DebuggerTcp::skipSourceCode(String* srcName) const
|
||||
|
|
@ -436,34 +468,6 @@ bool DebuggerTcp::skipSourceCode(String* srcName) const
|
|||
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()
|
||||
{
|
||||
// if there is remained receive buffer data,
|
||||
|
|
@ -484,91 +488,6 @@ bool DebuggerTcp::isThereAnyEvent()
|
|||
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)
|
||||
{
|
||||
if (!enabled()) {
|
||||
|
|
|
|||
|
|
@ -31,39 +31,75 @@ typedef SOCKET EscargotSocket;
|
|||
#else /* !WIN32 */
|
||||
typedef int EscargotSocket;
|
||||
#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:
|
||||
DebuggerTcp()
|
||||
: m_socket(0)
|
||||
, m_receiveBuffer{}
|
||||
DebuggerTcp(const EscargotSocket socket, String* skipSource, const uint32_t bufferSize, const uint8_t type)
|
||||
: m_socket(socket)
|
||||
, m_bufferSize(bufferSize)
|
||||
, m_receiveBuffer(new uint8_t[bufferSize])
|
||||
, m_payloadLength(0)
|
||||
, m_receiveBufferFill(0)
|
||||
, m_messageLength(0)
|
||||
, m_skipSourceName(nullptr)
|
||||
, m_headerLength(0)
|
||||
, 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,
|
||||
const uint8_t* source2, size_t source2Length,
|
||||
uint8_t destination[20]);
|
||||
|
||||
protected:
|
||||
virtual bool send(uint8_t type, const void* buffer, size_t length) override;
|
||||
virtual bool receive(uint8_t* buffer, size_t& length) override;
|
||||
virtual bool isThereAnyEvent() override;
|
||||
virtual void close(CloseReason reason) override;
|
||||
static bool tcpReceive(EscargotSocket socket, uint8_t* message, size_t maxLength, size_t* receivedLength);
|
||||
static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messageLength);
|
||||
|
||||
private:
|
||||
void receiveData();
|
||||
protected:
|
||||
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;
|
||||
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_messageLength;
|
||||
uint8_t m_headerLength;
|
||||
uint8_t m_websocketMessageType;
|
||||
|
||||
|
||||
// skip generating debugging bytecode for source code whose name contains m_skipSourceName
|
||||
String* m_skipSourceName;
|
||||
|
|
|
|||
|
|
@ -158,14 +158,14 @@ ASTAllocator& Context::astAllocator()
|
|||
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
|
||||
bool Context::initDebuggerRemote(const char* options)
|
||||
bool Context::initDebugger(const char* options)
|
||||
{
|
||||
if (debuggerEnabled()) {
|
||||
// debugger cannot be re-initialized
|
||||
return false;
|
||||
}
|
||||
|
||||
Debugger::createDebuggerRemote(options, this);
|
||||
Debugger::createDebugger(options, this);
|
||||
return m_debugger != nullptr;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ public:
|
|||
return m_debugger;
|
||||
}
|
||||
|
||||
bool initDebuggerRemote(const char* options);
|
||||
bool initDebugger(const char* options);
|
||||
void initDebugger(Debugger* debugger);
|
||||
void removeDebugger();
|
||||
bool debuggerEnabled() const;
|
||||
|
|
|
|||
|
|
@ -294,7 +294,8 @@ private:
|
|||
// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-global-environment-records
|
||||
class GlobalEnvironmentRecord : public EnvironmentRecord {
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
friend class DebuggerRemote;
|
||||
friend class DebuggerEscargot;
|
||||
friend class DebuggerDevtools;
|
||||
friend class DebuggerAPI;
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
public:
|
||||
|
|
@ -587,7 +588,8 @@ public:
|
|||
// DeclarativeEnvironmentRecordNotIndexed record does not create binding self likes FunctionEnvironmentRecord
|
||||
class DeclarativeEnvironmentRecordNotIndexed : public DeclarativeEnvironmentRecord {
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
friend class DebuggerRemote;
|
||||
friend class DebuggerEscargot;
|
||||
friend class DebuggerDevtools;
|
||||
friend class DebuggerAPI;
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
public:
|
||||
|
|
@ -1202,7 +1204,8 @@ public:
|
|||
|
||||
class ModuleEnvironmentRecord : public DeclarativeEnvironmentRecord {
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
friend class DebuggerRemote;
|
||||
friend class DebuggerEscargot;
|
||||
friend class DebuggerDevtools;
|
||||
friend class DebuggerAPI;
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -1202,7 +1202,7 @@ int main(int argc, char* argv[])
|
|||
continue;
|
||||
}
|
||||
if (strcmp(argv[i], "--start-debug-server") == 0) {
|
||||
context->initDebuggerRemote(nullptr);
|
||||
context->initDebugger(nullptr);
|
||||
continue;
|
||||
}
|
||||
if (strcmp(argv[i], "--debugger-wait-source") == 0) {
|
||||
|
|
|
|||
|
|
@ -2752,7 +2752,7 @@ TEST(Debugger, Basic)
|
|||
DebuggerTest* debuggerTest = new DebuggerTest();
|
||||
StringRef* fileName = StringRef::createFromUTF8(debuggerFileNameString, sizeof(debuggerFileNameString) - 1);
|
||||
|
||||
context->initDebugger(debuggerTest);
|
||||
context->initDebuggerClient(debuggerTest);
|
||||
|
||||
StringRef* source = StringRef::createFromUTF8(debuggerSourceString1, sizeof(debuggerSourceString1) - 1);
|
||||
evalScript(context, source, fileName, false);
|
||||
|
|
@ -2778,7 +2778,7 @@ TEST(Debugger, RemoteOption)
|
|||
PersistentRefHolder<VMInstanceRef> instance = VMInstanceRef::create();
|
||||
PersistentRefHolder<ContextRef> context = createEscargotContext(instance.get());
|
||||
// 100ms
|
||||
EXPECT_FALSE(context->initDebuggerRemote("--accept-timeout=100"));
|
||||
EXPECT_FALSE(context->initDebugger("--accept-timeout=100"));
|
||||
EXPECT_FALSE(context->isDebuggerRunning());
|
||||
EXPECT_FALSE(context->isWaitBeforeExit());
|
||||
|
||||
|
|
@ -2873,7 +2873,7 @@ TEST(Debugger, ObjectStore)
|
|||
DebuggerTest* debuggerTest = new DebuggerTest();
|
||||
StringRef* fileName = StringRef::createFromUTF8(debuggerFileNameString, sizeof(debuggerFileNameString) - 1);
|
||||
|
||||
context->initDebugger(debuggerTest);
|
||||
context->initDebuggerClient(debuggerTest);
|
||||
|
||||
StringRef* source = StringRef::createFromUTF8(debuggerSourceString1, sizeof(debuggerSourceString1) - 1);
|
||||
evalScript(context, source, fileName, false);
|
||||
|
|
|
|||
|
|
@ -2001,7 +2001,7 @@ public:
|
|||
\param stackCapacity Optional initial capacity of stack in bytes.
|
||||
\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)
|
||||
:
|
||||
#else
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue