mirror of
https://github.com/jerryscript-project/jerryscript.git
synced 2025-12-15 16:29:21 +00:00
The debugger supports setting breakpoints, execution control (step, next, continue) and getting backtrace. The communication is WebSocket-based, so a browser can communicate with JerryScript without any intermediate application. JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com JerryScript-DCO-1.0-Signed-off-by: Levente Orban orbanl@inf.u-szeged.hu
447 lines
15 KiB
C
447 lines
15 KiB
C
/* Copyright JS Foundation and other contributors, http://js.foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "jerry-api.h"
|
|
|
|
#ifdef JERRY_DEBUGGER
|
|
|
|
#include "byte-code.h"
|
|
#include "jcontext.h"
|
|
#include "jerry-debugger.h"
|
|
#include "jerry-port.h"
|
|
|
|
/**
|
|
* Type cast the debugger send buffer into a specific type.
|
|
*/
|
|
#define JERRY_DEBUGGER_SEND_BUFFER_AS(type, name_p) \
|
|
type *name_p = (type *) (&JERRY_CONTEXT (debugger_send_buffer))
|
|
|
|
/**
|
|
* Type cast the debugger receive buffer into a specific type.
|
|
*/
|
|
#define JERRY_DEBUGGER_RECEIVE_BUFFER_AS(type, name_p) \
|
|
type *name_p = ((type *) recv_buffer_p)
|
|
|
|
/**
|
|
* Free all unreferenced byte code structures which
|
|
* were not acknowledged by the debugger client.
|
|
*/
|
|
void
|
|
jerry_debugger_free_unreferenced_byte_code (void)
|
|
{
|
|
jerry_debugger_byte_code_free_t *byte_code_free_p;
|
|
|
|
byte_code_free_p = JMEM_CP_GET_POINTER (jerry_debugger_byte_code_free_t,
|
|
JERRY_CONTEXT (debugger_byte_code_free_head));
|
|
|
|
while (byte_code_free_p != NULL)
|
|
{
|
|
jerry_debugger_byte_code_free_t *next_byte_code_free_p;
|
|
next_byte_code_free_p = JMEM_CP_GET_POINTER (jerry_debugger_byte_code_free_t,
|
|
byte_code_free_p->next_cp);
|
|
|
|
jmem_heap_free_block (byte_code_free_p,
|
|
((size_t) byte_code_free_p->size) << JMEM_ALIGNMENT_LOG);
|
|
|
|
byte_code_free_p = next_byte_code_free_p;
|
|
}
|
|
} /* jerry_debugger_free_unreferenced_byte_code */
|
|
|
|
/**
|
|
* Send backtrace.
|
|
*/
|
|
static void
|
|
jerry_debugger_send_backtrace (uint8_t *recv_buffer_p) /**< pointer the the received data */
|
|
{
|
|
JERRY_DEBUGGER_RECEIVE_BUFFER_AS (jerry_debugger_receive_get_backtrace_t, get_backtrace_p);
|
|
|
|
uint32_t max_depth;
|
|
memcpy (&max_depth, get_backtrace_p->max_depth, sizeof (uint32_t));
|
|
|
|
if (max_depth == 0)
|
|
{
|
|
max_depth = UINT32_MAX;
|
|
}
|
|
|
|
JERRY_DEBUGGER_SEND_BUFFER_AS (jerry_debugger_send_backtrace_t, backtrace_p);
|
|
|
|
JERRY_DEBUGGER_INIT_SEND_MESSAGE (backtrace_p);
|
|
JERRY_DEBUGGER_SET_SEND_MESSAGE_SIZE_FROM_TYPE (backtrace_p, jerry_debugger_send_backtrace_t);
|
|
backtrace_p->type = JERRY_DEBUGGER_BACKTRACE;
|
|
|
|
vm_frame_ctx_t *frame_ctx_p = JERRY_CONTEXT (vm_top_context_p);
|
|
|
|
uint32_t current_frame = 0;
|
|
|
|
while (frame_ctx_p != NULL && max_depth > 0)
|
|
{
|
|
if (current_frame >= JERRY_DEBUGGER_MAX_SIZE (jerry_debugger_frame_t))
|
|
{
|
|
if (!jerry_debugger_send (sizeof (jerry_debugger_send_backtrace_t)))
|
|
{
|
|
return;
|
|
}
|
|
current_frame = 0;
|
|
}
|
|
|
|
jerry_debugger_frame_t *frame_p = backtrace_p->frames + current_frame;
|
|
|
|
jmem_cpointer_t byte_code_cp;
|
|
JMEM_CP_SET_NON_NULL_POINTER (byte_code_cp, frame_ctx_p->bytecode_header_p);
|
|
memcpy (frame_p->byte_code_cp, &byte_code_cp, sizeof (jmem_cpointer_t));
|
|
|
|
uint32_t offset = (uint32_t) (frame_ctx_p->byte_code_p - (uint8_t *) frame_ctx_p->bytecode_header_p);
|
|
memcpy (frame_p->offset, &offset, sizeof (uint32_t));
|
|
|
|
frame_ctx_p = frame_ctx_p->prev_context_p;
|
|
current_frame++;
|
|
max_depth--;
|
|
}
|
|
|
|
size_t message_size = current_frame * sizeof (jerry_debugger_frame_t);
|
|
|
|
JERRY_DEBUGGER_SET_SEND_MESSAGE_SIZE (backtrace_p, 1 + message_size);
|
|
backtrace_p->type = JERRY_DEBUGGER_BACKTRACE_END;
|
|
|
|
jerry_debugger_send (sizeof (jerry_debugger_send_type_t) + message_size);
|
|
} /* jerry_debugger_send_backtrace */
|
|
|
|
/**
|
|
* Check received packet size.
|
|
*/
|
|
#define JERRY_DEBUGGER_CHECK_PACKET_SIZE(type) \
|
|
if (message_size != sizeof (type)) \
|
|
{ \
|
|
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Invalid message size\n"); \
|
|
jerry_debugger_close_connection (); \
|
|
return false; \
|
|
}
|
|
|
|
/**
|
|
* Receive message from the client.
|
|
*
|
|
* @return true - if message is processed successfully
|
|
* false - otherwise
|
|
*/
|
|
inline bool __attr_always_inline___
|
|
jerry_debugger_process_message (uint8_t *recv_buffer_p, /**< pointer the the received data */
|
|
uint32_t message_size, /**< message size */
|
|
bool *resume_exec_p) /**< pointer to the resume exec flag */
|
|
{
|
|
/* Process the received message. */
|
|
switch (recv_buffer_p[0])
|
|
{
|
|
case JERRY_DEBUGGER_FREE_BYTE_CODE_CP:
|
|
{
|
|
JERRY_DEBUGGER_CHECK_PACKET_SIZE (jerry_debugger_receive_byte_code_cp_t);
|
|
|
|
JERRY_DEBUGGER_RECEIVE_BUFFER_AS (jerry_debugger_receive_byte_code_cp_t, byte_code_p);
|
|
|
|
jmem_cpointer_t byte_code_free_cp;
|
|
memcpy (&byte_code_free_cp, byte_code_p->byte_code_cp, sizeof (jmem_cpointer_t));
|
|
|
|
jerry_debugger_byte_code_free_t *byte_code_free_p;
|
|
byte_code_free_p = JMEM_CP_GET_NON_NULL_POINTER (jerry_debugger_byte_code_free_t,
|
|
byte_code_free_cp);
|
|
|
|
if (JERRY_CONTEXT (debugger_byte_code_free_head) == byte_code_free_cp)
|
|
{
|
|
JERRY_CONTEXT (debugger_byte_code_free_head) = byte_code_free_p->next_cp;
|
|
}
|
|
|
|
if (byte_code_free_p->prev_cp != ECMA_NULL_POINTER)
|
|
{
|
|
jerry_debugger_byte_code_free_t *prev_byte_code_free_p;
|
|
prev_byte_code_free_p = JMEM_CP_GET_NON_NULL_POINTER (jerry_debugger_byte_code_free_t,
|
|
byte_code_free_p->prev_cp);
|
|
|
|
prev_byte_code_free_p->next_cp = byte_code_free_p->next_cp;
|
|
}
|
|
|
|
if (byte_code_free_p->next_cp != ECMA_NULL_POINTER)
|
|
{
|
|
jerry_debugger_byte_code_free_t *next_byte_code_free_p;
|
|
next_byte_code_free_p = JMEM_CP_GET_NON_NULL_POINTER (jerry_debugger_byte_code_free_t,
|
|
byte_code_free_p->next_cp);
|
|
|
|
next_byte_code_free_p->prev_cp = byte_code_free_p->prev_cp;
|
|
}
|
|
|
|
jmem_heap_free_block (byte_code_free_p,
|
|
((size_t) byte_code_free_p->size) << JMEM_ALIGNMENT_LOG);
|
|
return true;
|
|
}
|
|
|
|
case JERRY_DEBUGGER_UPDATE_BREAKPOINT:
|
|
{
|
|
JERRY_DEBUGGER_CHECK_PACKET_SIZE (jerry_debugger_receive_update_breakpoint_t);
|
|
|
|
JERRY_DEBUGGER_RECEIVE_BUFFER_AS (jerry_debugger_receive_update_breakpoint_t, update_breakpoint_p);
|
|
|
|
jmem_cpointer_t byte_code_cp;
|
|
memcpy (&byte_code_cp, update_breakpoint_p->byte_code_cp, sizeof (jmem_cpointer_t));
|
|
uint8_t *byte_code_p = JMEM_CP_GET_NON_NULL_POINTER (uint8_t, byte_code_cp);
|
|
|
|
uint32_t offset;
|
|
memcpy (&offset, update_breakpoint_p->offset, sizeof (uint32_t));
|
|
byte_code_p += offset;
|
|
|
|
JERRY_ASSERT (*byte_code_p == CBC_BREAKPOINT_ENABLED || *byte_code_p == CBC_BREAKPOINT_DISABLED);
|
|
|
|
*byte_code_p = update_breakpoint_p->is_set_breakpoint ? CBC_BREAKPOINT_ENABLED : CBC_BREAKPOINT_DISABLED;
|
|
return true;
|
|
}
|
|
|
|
case JERRY_DEBUGGER_STOP:
|
|
{
|
|
JERRY_DEBUGGER_CHECK_PACKET_SIZE (jerry_debugger_receive_type_t);
|
|
|
|
JERRY_CONTEXT (debugger_stop_exec) = true;
|
|
JERRY_CONTEXT (debugger_stop_context) = NULL;
|
|
*resume_exec_p = false;
|
|
return true;
|
|
}
|
|
|
|
case JERRY_DEBUGGER_CONTINUE:
|
|
{
|
|
JERRY_DEBUGGER_CHECK_PACKET_SIZE (jerry_debugger_receive_type_t);
|
|
|
|
JERRY_CONTEXT (debugger_stop_exec) = false;
|
|
JERRY_CONTEXT (debugger_stop_context) = NULL;
|
|
|
|
*resume_exec_p = true;
|
|
return true;
|
|
}
|
|
|
|
case JERRY_DEBUGGER_STEP:
|
|
{
|
|
JERRY_DEBUGGER_CHECK_PACKET_SIZE (jerry_debugger_receive_type_t);
|
|
|
|
JERRY_CONTEXT (debugger_stop_exec) = true;
|
|
JERRY_CONTEXT (debugger_stop_context) = NULL;
|
|
*resume_exec_p = true;
|
|
return true;
|
|
}
|
|
|
|
case JERRY_DEBUGGER_NEXT:
|
|
{
|
|
JERRY_DEBUGGER_CHECK_PACKET_SIZE (jerry_debugger_receive_type_t);
|
|
|
|
JERRY_CONTEXT (debugger_stop_exec) = true;
|
|
JERRY_CONTEXT (debugger_stop_context) = JERRY_CONTEXT (vm_top_context_p);
|
|
*resume_exec_p = true;
|
|
return true;
|
|
}
|
|
|
|
case JERRY_DEBUGGER_GET_BACKTRACE:
|
|
{
|
|
JERRY_DEBUGGER_CHECK_PACKET_SIZE (jerry_debugger_receive_get_backtrace_t);
|
|
|
|
jerry_debugger_send_backtrace (recv_buffer_p);
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
{
|
|
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Unexpected message.");
|
|
jerry_debugger_close_connection ();
|
|
return false;
|
|
}
|
|
}
|
|
} /* jerry_debugger_process_message */
|
|
|
|
#undef JERRY_DEBUGGER_CHECK_PACKET_SIZE
|
|
|
|
/**
|
|
* Tell the client that a breakpoint has been hit and wait for further debugger commands.
|
|
*/
|
|
void
|
|
jerry_debugger_breakpoint_hit (void)
|
|
{
|
|
JERRY_ASSERT (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER);
|
|
|
|
JERRY_DEBUGGER_SEND_BUFFER_AS (jerry_debugger_send_breakpoint_hit_t, breakpoint_hit_p);
|
|
|
|
JERRY_DEBUGGER_INIT_SEND_MESSAGE (breakpoint_hit_p);
|
|
JERRY_DEBUGGER_SET_SEND_MESSAGE_SIZE_FROM_TYPE (breakpoint_hit_p, jerry_debugger_send_breakpoint_hit_t);
|
|
breakpoint_hit_p->type = (uint8_t) JERRY_DEBUGGER_BREAKPOINT_HIT;
|
|
|
|
vm_frame_ctx_t *frame_ctx_p = JERRY_CONTEXT (vm_top_context_p);
|
|
|
|
jmem_cpointer_t byte_code_header_cp;
|
|
JMEM_CP_SET_NON_NULL_POINTER (byte_code_header_cp, frame_ctx_p->bytecode_header_p);
|
|
memcpy (breakpoint_hit_p->byte_code_cp, &byte_code_header_cp, sizeof (jmem_cpointer_t));
|
|
|
|
uint32_t offset = (uint32_t) (frame_ctx_p->byte_code_p - (uint8_t *) frame_ctx_p->bytecode_header_p);
|
|
memcpy (breakpoint_hit_p->offset, &offset, sizeof (uint32_t));
|
|
|
|
if (!jerry_debugger_send (sizeof (jerry_debugger_send_breakpoint_hit_t)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
while (!jerry_debugger_receive ())
|
|
{
|
|
}
|
|
|
|
JERRY_CONTEXT (debugger_message_delay) = JERRY_DEBUGGER_MESSAGE_FREQUENCY;
|
|
} /* jerry_debugger_breakpoint_hit */
|
|
|
|
/**
|
|
* Send the type signal to the client.
|
|
*/
|
|
void
|
|
jerry_debugger_send_type (jerry_debugger_header_type_t type) /**< message type */
|
|
{
|
|
JERRY_ASSERT (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER);
|
|
|
|
JERRY_DEBUGGER_SEND_BUFFER_AS (jerry_debugger_send_type_t, message_type_p);
|
|
|
|
JERRY_DEBUGGER_INIT_SEND_MESSAGE (message_type_p);
|
|
JERRY_DEBUGGER_SET_SEND_MESSAGE_SIZE_FROM_TYPE (message_type_p, jerry_debugger_send_type_t)
|
|
message_type_p->type = (uint8_t) type;
|
|
|
|
jerry_debugger_send (sizeof (jerry_debugger_send_type_t));
|
|
} /* jerry_debugger_send_type */
|
|
|
|
|
|
/**
|
|
* Send the type signal to the client.
|
|
*
|
|
* @return true - if the data sent successfully to the debugger client,
|
|
* false - otherwise
|
|
*/
|
|
bool
|
|
jerry_debugger_send_configuration (uint8_t max_message_size) /**< maximum message size */
|
|
{
|
|
JERRY_DEBUGGER_SEND_BUFFER_AS (jerry_debugger_send_configuration_t, configuration_p);
|
|
|
|
/* Helper structure for endianness check. */
|
|
union
|
|
{
|
|
uint16_t uint16_value; /**< a 16-bit value */
|
|
uint8_t uint8_value[2]; /**< lower and upper byte of a 16-bit value */
|
|
} endian_data;
|
|
|
|
endian_data.uint16_value = 1;
|
|
|
|
JERRY_DEBUGGER_INIT_SEND_MESSAGE (configuration_p);
|
|
JERRY_DEBUGGER_SET_SEND_MESSAGE_SIZE_FROM_TYPE (configuration_p, jerry_debugger_send_configuration_t);
|
|
configuration_p->type = JERRY_DEBUGGER_CONFIGURATION;
|
|
configuration_p->max_message_size = max_message_size;
|
|
configuration_p->cpointer_size = sizeof (jmem_cpointer_t);
|
|
configuration_p->little_endian = (endian_data.uint8_value[0] == 1);
|
|
|
|
return jerry_debugger_send (sizeof (jerry_debugger_send_configuration_t));
|
|
} /* jerry_debugger_send_configuration */
|
|
|
|
/**
|
|
* Send raw data to the debugger client.
|
|
*/
|
|
void
|
|
jerry_debugger_send_data (jerry_debugger_header_type_t type, /**< message type */
|
|
const void *data, /**< raw data */
|
|
size_t size) /**< size of data */
|
|
{
|
|
JERRY_ASSERT (size < JERRY_DEBUGGER_MAX_SIZE (uint8_t));
|
|
|
|
JERRY_DEBUGGER_SEND_BUFFER_AS (jerry_debugger_send_type_t, message_type_p);
|
|
|
|
JERRY_DEBUGGER_INIT_SEND_MESSAGE (message_type_p);
|
|
JERRY_DEBUGGER_SET_SEND_MESSAGE_SIZE (message_type_p, 1 + size);
|
|
message_type_p->type = type;
|
|
memcpy (message_type_p + 1, data, size);
|
|
|
|
jerry_debugger_send (sizeof (jerry_debugger_send_type_t) + size);
|
|
} /* jerry_debugger_send_data */
|
|
|
|
/**
|
|
* Send string to the debugger client.
|
|
*/
|
|
void
|
|
jerry_debugger_send_string (uint8_t message_type, /**< message type */
|
|
const jerry_char_t *string_p, /**< string data */
|
|
size_t string_length) /**< length of string */
|
|
{
|
|
JERRY_ASSERT (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER);
|
|
|
|
const size_t max_fragment_len = JERRY_DEBUGGER_MAX_SIZE (char);
|
|
|
|
JERRY_DEBUGGER_SEND_BUFFER_AS (jerry_debugger_send_string_t, message_string_p);
|
|
|
|
JERRY_DEBUGGER_INIT_SEND_MESSAGE (message_string_p);
|
|
JERRY_DEBUGGER_SET_SEND_MESSAGE_SIZE_FROM_TYPE (message_string_p, jerry_debugger_send_string_t);
|
|
message_string_p->type = message_type;
|
|
|
|
while (string_length > max_fragment_len)
|
|
{
|
|
memcpy (message_string_p->string, string_p, max_fragment_len);
|
|
|
|
if (!jerry_debugger_send (sizeof (jerry_debugger_send_string_t)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
string_length -= max_fragment_len;
|
|
string_p += max_fragment_len;
|
|
}
|
|
|
|
JERRY_DEBUGGER_SET_SEND_MESSAGE_SIZE (message_string_p, 1 + string_length);
|
|
message_string_p->type = (uint8_t) (message_type + 1);
|
|
|
|
memcpy (message_string_p->string, string_p, string_length);
|
|
|
|
jerry_debugger_send (sizeof (jerry_debugger_send_type_t) + string_length);
|
|
} /* jerry_debugger_send_string */
|
|
|
|
/**
|
|
* Send the function name to the debugger client.
|
|
*/
|
|
void
|
|
jerry_debugger_send_function_name (const jerry_char_t *function_name_p, /**< function name */
|
|
size_t function_name_length) /**< length of function name */
|
|
{
|
|
JERRY_ASSERT (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER);
|
|
|
|
jerry_debugger_send_string (JERRY_DEBUGGER_FUNCTION_NAME, function_name_p, function_name_length);
|
|
} /* jerry_debugger_send_function_name */
|
|
|
|
/**
|
|
* Send the function compressed pointer to the debugger client.
|
|
*
|
|
* @return true - if the data was sent successfully to the debugger client,
|
|
* false - otherwise
|
|
*/
|
|
bool
|
|
jerry_debugger_send_function_cp (jerry_debugger_header_type_t type, /**< message type */
|
|
ecma_compiled_code_t *compiled_code_p) /**< byte code pointer */
|
|
{
|
|
JERRY_ASSERT (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER);
|
|
|
|
JERRY_DEBUGGER_SEND_BUFFER_AS (jerry_debugger_send_byte_code_cp_t, byte_code_cp_p);
|
|
|
|
JERRY_DEBUGGER_INIT_SEND_MESSAGE (byte_code_cp_p);
|
|
JERRY_DEBUGGER_SET_SEND_MESSAGE_SIZE_FROM_TYPE (byte_code_cp_p, jerry_debugger_send_byte_code_cp_t);
|
|
byte_code_cp_p->type = (uint8_t) type;
|
|
|
|
jmem_cpointer_t compiled_code_cp;
|
|
JMEM_CP_SET_NON_NULL_POINTER (compiled_code_cp, compiled_code_p);
|
|
memcpy (byte_code_cp_p->byte_code_cp, &compiled_code_cp, sizeof (jmem_cpointer_t));
|
|
|
|
return jerry_debugger_send (sizeof (jerry_debugger_send_byte_code_cp_t));
|
|
} /* jerry_debugger_send_function_cp */
|
|
|
|
#endif /* JERRY_DEBUGGER */
|