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 |
|
| **SHADOWREALM** | Enable ShadowRealm support | -ESCARGOT_SHADOWREALM | ON/OFF | OFF |
|
||||||
| **SMALL_CONFIG** | Enable aggressive memory optimizations for tiny devices | -DESCARGOT_SMALL_CONFIG | ON/OFF | OFF |
|
| **SMALL_CONFIG** | Enable aggressive memory optimizations for tiny devices | -DESCARGOT_SMALL_CONFIG | ON/OFF | OFF |
|
||||||
| **TEST** | Enable additional features used only for testing | -DESCARGOT_TEST | ON/OFF | OFF |
|
| **TEST** | Enable additional features used only for testing | -DESCARGOT_TEST | ON/OFF | OFF |
|
||||||
|
| **DEBUGGER** | Enable Debug server | -DESCARGOT_DEBUGGER | ON/OFF | OFF |
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
|
|
@ -135,6 +136,27 @@ cd out
|
||||||
msbuild ESCARGOT.sln /property:Configuration=Release /p:platform=[ Win32 | x64 ]
|
msbuild ESCARGOT.sln /property:Configuration=Release /p:platform=[ Win32 | x64 ]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Debugger
|
||||||
|
|
||||||
|
Make sure Escargot is built with the `-DESCARGOT_DEBUGGER=1` flag (off by default) enabled;
|
||||||
|
then start Escargot with the `--start-debug-server` option.
|
||||||
|
|
||||||
|
### Connect using a debugger client
|
||||||
|
|
||||||
|
- Escargot python debugger
|
||||||
|
- run `./tools/debugger/debugger.py`; It will automatically connect to a debug server on the default port `6501`
|
||||||
|
- run `./tools/debugger/debugger.py --help` for a list of options
|
||||||
|
- [Visual Studio Code extension](https://github.com/Samsung/escargot-vscode-extension/?tab=readme-ov-file#how-to-use)
|
||||||
|
- Chrome Devtools `⚠️ Early in development ⚠️`
|
||||||
|
- Initial setup:
|
||||||
|
- Navigate to [chrome://inspect](chrome://inspect)
|
||||||
|
- Make sure *Discover network targets* is enabled; click configure
|
||||||
|
- Add `localhost:6501` as a target; click Done
|
||||||
|
- Usage:
|
||||||
|
- The started debug server will be listed in the *Remote Target* list (If it is not, the page may need to be reloaded using the browser reload button)
|
||||||
|
- Click `inspect`
|
||||||
|
- A new window with the Chrome Devtools debugger UI will open
|
||||||
|
|
||||||
## Testing ✅
|
## Testing ✅
|
||||||
|
|
||||||
Escargot supports various benchmark sets, which can be run using the [tools/run-tests.py](https://github.com/Samsung/escargot/blob/master/tools/run-tests.py) script.
|
Escargot supports various benchmark sets, which can be run using the [tools/run-tests.py](https://github.com/Samsung/escargot/blob/master/tools/run-tests.py) script.
|
||||||
|
|
|
||||||
|
|
@ -1984,6 +1984,7 @@ bool DebuggerOperationsRef::updateBreakpoint(WeakCodeRef* weakCodeRef, uint32_t
|
||||||
|
|
||||||
class DebuggerC : public Debugger {
|
class DebuggerC : public Debugger {
|
||||||
public:
|
public:
|
||||||
|
virtual void init(const char* options, Context* context) override {}
|
||||||
virtual void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) override;
|
virtual void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) override;
|
||||||
virtual void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) override;
|
virtual void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) override;
|
||||||
virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) override;
|
virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) override;
|
||||||
|
|
@ -3385,7 +3386,7 @@ void ContextRef::throwException(ValueRef* exceptionValue)
|
||||||
toImpl(this)->throwException(s, toImpl(exceptionValue));
|
toImpl(this)->throwException(s, toImpl(exceptionValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ContextRef::initDebugger(DebuggerOperationsRef::DebuggerClient* debuggerClient)
|
bool ContextRef::initDebuggerClient(DebuggerOperationsRef::DebuggerClient* debuggerClient)
|
||||||
{
|
{
|
||||||
#ifdef ESCARGOT_DEBUGGER
|
#ifdef ESCARGOT_DEBUGGER
|
||||||
Context* context = toImpl(this);
|
Context* context = toImpl(this);
|
||||||
|
|
@ -3414,10 +3415,10 @@ bool ContextRef::disableDebugger()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ContextRef::initDebuggerRemote(const char* options)
|
bool ContextRef::initDebugger(const char* options)
|
||||||
{
|
{
|
||||||
#ifdef ESCARGOT_DEBUGGER
|
#ifdef ESCARGOT_DEBUGGER
|
||||||
return toImpl(this)->initDebuggerRemote(options);
|
return toImpl(this)->initDebugger(options);
|
||||||
#else /* !ESCARGOT_DEBUGGER */
|
#else /* !ESCARGOT_DEBUGGER */
|
||||||
return false;
|
return false;
|
||||||
#endif /* ESCARGOT_DEBUGGER */
|
#endif /* ESCARGOT_DEBUGGER */
|
||||||
|
|
|
||||||
|
|
@ -950,11 +950,11 @@ public:
|
||||||
bool canThrowException();
|
bool canThrowException();
|
||||||
void throwException(ValueRef* exceptionValue);
|
void throwException(ValueRef* exceptionValue);
|
||||||
|
|
||||||
bool initDebugger(DebuggerOperationsRef::DebuggerClient* debuggerClient);
|
bool initDebuggerClient(DebuggerOperationsRef::DebuggerClient* debuggerClient);
|
||||||
bool disableDebugger();
|
bool disableDebugger();
|
||||||
// available options(separator is ';')
|
// available options(separator is ';')
|
||||||
// "--port=6501", default for TCP debugger
|
// "--port=6501", default for TCP debugger
|
||||||
bool initDebuggerRemote(const char* options);
|
bool initDebugger(const char* options);
|
||||||
bool isDebuggerRunning();
|
bool isDebuggerRunning();
|
||||||
bool isWaitBeforeExit();
|
bool isWaitBeforeExit();
|
||||||
void printDebugger(StringRef* output);
|
void printDebugger(StringRef* output);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -28,14 +28,11 @@ namespace Escargot {
|
||||||
#define ESCARGOT_DEBUGGER_MAX_STACK_TRACE_LENGTH 8
|
#define ESCARGOT_DEBUGGER_MAX_STACK_TRACE_LENGTH 8
|
||||||
|
|
||||||
/* WebSocket max length encoded in one byte. */
|
/* WebSocket max length encoded in one byte. */
|
||||||
#define ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH 125
|
|
||||||
#define ESCARGOT_DEBUGGER_VERSION 1
|
|
||||||
#define ESCARGOT_DEBUGGER_MESSAGE_PROCESS_DELAY 10
|
#define ESCARGOT_DEBUGGER_MESSAGE_PROCESS_DELAY 10
|
||||||
#define ESCARGOT_DEBUGGER_IN_WAIT_MODE (nullptr)
|
#define ESCARGOT_DEBUGGER_IN_WAIT_MODE (nullptr)
|
||||||
#define ESCARGOT_DEBUGGER_IN_EVAL_MODE (reinterpret_cast<ExecutionState*>(0x1))
|
#define ESCARGOT_DEBUGGER_IN_EVAL_MODE (reinterpret_cast<ExecutionState*>(0x1))
|
||||||
#define ESCARGOT_DEBUGGER_ALWAYS_STOP (reinterpret_cast<ExecutionState*>(0x2))
|
#define ESCARGOT_DEBUGGER_ALWAYS_STOP (reinterpret_cast<ExecutionState*>(0x2))
|
||||||
#define ESCARGOT_DEBUGGER_NO_STACK_TRACE_RESTORE (reinterpret_cast<ExecutionState*>(0x1))
|
#define ESCARGOT_DEBUGGER_NO_STACK_TRACE_RESTORE (reinterpret_cast<ExecutionState*>(0x1))
|
||||||
#define ESCARGOT_DEBUGGER_MAX_VARIABLE_LENGTH 128
|
|
||||||
|
|
||||||
class Context;
|
class Context;
|
||||||
class Object;
|
class Object;
|
||||||
|
|
@ -158,8 +155,7 @@ public:
|
||||||
m_stopState = stopState;
|
m_stopState = stopState;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void createDebuggerRemote(const char* options, Context* context);
|
virtual void init(const char* options, Context* context) = 0;
|
||||||
|
|
||||||
virtual void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) = 0;
|
virtual void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) = 0;
|
||||||
virtual void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) = 0;
|
virtual void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) = 0;
|
||||||
virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) = 0;
|
virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) = 0;
|
||||||
|
|
@ -176,6 +172,11 @@ public:
|
||||||
static SavedStackTraceDataVector* saveStackTrace(ExecutionState& state);
|
static SavedStackTraceDataVector* saveStackTrace(ExecutionState& state);
|
||||||
|
|
||||||
void pumpDebuggerEvents(ExecutionState* state);
|
void pumpDebuggerEvents(ExecutionState* state);
|
||||||
|
static void createDebugger(const char* options, Context* context);
|
||||||
|
|
||||||
|
void enable(Context* context);
|
||||||
|
|
||||||
|
Vector<Object*, GCUtil::gc_malloc_allocator<Object*>> m_activeObjects;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Debugger()
|
Debugger()
|
||||||
|
|
@ -193,7 +194,6 @@ protected:
|
||||||
return m_context != nullptr;
|
return m_context != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void enable(Context* context);
|
|
||||||
void disable();
|
void disable();
|
||||||
|
|
||||||
virtual bool processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest = true) = 0;
|
virtual bool processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest = true) = 0;
|
||||||
|
|
@ -201,6 +201,7 @@ protected:
|
||||||
uint32_t m_delay;
|
uint32_t m_delay;
|
||||||
ExecutionState* m_stopState;
|
ExecutionState* m_stopState;
|
||||||
std::vector<BreakpointLocationsInfo*> m_breakpointLocationsVector;
|
std::vector<BreakpointLocationsInfo*> m_breakpointLocationsVector;
|
||||||
|
Vector<uintptr_t, GCUtil::gc_malloc_atomic_allocator<uintptr_t>> m_releasedFunctions;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Context* m_context;
|
Context* m_context;
|
||||||
|
|
@ -212,246 +213,6 @@ private:
|
||||||
bool m_inDebuggingCodeMode;
|
bool m_inDebuggingCodeMode;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DebuggerRemote : public Debugger {
|
|
||||||
public:
|
|
||||||
// Messages sent by Escargot to the debugger client
|
|
||||||
enum {
|
|
||||||
ESCARGOT_MESSAGE_VERSION = 0,
|
|
||||||
ESCARGOT_MESSAGE_CONFIGURATION = 1,
|
|
||||||
ESCARGOT_MESSAGE_CLOSE_CONNECTION = 2,
|
|
||||||
ESCARGOT_MESSAGE_RELEASE_FUNCTION = 3,
|
|
||||||
ESCARGOT_MESSAGE_PARSE_DONE = 4,
|
|
||||||
ESCARGOT_MESSAGE_PARSE_ERROR = 5,
|
|
||||||
// These four must be in the same order.
|
|
||||||
ESCARGOT_MESSAGE_SOURCE_8BIT = 6,
|
|
||||||
ESCARGOT_MESSAGE_SOURCE_8BIT_END = 7,
|
|
||||||
ESCARGOT_MESSAGE_SOURCE_16BIT = 8,
|
|
||||||
ESCARGOT_MESSAGE_SOURCE_16BIT_END = 9,
|
|
||||||
// These four must be in the same order.
|
|
||||||
ESCARGOT_MESSAGE_FILE_NAME_8BIT = 10,
|
|
||||||
ESCARGOT_MESSAGE_FILE_NAME_8BIT_END = 11,
|
|
||||||
ESCARGOT_MESSAGE_FILE_NAME_16BIT = 12,
|
|
||||||
ESCARGOT_MESSAGE_FILE_NAME_16BIT_END = 13,
|
|
||||||
// These four must be in the same order.
|
|
||||||
ESCARGOT_MESSAGE_FUNCTION_NAME_8BIT = 14,
|
|
||||||
ESCARGOT_MESSAGE_FUNCTION_NAME_8BIT_END = 15,
|
|
||||||
ESCARGOT_MESSAGE_FUNCTION_NAME_16BIT = 16,
|
|
||||||
ESCARGOT_MESSAGE_FUNCTION_NAME_16BIT_END = 17,
|
|
||||||
ESCARGOT_MESSAGE_BREAKPOINT_LOCATION = 18,
|
|
||||||
ESCARGOT_MESSAGE_FUNCTION_PTR = 19,
|
|
||||||
ESCARGOT_MESSAGE_BREAKPOINT_HIT = 20,
|
|
||||||
ESCARGOT_MESSAGE_EXCEPTION_HIT = 21,
|
|
||||||
// These four must be in the same order.
|
|
||||||
ESCARGOT_MESSAGE_EVAL_RESULT_8BIT = 22,
|
|
||||||
ESCARGOT_MESSAGE_EVAL_RESULT_8BIT_END = 23,
|
|
||||||
ESCARGOT_MESSAGE_EVAL_RESULT_16BIT = 24,
|
|
||||||
ESCARGOT_MESSAGE_EVAL_RESULT_16BIT_END = 25,
|
|
||||||
// These four must be in the same order.
|
|
||||||
ESCARGOT_MESSAGE_EVAL_FAILED_8BIT = 26,
|
|
||||||
ESCARGOT_MESSAGE_EVAL_FAILED_8BIT_END = 27,
|
|
||||||
ESCARGOT_MESSAGE_EVAL_FAILED_16BIT = 28,
|
|
||||||
ESCARGOT_MESSAGE_EVAL_FAILED_16BIT_END = 29,
|
|
||||||
// These four must be in the same order.
|
|
||||||
ESCARGOT_MESSAGE_WATCH_RESULT_8BIT = 30,
|
|
||||||
ESCARGOT_MESSAGE_WATCH_RESULT_8BIT_END = 31,
|
|
||||||
ESCARGOT_MESSAGE_WATCH_RESULT_16BIT = 32,
|
|
||||||
ESCARGOT_MESSAGE_WATCH_RESULT_16BIT_END = 33,
|
|
||||||
ESCARGOT_MESSAGE_BACKTRACE_TOTAL = 34,
|
|
||||||
ESCARGOT_MESSAGE_BACKTRACE = 35,
|
|
||||||
ESCARGOT_MESSAGE_BACKTRACE_END = 36,
|
|
||||||
ESCARGOT_MESSAGE_SCOPE_CHAIN = 37,
|
|
||||||
ESCARGOT_MESSAGE_SCOPE_CHAIN_END = 38,
|
|
||||||
// These four must be in the same order.
|
|
||||||
ESCARGOT_MESSAGE_STRING_8BIT = 39,
|
|
||||||
ESCARGOT_MESSAGE_STRING_8BIT_END = 40,
|
|
||||||
ESCARGOT_MESSAGE_STRING_16BIT = 41,
|
|
||||||
ESCARGOT_MESSAGE_STRING_16BIT_END = 42,
|
|
||||||
ESCARGOT_MESSAGE_VARIABLE = 43,
|
|
||||||
ESCARGOT_MESSAGE_PRINT = 44,
|
|
||||||
ESCARGOT_MESSAGE_EXCEPTION = 45,
|
|
||||||
ESCARGOT_MESSAGE_EXCEPTION_BACKTRACE = 46,
|
|
||||||
ESCARGOT_DEBUGGER_WAIT_FOR_SOURCE = 47,
|
|
||||||
ESCARGOT_DEBUGGER_WAITING_AFTER_PENDING = 48,
|
|
||||||
ESCARGOT_DEBUGGER_WAIT_FOR_WAIT_EXIT = 49
|
|
||||||
};
|
|
||||||
|
|
||||||
// Messages sent by the debugger client to Escargot
|
|
||||||
enum {
|
|
||||||
ESCARGOT_MESSAGE_FUNCTION_RELEASED = 0,
|
|
||||||
ESCARGOT_MESSAGE_UPDATE_BREAKPOINT = 1,
|
|
||||||
ESCARGOT_MESSAGE_CONTINUE = 2,
|
|
||||||
ESCARGOT_MESSAGE_STEP = 3,
|
|
||||||
ESCARGOT_MESSAGE_NEXT = 4,
|
|
||||||
ESCARGOT_MESSAGE_FINISH = 5,
|
|
||||||
// These four must be in the same order.
|
|
||||||
ESCARGOT_MESSAGE_EVAL_8BIT_START = 6,
|
|
||||||
ESCARGOT_MESSAGE_EVAL_8BIT = 7,
|
|
||||||
ESCARGOT_MESSAGE_EVAL_16BIT_START = 8,
|
|
||||||
ESCARGOT_MESSAGE_EVAL_16BIT = 9,
|
|
||||||
// These four must be in the same order.
|
|
||||||
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_8BIT_START = 10,
|
|
||||||
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_8BIT = 11,
|
|
||||||
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_16BIT_START = 12,
|
|
||||||
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_16BIT = 13,
|
|
||||||
// These four must be in the same order.
|
|
||||||
ESCARGOT_MESSAGE_WATCH_8BIT_START = 14,
|
|
||||||
ESCARGOT_MESSAGE_WATCH_8BIT = 15,
|
|
||||||
ESCARGOT_MESSAGE_WATCH_16BIT_START = 16,
|
|
||||||
ESCARGOT_MESSAGE_WATCH_16BIT = 17,
|
|
||||||
ESCARGOT_MESSAGE_GET_BACKTRACE = 18,
|
|
||||||
ESCARGOT_MESSAGE_GET_SCOPE_CHAIN = 19,
|
|
||||||
ESCARGOT_MESSAGE_GET_SCOPE_VARIABLES = 20,
|
|
||||||
ESCARGOT_MESSAGE_GET_OBJECT = 21,
|
|
||||||
// These four must be in the same order.
|
|
||||||
ESCARGOT_DEBUGGER_CLIENT_SOURCE_8BIT_START = 22,
|
|
||||||
ESCARGOT_DEBUGGER_CLIENT_SOURCE_8BIT = 23,
|
|
||||||
ESCARGOT_DEBUGGER_CLIENT_SOURCE_16BIT_START = 24,
|
|
||||||
ESCARGOT_DEBUGGER_CLIENT_SOURCE_16BIT = 25,
|
|
||||||
ESCARGOT_DEBUGGER_THERE_WAS_NO_SOURCE = 26,
|
|
||||||
ESCARGOT_DEBUGGER_PENDING_CONFIG = 27,
|
|
||||||
ESCARGOT_DEBUGGER_PENDING_RESUME = 28,
|
|
||||||
ESCARGOT_DEBUGGER_WAIT_BEFORE_EXIT = 29,
|
|
||||||
ESCARGOT_DEBUGGER_STOP = 30
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Environment record types
|
|
||||||
enum {
|
|
||||||
ESCARGOT_RECORD_GLOBAL_ENVIRONMENT = 0,
|
|
||||||
ESCARGOT_RECORD_FUNCTION_ENVIRONMENT = 1,
|
|
||||||
ESCARGOT_RECORD_DECLARATIVE_ENVIRONMENT = 2,
|
|
||||||
ESCARGOT_RECORD_OBJECT_ENVIRONMENT = 3,
|
|
||||||
ESCARGOT_RECORD_MODULE_ENVIRONMENT = 4,
|
|
||||||
ESCARGOT_RECORD_UNKNOWN_ENVIRONMENT = 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Variable types
|
|
||||||
enum {
|
|
||||||
ESCARGOT_VARIABLE_END = 0,
|
|
||||||
ESCARGOT_VARIABLE_UNACCESSIBLE = 1,
|
|
||||||
ESCARGOT_VARIABLE_UNDEFINED = 2,
|
|
||||||
ESCARGOT_VARIABLE_NULL = 3,
|
|
||||||
ESCARGOT_VARIABLE_TRUE = 4,
|
|
||||||
ESCARGOT_VARIABLE_FALSE = 5,
|
|
||||||
ESCARGOT_VARIABLE_NUMBER = 6,
|
|
||||||
ESCARGOT_VARIABLE_STRING = 7,
|
|
||||||
ESCARGOT_VARIABLE_SYMBOL = 8,
|
|
||||||
ESCARGOT_VARIABLE_BIGINT = 9,
|
|
||||||
// Only object types should be defined after this point.
|
|
||||||
ESCARGOT_VARIABLE_OBJECT = 10,
|
|
||||||
ESCARGOT_VARIABLE_ARRAY = 11,
|
|
||||||
ESCARGOT_VARIABLE_FUNCTION = 12,
|
|
||||||
ESCARGOT_VARIABLE_TYPE_MASK = 0x3f,
|
|
||||||
ESCARGOT_VARIABLE_LONG_NAME = 0x40,
|
|
||||||
ESCARGOT_VARIABLE_LONG_VALUE = 0x80,
|
|
||||||
};
|
|
||||||
|
|
||||||
inline bool pendingWait(void)
|
|
||||||
{
|
|
||||||
return m_pendingWait;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool connected(void)
|
|
||||||
{
|
|
||||||
return enabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendType(uint8_t type);
|
|
||||||
void sendSubtype(uint8_t type, uint8_t subType);
|
|
||||||
void sendString(uint8_t type, const String* string);
|
|
||||||
void sendPointer(uint8_t type, const void* ptr);
|
|
||||||
|
|
||||||
virtual void init(const char* options, Context* context) = 0;
|
|
||||||
virtual void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) override;
|
|
||||||
virtual void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) override;
|
|
||||||
virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) override;
|
|
||||||
virtual void exceptionCaught(String* message, SavedStackTraceDataVector& exceptionTrace) override;
|
|
||||||
virtual void consoleOut(String* output) override;
|
|
||||||
virtual String* getClientSource(String** sourceName) override;
|
|
||||||
virtual bool getWaitBeforeExitClient() override;
|
|
||||||
|
|
||||||
void sendBacktraceInfo(uint8_t type, ByteCodeBlock* byteCodeBlock, uint32_t line, uint32_t column, uint32_t executionStateDepth);
|
|
||||||
void sendVariableObjectInfo(uint8_t subType, Object* object);
|
|
||||||
void waitForResolvingPendingBreakpoints();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
enum CloseReason {
|
|
||||||
CloseEndConnection,
|
|
||||||
CloseAbortConnection,
|
|
||||||
CloseProtocolUnsupported,
|
|
||||||
CloseProtocolError,
|
|
||||||
};
|
|
||||||
|
|
||||||
DebuggerRemote()
|
|
||||||
: m_exitClient(false)
|
|
||||||
, m_pendingWait(false)
|
|
||||||
, m_waitForResume(false)
|
|
||||||
, m_watchEval(false)
|
|
||||||
, m_clientSourceData(nullptr)
|
|
||||||
, m_clientSourceName(nullptr)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest = true) override;
|
|
||||||
|
|
||||||
virtual bool send(uint8_t type, const void* buffer, size_t length) = 0;
|
|
||||||
virtual bool receive(uint8_t* buffer, size_t& length) = 0;
|
|
||||||
virtual bool isThereAnyEvent() = 0;
|
|
||||||
virtual void close(CloseReason reason) = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Packed structure definitions to reduce network traffic
|
|
||||||
|
|
||||||
struct MessageVersion {
|
|
||||||
uint8_t littleEndian;
|
|
||||||
uint8_t version[sizeof(uint32_t)];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MessageConfiguration {
|
|
||||||
uint8_t maxMessageSize;
|
|
||||||
uint8_t pointerSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FunctionInfo {
|
|
||||||
uint8_t byteCodeStart[sizeof(void*)];
|
|
||||||
uint8_t startLine[sizeof(uint32_t)];
|
|
||||||
uint8_t startColumn[sizeof(uint32_t)];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BreakpointOffset {
|
|
||||||
uint8_t byteCodeStart[sizeof(void*)];
|
|
||||||
uint8_t offset[sizeof(uint32_t)];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BacktraceInfo {
|
|
||||||
uint8_t byteCode[sizeof(void*)];
|
|
||||||
uint8_t line[sizeof(uint32_t)];
|
|
||||||
uint8_t column[sizeof(uint32_t)];
|
|
||||||
uint8_t executionStateDepth[sizeof(uint32_t)];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VariableObjectInfo {
|
|
||||||
uint8_t subType;
|
|
||||||
uint8_t index[sizeof(uint32_t)];
|
|
||||||
};
|
|
||||||
|
|
||||||
uint32_t appendToActiveObjects(Object* object);
|
|
||||||
bool doEval(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, uint8_t* buffer, size_t length);
|
|
||||||
void getBacktrace(ExecutionState* state, uint32_t minDepth, uint32_t maxDepth, bool getTotal);
|
|
||||||
void getScopeChain(ExecutionState* state, uint32_t stateIndex);
|
|
||||||
void getScopeVariables(ExecutionState* state, uint32_t stateIndex, uint32_t index);
|
|
||||||
|
|
||||||
bool m_exitClient : 1;
|
|
||||||
bool m_pendingWait : 1;
|
|
||||||
bool m_waitForResume : 1;
|
|
||||||
bool m_watchEval : 1;
|
|
||||||
String* m_clientSourceData;
|
|
||||||
String* m_clientSourceName;
|
|
||||||
|
|
||||||
Vector<uintptr_t, GCUtil::gc_malloc_atomic_allocator<uintptr_t>> m_releasedFunctions;
|
|
||||||
Vector<Object*, GCUtil::gc_malloc_allocator<Object*>> m_activeObjects;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Escargot
|
} // namespace Escargot
|
||||||
#endif /* ESCARGOT_DEBUGGER */
|
#endif /* ESCARGOT_DEBUGGER */
|
||||||
|
|
||||||
|
|
|
||||||
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 "Escargot.h"
|
||||||
#include "DebuggerTcp.h"
|
#include "DebuggerTcp.h"
|
||||||
|
#include "DebuggerDevtools.h"
|
||||||
|
#include "DebuggerEscargot.h"
|
||||||
|
#include "DebuggerHttpRouter.h"
|
||||||
#include "runtime/String.h" // for split function
|
#include "runtime/String.h" // for split function
|
||||||
|
|
||||||
#ifdef ESCARGOT_DEBUGGER
|
#ifdef ESCARGOT_DEBUGGER
|
||||||
|
|
@ -110,7 +113,7 @@ static inline void tcpCloseSocket(EscargotSocket socket)
|
||||||
#endif /* WIN32 */
|
#endif /* WIN32 */
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messageLength)
|
bool DebuggerTcp::tcpSend(EscargotSocket socket, const uint8_t* message, size_t messageLength)
|
||||||
{
|
{
|
||||||
do {
|
do {
|
||||||
#ifdef OS_POSIX
|
#ifdef OS_POSIX
|
||||||
|
|
@ -122,7 +125,7 @@ static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messag
|
||||||
}
|
}
|
||||||
#endif /* OS_POSIX */
|
#endif /* OS_POSIX */
|
||||||
|
|
||||||
ssize_t sentBytes = send(socket, message, messageLength, 0);
|
ssize_t sentBytes = Escargot::send(socket, message, messageLength, 0);
|
||||||
|
|
||||||
if (sentBytes < 0) {
|
if (sentBytes < 0) {
|
||||||
int errorNumber = tcpGetErrno();
|
int errorNumber = tcpGetErrno();
|
||||||
|
|
@ -142,7 +145,7 @@ static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messag
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool tcpReceive(EscargotSocket socket, uint8_t* message, size_t maxLength, size_t* receivedLength)
|
bool DebuggerTcp::tcpReceive(EscargotSocket socket, uint8_t* message, size_t maxLength, size_t* receivedLength)
|
||||||
{
|
{
|
||||||
*receivedLength = 0;
|
*receivedLength = 0;
|
||||||
|
|
||||||
|
|
@ -162,152 +165,169 @@ static bool tcpReceive(EscargotSocket socket, uint8_t* message, size_t maxLength
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t toBase64Character(uint8_t value)
|
bool DebuggerTcp::send(const uint8_t type, const void* buffer, const size_t length)
|
||||||
{
|
{
|
||||||
if (value < 26) {
|
ASSERT(enabled());
|
||||||
return (uint8_t)(value + 'A');
|
ASSERT(m_websocketMessageType == ESCARGOT_DEBUGGER_WEBSOCKET_TEXT_FRAME
|
||||||
}
|
|| (m_websocketMessageType == ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME && length <= ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH));
|
||||||
|
|
||||||
if (value < 52) {
|
if (length > ESCARGOT_WS_MAX_MESSAGE_LENGTH) {
|
||||||
return (uint8_t)(value - 26 + 'a');
|
ESCARGOT_LOG_ERROR("Cannot send WebSocket payload: 64-bit payload length is not supported.\n");
|
||||||
}
|
close(CloseAbortConnection);
|
||||||
|
|
||||||
if (value < 62) {
|
|
||||||
return (uint8_t)(value - 52 + '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value == 62) {
|
|
||||||
return (uint8_t)'+';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (uint8_t)'/';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode a byte sequence into Base64 string.
|
|
||||||
*/
|
|
||||||
static void toBase64(const uint8_t* source, uint8_t* destination, size_t length)
|
|
||||||
{
|
|
||||||
while (length >= 3) {
|
|
||||||
uint8_t value = (source[0] >> 2);
|
|
||||||
destination[0] = toBase64Character(value);
|
|
||||||
|
|
||||||
value = (uint8_t)(((source[0] << 4) | (source[1] >> 4)) & 0x3f);
|
|
||||||
destination[1] = toBase64Character(value);
|
|
||||||
|
|
||||||
value = (uint8_t)(((source[1] << 2) | (source[2] >> 6)) & 0x3f);
|
|
||||||
destination[2] = toBase64Character(value);
|
|
||||||
|
|
||||||
value = (uint8_t)(source[2] & 0x3f);
|
|
||||||
destination[3] = toBase64Character(value);
|
|
||||||
|
|
||||||
source += 3;
|
|
||||||
destination += 4;
|
|
||||||
length -= 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool webSocketHandshake(EscargotSocket socket)
|
|
||||||
{
|
|
||||||
uint8_t buffer[1024];
|
|
||||||
size_t remainingLength = sizeof(buffer);
|
|
||||||
uint8_t* message = buffer;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
size_t receivedLength;
|
|
||||||
if (!tcpReceive(socket, message, remainingLength, &receivedLength)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
message += receivedLength;
|
|
||||||
remainingLength -= receivedLength;
|
|
||||||
|
|
||||||
if (message > (buffer + 4) && memcmp(message - 4, "\r\n\r\n", 4) == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remainingLength == 0) {
|
|
||||||
ESCARGOT_LOG_ERROR("WebSocket Error: Request too long\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check protocol. */
|
|
||||||
const char expectedProtocol[] = "GET /escargot-debugger";
|
|
||||||
const size_t expectedProtocolLength = sizeof(expectedProtocol) - 1;
|
|
||||||
|
|
||||||
if ((size_t)(message - buffer) < expectedProtocolLength
|
|
||||||
|| memcmp(buffer, expectedProtocol, expectedProtocolLength) != 0) {
|
|
||||||
ESCARGOT_LOG_ERROR("WebSocket Error: Invalid handshake format.\n");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t* websocketKey = buffer + expectedProtocolLength;
|
size_t headerLength = 0;
|
||||||
|
uint8_t message[ESCARGOT_WS_HEADER_BASE_SIZE + ESCARGOT_WS_EXT_LEN16_SIZE + length];
|
||||||
|
message[0] = ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT | m_websocketMessageType;
|
||||||
|
|
||||||
const char expectedWebsocketKey[] = "Sec-WebSocket-Key:";
|
// Server-to-client WebSocket frames are not masked,
|
||||||
const size_t expectedWebsocketKeyLength = sizeof(expectedWebsocketKey) - 1;
|
// therefore the masking key is not included in the header.
|
||||||
|
if (length <= ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH) {
|
||||||
|
const uint8_t type_byte = (m_websocketMessageType == ESCARGOT_DEBUGGER_WEBSOCKET_TEXT_FRAME ? 0 : 1);
|
||||||
|
headerLength = ESCARGOT_WS_HEADER_SIZE - ESCARGOT_WS_MASK_SIZE + type_byte;
|
||||||
|
|
||||||
while (true) {
|
message[1] = static_cast<uint8_t>(length + type_byte);
|
||||||
if ((size_t)(message - websocketKey) < expectedWebsocketKeyLength) {
|
if (type_byte) {
|
||||||
ESCARGOT_LOG_ERROR("Sec-WebSocket-Key not found.\n");
|
message[2] = type;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
headerLength = ESCARGOT_WS_HEADER_LEN16_SIZE - ESCARGOT_WS_MASK_SIZE;
|
||||||
|
|
||||||
if (websocketKey[0] == 'S'
|
message[1] = ESCARGOT_WS_MESSAGE_16BIT_LENGTH_MARKER;
|
||||||
&& websocketKey[-1] == '\n'
|
message[2] = static_cast<uint8_t>((length >> 8) & 0xFF);
|
||||||
&& websocketKey[-2] == '\r'
|
message[3] = static_cast<uint8_t>(length & 0xFF);
|
||||||
&& memcmp(websocketKey, expectedWebsocketKey, expectedWebsocketKeyLength) == 0) {
|
|
||||||
websocketKey += expectedWebsocketKeyLength;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
websocketKey++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* String terminated by double newlines. */
|
memcpy(message + headerLength, buffer, length);
|
||||||
while (*websocketKey == ' ') {
|
|
||||||
websocketKey++;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t* websocketKeyEnd = websocketKey;
|
if (!tcpSend(m_socket, message, headerLength + length)) {
|
||||||
|
ESCARGOT_LOG_ERROR("Failed to send data via WebSocket connection.\n");
|
||||||
while (*websocketKeyEnd > ' ') {
|
close(CloseAbortConnection);
|
||||||
websocketKeyEnd++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Since the buffer is not needed anymore it can
|
|
||||||
* be reused for storing the SHA-1 key and Base64 string. */
|
|
||||||
const size_t sha1Length = 20;
|
|
||||||
|
|
||||||
DebuggerTcp::computeSha1(websocketKey,
|
|
||||||
(size_t)(websocketKeyEnd - websocketKey),
|
|
||||||
(const uint8_t*)"258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
|
|
||||||
36,
|
|
||||||
buffer);
|
|
||||||
|
|
||||||
/* The SHA-1 key is 20 bytes long but toBase64 expects a length
|
|
||||||
* divisible by 3 so an extra 0 is appended at the end. */
|
|
||||||
buffer[sha1Length] = 0;
|
|
||||||
|
|
||||||
toBase64(buffer, buffer + sha1Length + 1, sha1Length + 1);
|
|
||||||
|
|
||||||
/* Last value must be replaced by equal sign. */
|
|
||||||
const uint8_t responsePrefix[] = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ";
|
|
||||||
|
|
||||||
if (!tcpSend(socket, responsePrefix, sizeof(responsePrefix) - 1)
|
|
||||||
|| !tcpSend(socket, buffer + sha1Length + 1, 27)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t responseSuffix[] = "=\r\n\r\n";
|
// ESCARGOT_LOG_INFO("Sent message: %s\n", static_cast<const char*>(buffer));
|
||||||
return tcpSend(socket, responseSuffix, sizeof(responseSuffix) - 1);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebuggerTcp::init(const char* options, Context* context)
|
bool DebuggerTcp::receive(uint8_t* buffer, size_t& length)
|
||||||
|
{
|
||||||
|
size_t receivedLength = 0;
|
||||||
|
|
||||||
|
if (m_payloadLength == 0 || m_receiveBufferFill < m_headerLength + m_payloadLength) {
|
||||||
|
/* Cannot extract a whole message from the buffer. */
|
||||||
|
if (!tcpReceive(m_socket,
|
||||||
|
m_receiveBuffer + m_receiveBufferFill,
|
||||||
|
m_bufferSize - m_receiveBufferFill,
|
||||||
|
&receivedLength)) {
|
||||||
|
ESCARGOT_LOG_ERROR("Failed to receive data from WebSocket connection.\n");
|
||||||
|
close(CloseAbortConnection);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receivedLength == 0 && m_receiveBufferFill < m_headerLength) {
|
||||||
|
// ESCARGOT_LOG_INFO("Incomplete WebSocket frame header, waiting for more data.\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_receiveBufferFill = static_cast<uint32_t>(m_receiveBufferFill + receivedLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_payloadLength == 0) {
|
||||||
|
if (m_receiveBufferFill < ESCARGOT_WS_HEADER_BASE_SIZE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((m_receiveBuffer[0] & ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) != m_websocketMessageType) {
|
||||||
|
if ((m_receiveBuffer[0] & ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) == ESCARGOT_DEBUGGER_WEBSOCKET_CLOSE_FRAME) {
|
||||||
|
close(CloseEndConnection);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESCARGOT_LOG_ERROR("Unsupported Websocket opcode.\n");
|
||||||
|
close(CloseProtocolUnsupported);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((m_receiveBuffer[0] & ~ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) != ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT
|
||||||
|
|| !(m_receiveBuffer[1] & ESCARGOT_DEBUGGER_WEBSOCKET_MASK_BIT)) {
|
||||||
|
ESCARGOT_LOG_ERROR("Unsupported Websocket message.\n");
|
||||||
|
close(CloseProtocolUnsupported);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t payloadLengthField = m_receiveBuffer[1] & ESCARGOT_DEBUGGER_WEBSOCKET_LENGTH_MASK;
|
||||||
|
|
||||||
|
if (payloadLengthField > ESCARGOT_WS_MESSAGE_16BIT_LENGTH_MARKER) {
|
||||||
|
ESCARGOT_LOG_ERROR("64-bit WebSocket payload length is not supported.\n");
|
||||||
|
close(CloseProtocolUnsupported);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payloadLengthField <= ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH) {
|
||||||
|
m_payloadLength = payloadLengthField;
|
||||||
|
m_headerLength = ESCARGOT_WS_HEADER_SIZE;
|
||||||
|
} else {
|
||||||
|
ASSERT(payloadLengthField == ESCARGOT_WS_MESSAGE_16BIT_LENGTH_MARKER);
|
||||||
|
// Ensure that the extended payload length field is available
|
||||||
|
if (m_receiveBufferFill < ESCARGOT_WS_HEADER_LEN16_SIZE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_headerLength = ESCARGOT_WS_HEADER_LEN16_SIZE;
|
||||||
|
m_payloadLength = (static_cast<uint16_t>(m_receiveBuffer[2]) << 8) | static_cast<uint16_t>(m_receiveBuffer[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_payloadLength == 0) {
|
||||||
|
ESCARGOT_LOG_ERROR("Invalid WebSocket payload length: zero-length messages are not supported.\n");
|
||||||
|
close(CloseProtocolUnsupported);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t totalSize = m_headerLength + m_payloadLength;
|
||||||
|
|
||||||
|
if (m_receiveBufferFill < totalSize) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* mask = m_receiveBuffer + m_headerLength - ESCARGOT_WS_MASK_SIZE;
|
||||||
|
const uint8_t* mask_end = mask + ESCARGOT_WS_MASK_SIZE;
|
||||||
|
const uint8_t* source = mask_end;
|
||||||
|
uint8_t* buffer_end = buffer + m_payloadLength;
|
||||||
|
|
||||||
|
while (buffer < buffer_end) {
|
||||||
|
*buffer++ = *source++ ^ *mask++;
|
||||||
|
|
||||||
|
if (mask >= mask_end) {
|
||||||
|
mask -= 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*buffer_end = 0;
|
||||||
|
|
||||||
|
length = m_payloadLength;
|
||||||
|
m_headerLength = ESCARGOT_WS_HEADER_SIZE;
|
||||||
|
m_payloadLength = 0;
|
||||||
|
|
||||||
|
if (m_receiveBufferFill == totalSize) {
|
||||||
|
m_receiveBufferFill = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_receiveBufferFill = static_cast<uint32_t>(m_receiveBufferFill - totalSize);
|
||||||
|
memmove(m_receiveBuffer, m_receiveBuffer + totalSize, m_receiveBufferFill);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debugger* DebuggerTcp::createDebugger(const char* options, Context* context)
|
||||||
{
|
{
|
||||||
uint16_t port = 6501;
|
uint16_t port = 6501;
|
||||||
int timeout = -1;
|
int timeout = -1;
|
||||||
|
|
||||||
|
String* skipSourceName = nullptr;
|
||||||
|
EscargotSocket clientSocket = 0;
|
||||||
|
|
||||||
if (options) {
|
if (options) {
|
||||||
auto v = split(options, ';');
|
auto v = split(options, ';');
|
||||||
const char portOption[] = "--port=";
|
const char portOption[] = "--port=";
|
||||||
|
|
@ -325,31 +345,29 @@ void DebuggerTcp::init(const char* options, Context* context)
|
||||||
} else if (s.find(skipOption) == 0) {
|
} else if (s.find(skipOption) == 0) {
|
||||||
const char* skipStr = const_cast<const char*>(s.data() + sizeof(skipOption) - 1);
|
const char* skipStr = const_cast<const char*>(s.data() + sizeof(skipOption) - 1);
|
||||||
size_t skipLen = strlen(skipStr);
|
size_t skipLen = strlen(skipStr);
|
||||||
m_skipSourceName = String::fromASCII(skipStr, skipLen);
|
skipSourceName = String::fromASCII(skipStr, skipLen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT(enabled() == false);
|
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
WSADATA wsaData;
|
WSADATA wsaData;
|
||||||
int wsa_init_status = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
int wsa_init_status = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||||
if (wsa_init_status != NO_ERROR) {
|
if (wsa_init_status != NO_ERROR) {
|
||||||
return;
|
return nullptr;
|
||||||
}
|
}
|
||||||
#endif /* WIN32*/
|
#endif /* WIN32*/
|
||||||
|
|
||||||
EscargotSocket serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
EscargotSocket serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
if (m_socket == ESCARGOT_INVALID_SOCKET) {
|
if (serverSocket == ESCARGOT_INVALID_SOCKET) {
|
||||||
return;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tcpConfigureSocket(serverSocket, port)) {
|
if (!tcpConfigureSocket(serverSocket, port)) {
|
||||||
int error = tcpGetErrno();
|
int error = tcpGetErrno();
|
||||||
tcpCloseSocket(serverSocket);
|
tcpCloseSocket(serverSocket);
|
||||||
tcpLogError(error);
|
tcpLogError(error);
|
||||||
return;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESCARGOT_LOG_INFO("Waiting for client connection 0.0.0.0:%hd\n", port);
|
ESCARGOT_LOG_INFO("Waiting for client connection 0.0.0.0:%hd\n", port);
|
||||||
|
|
@ -372,58 +390,72 @@ void DebuggerTcp::init(const char* options, Context* context)
|
||||||
if (timeout < 0) {
|
if (timeout < 0) {
|
||||||
ESCARGOT_LOG_ERROR("Waiting for client connection error: timeout reached\n");
|
ESCARGOT_LOG_ERROR("Waiting for client connection error: timeout reached\n");
|
||||||
tcpCloseSocket(serverSocket);
|
tcpCloseSocket(serverSocket);
|
||||||
return;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sockaddr_in addr;
|
sockaddr_in addr;
|
||||||
socklen_t sinSize = sizeof(sockaddr_in);
|
socklen_t sinSize = sizeof(sockaddr_in);
|
||||||
|
DebuggerHttpRouter httpRouter;
|
||||||
|
|
||||||
m_socket = accept(serverSocket, (sockaddr*)&addr, &sinSize);
|
while (true) {
|
||||||
|
clientSocket = accept(serverSocket, (sockaddr*)&addr, &sinSize);
|
||||||
|
|
||||||
|
if (clientSocket == ESCARGOT_INVALID_SOCKET) {
|
||||||
|
tcpLogError(tcpGetErrno());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
u_long nonblockingEnabled = 1;
|
||||||
|
|
||||||
|
/* Set non-blocking mode. */
|
||||||
|
if (ioctlsocket(clientSocket, FIONBIO, &nonblockingEnabled) != NO_ERROR) {
|
||||||
|
tcpCloseSocket(clientSocket);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#else /* !WIN32 */
|
||||||
|
int socketFlags = fcntl(clientSocket, F_GETFL, 0);
|
||||||
|
|
||||||
|
if (socketFlags < 0) {
|
||||||
|
tcpCloseSocket(clientSocket);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set non-blocking mode. */
|
||||||
|
if (fcntl(clientSocket, F_SETFL, socketFlags | O_NONBLOCK) == -1) {
|
||||||
|
tcpCloseSocket(clientSocket);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#endif /* WIN32 */
|
||||||
|
|
||||||
|
ESCARGOT_LOG_INFO("Connected from: %s\n", inet_ntoa(addr.sin_addr));
|
||||||
|
|
||||||
|
if (!httpRouter.handleHttpRequest(clientSocket)) {
|
||||||
|
tcpCloseSocket(clientSocket);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpRouter.webSocketEstablished()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close connection, waiting for the next request
|
||||||
|
tcpCloseSocket(clientSocket);
|
||||||
|
}
|
||||||
|
|
||||||
tcpCloseSocket(serverSocket);
|
tcpCloseSocket(serverSocket);
|
||||||
|
|
||||||
if (m_socket == ESCARGOT_INVALID_SOCKET) {
|
Debugger* client;
|
||||||
tcpLogError(tcpGetErrno());
|
|
||||||
return;
|
if (httpRouter.client() == DebuggerClient::Escargot) {
|
||||||
|
client = new DebuggerEscargot(clientSocket, skipSourceName);
|
||||||
|
} else {
|
||||||
|
client = new DebuggerDevtools(clientSocket, skipSourceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef WIN32
|
client->enable(context);
|
||||||
u_long nonblockingEnabled = 1;
|
return client;
|
||||||
|
|
||||||
/* Set non-blocking mode. */
|
|
||||||
if (ioctlsocket(m_socket, FIONBIO, &nonblockingEnabled) != NO_ERROR) {
|
|
||||||
tcpCloseSocket(m_socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#else /* !WIN32 */
|
|
||||||
int socketFlags = fcntl(m_socket, F_GETFL, 0);
|
|
||||||
|
|
||||||
if (socketFlags < 0) {
|
|
||||||
tcpCloseSocket(m_socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set non-blocking mode. */
|
|
||||||
if (fcntl(m_socket, F_SETFL, socketFlags | O_NONBLOCK) == -1) {
|
|
||||||
tcpCloseSocket(m_socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif /* WIN32 */
|
|
||||||
|
|
||||||
ESCARGOT_LOG_INFO("Connected from: %s\n", inet_ntoa(addr.sin_addr));
|
|
||||||
|
|
||||||
if (!webSocketHandshake(m_socket)) {
|
|
||||||
tcpCloseSocket(m_socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_receiveBufferFill = 0;
|
|
||||||
m_messageLength = 0;
|
|
||||||
|
|
||||||
enable(context);
|
|
||||||
|
|
||||||
return DebuggerRemote::init(nullptr, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DebuggerTcp::skipSourceCode(String* srcName) const
|
bool DebuggerTcp::skipSourceCode(String* srcName) const
|
||||||
|
|
@ -436,34 +468,6 @@ bool DebuggerTcp::skipSourceCode(String* srcName) const
|
||||||
return srcName->contains(m_skipSourceName);
|
return srcName->contains(m_skipSourceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT 0x80
|
|
||||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME 2
|
|
||||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_CLOSE_FRAME 8
|
|
||||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK 0x0f
|
|
||||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_LENGTH_MASK 0x7f
|
|
||||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_ONE_BYTE_LEN_MAX 125
|
|
||||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_MASK_BIT 0x80
|
|
||||||
|
|
||||||
bool DebuggerTcp::send(uint8_t type, const void* buffer, size_t length)
|
|
||||||
{
|
|
||||||
ASSERT(enabled());
|
|
||||||
ASSERT(length < ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH);
|
|
||||||
|
|
||||||
uint8_t message[ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH + 2];
|
|
||||||
|
|
||||||
message[0] = ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT | ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME;
|
|
||||||
message[1] = (uint8_t)(length + 1);
|
|
||||||
message[2] = type;
|
|
||||||
memcpy(message + 3, buffer, length);
|
|
||||||
|
|
||||||
if (tcpSend(m_socket, message, length + 3)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
close(CloseAbortConnection);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DebuggerTcp::isThereAnyEvent()
|
bool DebuggerTcp::isThereAnyEvent()
|
||||||
{
|
{
|
||||||
// if there is remained receive buffer data,
|
// if there is remained receive buffer data,
|
||||||
|
|
@ -484,91 +488,6 @@ bool DebuggerTcp::isThereAnyEvent()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DebuggerTcp::receive(uint8_t* buffer, size_t& length)
|
|
||||||
{
|
|
||||||
size_t receivedLength;
|
|
||||||
|
|
||||||
if (m_messageLength == 0 || m_receiveBufferFill < 2 + sizeof(uint32_t) + m_messageLength) {
|
|
||||||
/* Cannot extract a whole message from the buffer. */
|
|
||||||
if (!tcpReceive(m_socket,
|
|
||||||
m_receiveBuffer + m_receiveBufferFill,
|
|
||||||
ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH + 2 + sizeof(uint32_t) - m_receiveBufferFill,
|
|
||||||
&receivedLength)) {
|
|
||||||
close(CloseAbortConnection);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (receivedLength == 0 && m_receiveBufferFill < (2 + sizeof(uint32_t))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_receiveBufferFill = (uint8_t)(m_receiveBufferFill + receivedLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_messageLength == 0) {
|
|
||||||
if (m_receiveBufferFill < 3) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((m_receiveBuffer[0] & ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) != ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME) {
|
|
||||||
if ((m_receiveBuffer[0] & ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) == ESCARGOT_DEBUGGER_WEBSOCKET_CLOSE_FRAME) {
|
|
||||||
close(CloseEndConnection);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESCARGOT_LOG_ERROR("Unsupported Websocket opcode.\n");
|
|
||||||
close(CloseProtocolUnsupported);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((m_receiveBuffer[0] & ~ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) != ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT
|
|
||||||
|| !(m_receiveBuffer[1] & ESCARGOT_DEBUGGER_WEBSOCKET_MASK_BIT)) {
|
|
||||||
ESCARGOT_LOG_ERROR("Unsupported Websocket message.\n");
|
|
||||||
close(CloseProtocolUnsupported);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_messageLength = (uint8_t)(m_receiveBuffer[1] & ESCARGOT_DEBUGGER_WEBSOCKET_LENGTH_MASK);
|
|
||||||
|
|
||||||
if (m_messageLength == 0 || m_messageLength > ESCARGOT_DEBUGGER_WEBSOCKET_ONE_BYTE_LEN_MAX) {
|
|
||||||
ESCARGOT_LOG_ERROR("Unsupported Websocket message size.\n");
|
|
||||||
close(CloseProtocolUnsupported);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t totalSize = 2 + sizeof(uint32_t) + m_messageLength;
|
|
||||||
|
|
||||||
if (m_receiveBufferFill < totalSize) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t* mask = m_receiveBuffer + 2;
|
|
||||||
uint8_t* mask_end = mask + sizeof(uint32_t);
|
|
||||||
uint8_t* source = mask_end;
|
|
||||||
uint8_t* buffer_end = buffer + m_messageLength;
|
|
||||||
|
|
||||||
while (buffer < buffer_end) {
|
|
||||||
*buffer++ = *source++ ^ *mask++;
|
|
||||||
|
|
||||||
if (mask >= mask_end) {
|
|
||||||
mask -= 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
length = m_messageLength;
|
|
||||||
m_messageLength = 0;
|
|
||||||
|
|
||||||
if (m_receiveBufferFill == totalSize) {
|
|
||||||
m_receiveBufferFill = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_receiveBufferFill = (uint8_t)(m_receiveBufferFill - totalSize);
|
|
||||||
memmove(m_receiveBuffer, m_receiveBuffer + totalSize, m_receiveBufferFill);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DebuggerTcp::close(CloseReason reason)
|
void DebuggerTcp::close(CloseReason reason)
|
||||||
{
|
{
|
||||||
if (!enabled()) {
|
if (!enabled()) {
|
||||||
|
|
|
||||||
|
|
@ -31,39 +31,75 @@ typedef SOCKET EscargotSocket;
|
||||||
#else /* !WIN32 */
|
#else /* !WIN32 */
|
||||||
typedef int EscargotSocket;
|
typedef int EscargotSocket;
|
||||||
#endif /* WIN32 */
|
#endif /* WIN32 */
|
||||||
|
#define ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH 125
|
||||||
|
|
||||||
class DebuggerTcp : public DebuggerRemote {
|
#define ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT 0x80
|
||||||
|
#define ESCARGOT_DEBUGGER_WEBSOCKET_TEXT_FRAME 1
|
||||||
|
#define ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME 2
|
||||||
|
#define ESCARGOT_DEBUGGER_WEBSOCKET_CLOSE_FRAME 8
|
||||||
|
#define ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK 0x0f
|
||||||
|
#define ESCARGOT_DEBUGGER_WEBSOCKET_LENGTH_MASK 0x7f
|
||||||
|
#define ESCARGOT_DEBUGGER_WEBSOCKET_ONE_BYTE_LEN_MAX 125
|
||||||
|
#define ESCARGOT_DEBUGGER_WEBSOCKET_MASK_BIT 0x80
|
||||||
|
|
||||||
|
#define ESCARGOT_WS_HEADER_BASE_SIZE 2
|
||||||
|
#define ESCARGOT_WS_EXT_LEN16_SIZE 2
|
||||||
|
#define ESCARGOT_WS_MASK_SIZE 4
|
||||||
|
|
||||||
|
#define ESCARGOT_WS_MESSAGE_16BIT_LENGTH_MARKER 126
|
||||||
|
#define ESCARGOT_WS_MAX_MESSAGE_LENGTH 65535
|
||||||
|
|
||||||
|
#define ESCARGOT_WS_HEADER_SIZE (ESCARGOT_WS_HEADER_BASE_SIZE + ESCARGOT_WS_MASK_SIZE)
|
||||||
|
#define ESCARGOT_WS_HEADER_LEN16_SIZE (ESCARGOT_WS_HEADER_SIZE + ESCARGOT_WS_EXT_LEN16_SIZE)
|
||||||
|
#define ESCARGOT_WS_BUFFER_SIZE (ESCARGOT_WS_HEADER_LEN16_SIZE + ESCARGOT_WS_MAX_MESSAGE_LENGTH)
|
||||||
|
|
||||||
|
class DebuggerTcp : public Debugger {
|
||||||
public:
|
public:
|
||||||
DebuggerTcp()
|
DebuggerTcp(const EscargotSocket socket, String* skipSource, const uint32_t bufferSize, const uint8_t type)
|
||||||
: m_socket(0)
|
: m_socket(socket)
|
||||||
, m_receiveBuffer{}
|
, m_bufferSize(bufferSize)
|
||||||
|
, m_receiveBuffer(new uint8_t[bufferSize])
|
||||||
|
, m_payloadLength(0)
|
||||||
, m_receiveBufferFill(0)
|
, m_receiveBufferFill(0)
|
||||||
, m_messageLength(0)
|
, m_headerLength(0)
|
||||||
, m_skipSourceName(nullptr)
|
, m_websocketMessageType(type)
|
||||||
|
, m_skipSourceName(skipSource)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void init(const char* options, Context* context) override;
|
static Debugger* createDebugger(const char* options, Context* context);
|
||||||
|
void init(const char* options, Context* context) override {}
|
||||||
|
|
||||||
virtual bool skipSourceCode(String* srcName) const override;
|
bool skipSourceCode(String* srcName) const override;
|
||||||
|
|
||||||
static void computeSha1(const uint8_t* source1, size_t source1Length,
|
static void computeSha1(const uint8_t* source1, size_t source1Length,
|
||||||
const uint8_t* source2, size_t source2Length,
|
const uint8_t* source2, size_t source2Length,
|
||||||
uint8_t destination[20]);
|
uint8_t destination[20]);
|
||||||
|
|
||||||
protected:
|
static bool tcpReceive(EscargotSocket socket, uint8_t* message, size_t maxLength, size_t* receivedLength);
|
||||||
virtual bool send(uint8_t type, const void* buffer, size_t length) override;
|
static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messageLength);
|
||||||
virtual bool receive(uint8_t* buffer, size_t& length) override;
|
|
||||||
virtual bool isThereAnyEvent() override;
|
|
||||||
virtual void close(CloseReason reason) override;
|
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
void receiveData();
|
enum CloseReason {
|
||||||
|
CloseEndConnection,
|
||||||
|
CloseAbortConnection,
|
||||||
|
CloseProtocolUnsupported,
|
||||||
|
CloseProtocolError,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool send(uint8_t type, const void* buffer, size_t length);
|
||||||
|
bool receive(uint8_t* buffer, size_t& length);
|
||||||
|
bool isThereAnyEvent();
|
||||||
|
void close(CloseReason reason);
|
||||||
|
|
||||||
EscargotSocket m_socket;
|
EscargotSocket m_socket;
|
||||||
uint8_t m_receiveBuffer[2 + sizeof(uint32_t) + ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH];
|
uint32_t m_bufferSize;
|
||||||
|
uint8_t* m_receiveBuffer;
|
||||||
|
uint16_t m_payloadLength;
|
||||||
uint8_t m_receiveBufferFill;
|
uint8_t m_receiveBufferFill;
|
||||||
uint8_t m_messageLength;
|
uint8_t m_headerLength;
|
||||||
|
uint8_t m_websocketMessageType;
|
||||||
|
|
||||||
|
|
||||||
// skip generating debugging bytecode for source code whose name contains m_skipSourceName
|
// skip generating debugging bytecode for source code whose name contains m_skipSourceName
|
||||||
String* m_skipSourceName;
|
String* m_skipSourceName;
|
||||||
|
|
|
||||||
|
|
@ -158,14 +158,14 @@ ASTAllocator& Context::astAllocator()
|
||||||
|
|
||||||
#ifdef ESCARGOT_DEBUGGER
|
#ifdef ESCARGOT_DEBUGGER
|
||||||
|
|
||||||
bool Context::initDebuggerRemote(const char* options)
|
bool Context::initDebugger(const char* options)
|
||||||
{
|
{
|
||||||
if (debuggerEnabled()) {
|
if (debuggerEnabled()) {
|
||||||
// debugger cannot be re-initialized
|
// debugger cannot be re-initialized
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debugger::createDebuggerRemote(options, this);
|
Debugger::createDebugger(options, this);
|
||||||
return m_debugger != nullptr;
|
return m_debugger != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -323,7 +323,7 @@ public:
|
||||||
return m_debugger;
|
return m_debugger;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool initDebuggerRemote(const char* options);
|
bool initDebugger(const char* options);
|
||||||
void initDebugger(Debugger* debugger);
|
void initDebugger(Debugger* debugger);
|
||||||
void removeDebugger();
|
void removeDebugger();
|
||||||
bool debuggerEnabled() const;
|
bool debuggerEnabled() const;
|
||||||
|
|
|
||||||
|
|
@ -294,7 +294,8 @@ private:
|
||||||
// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-global-environment-records
|
// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-global-environment-records
|
||||||
class GlobalEnvironmentRecord : public EnvironmentRecord {
|
class GlobalEnvironmentRecord : public EnvironmentRecord {
|
||||||
#ifdef ESCARGOT_DEBUGGER
|
#ifdef ESCARGOT_DEBUGGER
|
||||||
friend class DebuggerRemote;
|
friend class DebuggerEscargot;
|
||||||
|
friend class DebuggerDevtools;
|
||||||
friend class DebuggerAPI;
|
friend class DebuggerAPI;
|
||||||
#endif /* ESCARGOT_DEBUGGER */
|
#endif /* ESCARGOT_DEBUGGER */
|
||||||
public:
|
public:
|
||||||
|
|
@ -587,7 +588,8 @@ public:
|
||||||
// DeclarativeEnvironmentRecordNotIndexed record does not create binding self likes FunctionEnvironmentRecord
|
// DeclarativeEnvironmentRecordNotIndexed record does not create binding self likes FunctionEnvironmentRecord
|
||||||
class DeclarativeEnvironmentRecordNotIndexed : public DeclarativeEnvironmentRecord {
|
class DeclarativeEnvironmentRecordNotIndexed : public DeclarativeEnvironmentRecord {
|
||||||
#ifdef ESCARGOT_DEBUGGER
|
#ifdef ESCARGOT_DEBUGGER
|
||||||
friend class DebuggerRemote;
|
friend class DebuggerEscargot;
|
||||||
|
friend class DebuggerDevtools;
|
||||||
friend class DebuggerAPI;
|
friend class DebuggerAPI;
|
||||||
#endif /* ESCARGOT_DEBUGGER */
|
#endif /* ESCARGOT_DEBUGGER */
|
||||||
public:
|
public:
|
||||||
|
|
@ -1202,7 +1204,8 @@ public:
|
||||||
|
|
||||||
class ModuleEnvironmentRecord : public DeclarativeEnvironmentRecord {
|
class ModuleEnvironmentRecord : public DeclarativeEnvironmentRecord {
|
||||||
#ifdef ESCARGOT_DEBUGGER
|
#ifdef ESCARGOT_DEBUGGER
|
||||||
friend class DebuggerRemote;
|
friend class DebuggerEscargot;
|
||||||
|
friend class DebuggerDevtools;
|
||||||
friend class DebuggerAPI;
|
friend class DebuggerAPI;
|
||||||
#endif /* ESCARGOT_DEBUGGER */
|
#endif /* ESCARGOT_DEBUGGER */
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -1202,7 +1202,7 @@ int main(int argc, char* argv[])
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (strcmp(argv[i], "--start-debug-server") == 0) {
|
if (strcmp(argv[i], "--start-debug-server") == 0) {
|
||||||
context->initDebuggerRemote(nullptr);
|
context->initDebugger(nullptr);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (strcmp(argv[i], "--debugger-wait-source") == 0) {
|
if (strcmp(argv[i], "--debugger-wait-source") == 0) {
|
||||||
|
|
|
||||||
|
|
@ -2752,7 +2752,7 @@ TEST(Debugger, Basic)
|
||||||
DebuggerTest* debuggerTest = new DebuggerTest();
|
DebuggerTest* debuggerTest = new DebuggerTest();
|
||||||
StringRef* fileName = StringRef::createFromUTF8(debuggerFileNameString, sizeof(debuggerFileNameString) - 1);
|
StringRef* fileName = StringRef::createFromUTF8(debuggerFileNameString, sizeof(debuggerFileNameString) - 1);
|
||||||
|
|
||||||
context->initDebugger(debuggerTest);
|
context->initDebuggerClient(debuggerTest);
|
||||||
|
|
||||||
StringRef* source = StringRef::createFromUTF8(debuggerSourceString1, sizeof(debuggerSourceString1) - 1);
|
StringRef* source = StringRef::createFromUTF8(debuggerSourceString1, sizeof(debuggerSourceString1) - 1);
|
||||||
evalScript(context, source, fileName, false);
|
evalScript(context, source, fileName, false);
|
||||||
|
|
@ -2778,7 +2778,7 @@ TEST(Debugger, RemoteOption)
|
||||||
PersistentRefHolder<VMInstanceRef> instance = VMInstanceRef::create();
|
PersistentRefHolder<VMInstanceRef> instance = VMInstanceRef::create();
|
||||||
PersistentRefHolder<ContextRef> context = createEscargotContext(instance.get());
|
PersistentRefHolder<ContextRef> context = createEscargotContext(instance.get());
|
||||||
// 100ms
|
// 100ms
|
||||||
EXPECT_FALSE(context->initDebuggerRemote("--accept-timeout=100"));
|
EXPECT_FALSE(context->initDebugger("--accept-timeout=100"));
|
||||||
EXPECT_FALSE(context->isDebuggerRunning());
|
EXPECT_FALSE(context->isDebuggerRunning());
|
||||||
EXPECT_FALSE(context->isWaitBeforeExit());
|
EXPECT_FALSE(context->isWaitBeforeExit());
|
||||||
|
|
||||||
|
|
@ -2873,7 +2873,7 @@ TEST(Debugger, ObjectStore)
|
||||||
DebuggerTest* debuggerTest = new DebuggerTest();
|
DebuggerTest* debuggerTest = new DebuggerTest();
|
||||||
StringRef* fileName = StringRef::createFromUTF8(debuggerFileNameString, sizeof(debuggerFileNameString) - 1);
|
StringRef* fileName = StringRef::createFromUTF8(debuggerFileNameString, sizeof(debuggerFileNameString) - 1);
|
||||||
|
|
||||||
context->initDebugger(debuggerTest);
|
context->initDebuggerClient(debuggerTest);
|
||||||
|
|
||||||
StringRef* source = StringRef::createFromUTF8(debuggerSourceString1, sizeof(debuggerSourceString1) - 1);
|
StringRef* source = StringRef::createFromUTF8(debuggerSourceString1, sizeof(debuggerSourceString1) - 1);
|
||||||
evalScript(context, source, fileName, false);
|
evalScript(context, source, fileName, false);
|
||||||
|
|
|
||||||
|
|
@ -2001,7 +2001,7 @@ public:
|
||||||
\param stackCapacity Optional initial capacity of stack in bytes.
|
\param stackCapacity Optional initial capacity of stack in bytes.
|
||||||
\param stackAllocator Optional allocator for allocating memory for stack.
|
\param stackAllocator Optional allocator for allocating memory for stack.
|
||||||
*/
|
*/
|
||||||
#if defined(ESCARGOT) && !defined(COMPILER_MSVC)
|
#if defined(ESCARGOT) && (defined(COMPILER_GCC) || defined(COMPILER_CLANG))
|
||||||
__attribute__((__noinline__)) GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0)
|
__attribute__((__noinline__)) GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0)
|
||||||
:
|
:
|
||||||
#else
|
#else
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue