Initial version of JerryScript debugger (#1557)

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
This commit is contained in:
Levente Orban 2017-02-14 15:03:01 +01:00 committed by Tilmann Scheller
parent 453066fcf1
commit 025a99ccbb
39 changed files with 4166 additions and 5 deletions

View File

@ -6,6 +6,7 @@ sudo: required
env:
- OPTS="--check-signed-off-travis --check-cppcheck --check-doxygen --check-vera --check-license"
- OPTS="--jerry-debugger"
- OPTS="--jerry-tests --jerry-test-suite"
- OPTS="--jerry-tests --jerry-test-suite --toolchain=cmake/toolchain_linux_armv7l.cmake" TIMEOUT=300 INSTALL_QEMU_ARM=yes
- OPTS="--buildoption-test"

View File

@ -9,6 +9,7 @@ Enum that contains the following elements:
- JERRY_INIT_SHOW_REGEXP_OPCODES - dump regexp byte-code to log after compilation
- JERRY_INIT_MEM_STATS - dump memory statistics
- JERRY_INIT_MEM_STATS_SEPARATE - dump memory statistics and reset peak values after parse
- JERRY_INIT_DEBUGGER - enable all features required by debugging
## jerry_error_t
@ -211,6 +212,7 @@ jerry_init (jerry_init_flag_t flags)
- `JERRY_INIT_SHOW_REGEXP_OPCODES` - print compiled regexp byte-code.
- `JERRY_INIT_MEM_STATS` - dump memory statistics.
- `JERRY_INIT_MEM_STATS_SEPARATE` - dump memory statistics and reset peak values after parse.
- `JERRY_INIT_DEBUGGER` - enable all features required by debugging.
**Example**
@ -451,6 +453,40 @@ jerry_parse (const jerry_char_t *source_p,
- [jerry_run](#jerry_run)
## jerry_parse_named_resource
**Summary**
Parse script and construct an ECMAScript function. The lexical
environment is set to the global lexical environment. The name
(usually a file name) is also passed to this function which is
used by the debugger to find the source code.
*Note*: The returned value must be freed with [jerry_release_value](#jerry_release_value) when it
is no longer needed.
**Prototype**
```c
jerry_value_t
jerry_parse_named_resource (const jerry_char_t *name_p, /**< name (usually a file name) */
size_t name_length, /**< length of name */
const jerry_char_t *source_p, /**< script source */
size_t source_size, /**< script source size */
bool is_strict) /**< strict mode */
{
```
- `name_p` - name, usually a file name
- `name_length` - size of the file name, in bytes
- `source_p` - string, containing source code to parse. It must be a valid UTF8 string
- `source_size` - size of the string, in bytes
- `is_strict` - defines strict mode
- return value
- function object value, if script was parsed successfully,
- thrown error, otherwise
This function is identical to [jerry_parse](#jerry_parse), except that an additional filename parameter has been added.
## jerry_run

72
docs/07.DEBUGGER.md Normal file
View File

@ -0,0 +1,72 @@
## JerryScript debugger interface
JerryScript provides a remote debugger which allows debugging
JavaScript programs. The debugger has two main components:
a server which is part of the JerryScript binary and a
separate client application. Currently two debugger clients
are available in the /jerry-debugger subdirectory: an HTML
and a Python application. These simple applications demonstrate
the communication protocol between the client and server and can
be reused by integrated development environments.
## Setting up the debugger server
The following arguments must be passed to `tools/build.py`:
`--jerry-debugger=on --jerry-libc=off`
At the moment only a Websocket-based implementation is provided
by JerryScript which transmits messages over TCP/IP networks.
This implementation requires a socket API which is not yet
supported by jerry-libc so the standard libc is used instead.
In the future any reliable stream or datagram based protocol
can be used for transmitting debugger messages.
## Debugging JavaScript applications
The debugger client must be connected to the server before the
JavaScript application runs. On-the-fly attachment is not supported
because the debugging information (e.g. line index of each possible
breakpoint location) is not preserved by JerryScript. The client is
expected to be run on a system with much more resources and it should
be capable of storing this information. JerryScript frees all debug
information after it is transmitted to the client to save memory.
The following argument makes JerryScript wait for a client
connection:
`--start-debug-server`
It is also recommended to increase the log level to see
the *Waiting for client connection* message:
`--log-level 2`
The HTML client can connect to the IP address of the server with
the `connect` command. The IP address can be localhost
if the server and the client are running on the same machine.
After the connection is established the execution can be
controlled by the debugger. The debugger always stops at
the first possible breakpoint location. The effect is the
same as using the `stop` command. This allows inserting
breakpoints right before the meaningful part of the execution
starts.
All available commands of the client can be queried by the
`help` command.
## Integrating debugger support into applications using JerryScript
The debugger can be enabled by passing the `JERRY_INIT_DEBUGGER` flag
to the `jerry_init ()` function which then initializes the debugger
and blocks until a client connects.
When the debugger is enabled it is recommended to use
`jerry_parse_named_resource ()` instead of `jerry_parse ()` because
the resource name (usually a file name) is also passed to this
function. This resource name is used by the client to identify
the corresponding resource. In general it is always recommended to
use `jerry_parse_named_resource ()` when the resource name is
available because it silently ignores the resource name if the
debugger is disabled.

View File

@ -18,6 +18,7 @@ project (${JERRY_CORE_NAME} C)
# Optional features
set(FEATURE_CPOINTER_32_BIT OFF CACHE BOOL "Enable 32 bit compressed pointers?")
set(FEATURE_DEBUGGER OFF CACHE BOOL "Enable JerryScript debugger?")
set(FEATURE_ERROR_MESSAGES OFF CACHE BOOL "Enable error messages?")
set(FEATURE_JS_PARSER ON CACHE BOOL "Enable js-parser?")
set(FEATURE_MEM_STATS OFF CACHE BOOL "Enable memory statistics?")
@ -38,6 +39,7 @@ endif()
# Status messages
message(STATUS "FEATURE_CPOINTER_32_BIT " ${FEATURE_CPOINTER_32_BIT})
message(STATUS "FEATURE_DEBUGGER " ${FEATURE_DEBUGGER})
message(STATUS "FEATURE_ERROR_MESSAGES " ${FEATURE_ERROR_MESSAGES})
message(STATUS "FEATURE_JS_PARSER " ${FEATURE_JS_PARSER})
message(STATUS "FEATURE_MEM_STATS " ${FEATURE_MEM_STATS})
@ -55,6 +57,7 @@ message(STATUS "MEM_HEAP_SIZE_KB " ${MEM_HEAP_SIZE_KB})
# Include directories
set(INCLUDE_CORE
"${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/debugger"
"${CMAKE_CURRENT_SOURCE_DIR}/ecma/base"
"${CMAKE_CURRENT_SOURCE_DIR}/ecma/builtin-objects"
"${CMAKE_CURRENT_SOURCE_DIR}/ecma/builtin-objects/typedarray"
@ -70,6 +73,7 @@ set(INCLUDE_CORE
# Sources
# Jerry core
file(GLOB SOURCE_CORE_API *.c)
file(GLOB SOURCE_CORE_DEBUGGER debugger/*.c)
file(GLOB SOURCE_CORE_ECMA_BASE ecma/base/*.c)
file(GLOB SOURCE_CORE_ECMA_BUILTINS ecma/builtin-objects/*.c)
file(GLOB SOURCE_CORE_ECMA_BUILTINS_TYPEDARRAY ecma/builtin-objects/typedarray/*.c)
@ -96,6 +100,10 @@ set(SOURCE_CORE_FILES
${SOURCE_CORE_PARSER_REGEXP}
${SOURCE_CORE_VM})
if(FEATURE_DEBUGGER)
set(SOURCE_CORE_FILES ${SOURCE_CORE_FILES} ${SOURCE_CORE_DEBUGGER})
endif()
# Jerry port
file(GLOB SOURCE_PORT_FILES "${PORT_DIR}/*.c")
@ -162,6 +170,11 @@ if(FEATURE_MEM_STATS)
set(DEFINES_JERRY ${DEFINES_JERRY} JMEM_STATS)
endif()
# Enable debugger
if(FEATURE_DEBUGGER)
set(DEFINES_JERRY ${DEFINES_JERRY} JERRY_DEBUGGER)
endif()
# Memory management stress-test mode
if(FEATURE_MEM_STRESS_TEST)
set(DEFINES_JERRY ${DEFINES_JERRY} JMEM_GC_BEFORE_EACH_ALLOC)
@ -185,6 +198,10 @@ else()
MESSAGE(FATAL_ERROR "Profile file: '${FEATURE_PROFILE}' doesn't exist!")
endif()
if(JERRY_LIBC AND FEATURE_DEBUGGER)
MESSAGE(FATAL_ERROR "This configuration is not supported. Please build against your system libc to enable the JerryScript debugger.")
endif()
# RegExp byte-code dumps
if(FEATURE_REGEXP_DUMP)
set(DEFINES_JERRY ${DEFINES_JERRY} REGEXP_DUMP_BYTE_CODE)

View File

@ -0,0 +1,547 @@
/* 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 <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include "jcontext.h"
#include "jerry-debugger.h"
#include "jerry-port.h"
/**
* Debugger socket communication port.
*/
#define JERRY_DEBUGGER_PORT 5001
/**
* Masking-key is available.
*/
#define JERRY_DEBUGGER_WEBSOCKET_MASK_BIT 0x80
/**
* Opcode type mask.
*/
#define JERRY_DEBUGGER_WEBSOCKET_OPCODE_MASK 0x0fu
/**
* Packet length mask.
*/
#define JERRY_DEBUGGER_WEBSOCKET_LENGTH_MASK 0x7fu
/**
* Payload mask size in bytes of a websocket package.
*/
#define JERRY_DEBUGGER_WEBSOCKET_MASK_SIZE 4
/**
* Header for incoming packets.
*/
typedef struct
{
uint8_t ws_opcode; /**< websocket opcode */
uint8_t size; /**< size of the message */
uint8_t mask[4]; /**< mask bytes */
} jerry_debugger_receive_header_t;
/**
* Close the socket connection to the client.
*/
static void
jerry_debugger_close_connection_tcp (bool log_error) /**< log error */
{
JERRY_ASSERT (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER);
JERRY_CONTEXT (jerry_init_flags) &= (uint32_t) ~JERRY_INIT_DEBUGGER;
if (log_error)
{
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", strerror (errno));
}
jerry_port_log (JERRY_LOG_LEVEL_DEBUG, "Debugger client connection closed.\n");
close (JERRY_CONTEXT (debugger_connection));
JERRY_CONTEXT (debugger_connection) = -1;
jerry_debugger_free_unreferenced_byte_code ();
} /* jerry_debugger_close_connection_tcp */
/**
* Send message to the client side.
*
* @return true - if the data was sent successfully to the client side
* false - otherwise
*/
static bool
jerry_debugger_send_tcp (const uint8_t *data_p, /**< data pointer */
size_t data_size) /**< data size */
{
JERRY_ASSERT (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER);
do
{
ssize_t sent_bytes = send (JERRY_CONTEXT (debugger_connection), data_p, data_size, 0);
if (sent_bytes < 0)
{
if (errno == EWOULDBLOCK)
{
continue;
}
jerry_debugger_close_connection_tcp (true);
return false;
}
data_size -= (size_t) sent_bytes;
data_p += sent_bytes;
}
while (data_size > 0);
return true;
} /* jerry_debugger_send_tcp */
/**
* Convert a 6-bit value to a Base64 character.
*
* @return Base64 character
*/
static uint8_t
jerry_to_base64_character (uint8_t value) /**< 6-bit value */
{
if (value < 26)
{
return (uint8_t) (value + 'A');
}
if (value < 52)
{
return (uint8_t) (value - 26 + 'a');
}
if (value < 62)
{
return (uint8_t) (value - 52 + '0');
}
if (value == 62)
{
return (uint8_t) '+';
}
return (uint8_t) '/';
} /* jerry_to_base64_character */
/**
* Encode a byte sequence into Base64 string.
*/
static void
jerry_to_base64 (const uint8_t *source_p, /**< source data */
uint8_t *destination_p, /**< destination buffer */
size_t length) /**< length of source, must be divisible by 3 */
{
while (length >= 3)
{
uint8_t value = (source_p[0] >> 2);
destination_p[0] = jerry_to_base64_character (value);
value = (uint8_t) (((source_p[0] << 4) | (source_p[1] >> 4)) & 0x3f);
destination_p[1] = jerry_to_base64_character (value);
value = (uint8_t) (((source_p[1] << 2) | (source_p[2] >> 6)) & 0x3f);
destination_p[2] = jerry_to_base64_character (value);
value = (uint8_t) (source_p[2] & 0x3f);
destination_p[3] = jerry_to_base64_character (value);
source_p += 3;
destination_p += 4;
length -= 3;
}
} /* jerry_to_base64 */
/**
* Process WebSocket handshake.
*
* @return true - if the handshake was completed successfully
* false - otherwise
*/
static bool
jerry_process_handshake (int client_socket, /**< client socket */
uint8_t *request_buffer_p) /**< temporary buffer */
{
size_t request_buffer_size = 1024;
uint8_t *request_end_p = request_buffer_p;
/* Buffer request text until the double newlines are received. */
while (true)
{
size_t length = request_buffer_size - 1u - (size_t) (request_end_p - request_buffer_p);
if (length == 0)
{
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Handshake buffer too small.\n");
return false;
}
ssize_t size = recv (client_socket, request_end_p, length, 0);
if (size < 0)
{
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", strerror (errno));
return false;
}
request_end_p += (size_t) size;
*request_end_p = 0;
if (request_end_p > request_buffer_p + 4
&& memcmp (request_end_p - 4, "\r\n\r\n", 4) == 0)
{
break;
}
}
/* Check protocol. */
const char *text_p = "GET /jerry-debugger";
size_t text_len = strlen (text_p);
if ((size_t) (request_end_p - request_buffer_p) < text_len
|| memcmp (request_buffer_p, text_p, text_len) != 0)
{
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Invalid handshake format.\n");
return false;
}
uint8_t *websocket_key_p = request_buffer_p + text_len;
text_p = "Sec-WebSocket-Key:";
text_len = strlen (text_p);
while (true)
{
if ((size_t) (request_end_p - websocket_key_p) < text_len)
{
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Sec-WebSocket-Key not found.\n");
return false;
}
if (websocket_key_p[0] == 'S'
&& websocket_key_p[-1] == '\n'
&& websocket_key_p[-2] == '\r'
&& memcmp (websocket_key_p, text_p, text_len) == 0)
{
websocket_key_p += text_len;
break;
}
websocket_key_p++;
}
/* String terminated by double newlines. */
while (*websocket_key_p == ' ')
{
websocket_key_p++;
}
uint8_t *websocket_key_end_p = websocket_key_p;
while (*websocket_key_end_p > ' ')
{
websocket_key_end_p++;
}
/* Since the request_buffer_p is not needed anymore it can
* be reused for storing the SHA-1 key and Base64 string. */
const size_t sha1_length = 20;
jerry_debugger_compute_sha1 (websocket_key_p,
(size_t) (websocket_key_end_p - websocket_key_p),
(const uint8_t *) "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
36,
request_buffer_p);
/* The SHA-1 key is 20 bytes long but jerry_to_base64 expects
* a length divisible by 3 so an extra 0 is appended at the end. */
request_buffer_p[sha1_length] = 0;
jerry_to_base64 (request_buffer_p, request_buffer_p + sha1_length + 1, sha1_length + 1);
/* Last value must be replaced by equal sign. */
text_p = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ";
if (!jerry_debugger_send_tcp ((const uint8_t *) text_p, strlen (text_p))
|| !jerry_debugger_send_tcp (request_buffer_p + sha1_length + 1, 27))
{
return false;
}
text_p = "=\r\n\r\n";
return jerry_debugger_send_tcp ((const uint8_t *) text_p, strlen (text_p));
} /* jerry_process_handshake */
/**
* Initialize the socket connection.
*
* @return true - if the connection succeeded
* false - otherwise
*/
bool
jerry_debugger_accept_connection ()
{
int server_socket;
struct sockaddr_in addr;
socklen_t sin_size = sizeof (struct sockaddr_in);
JERRY_ASSERT (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER);
/* Disable debugger flag temporarily. */
JERRY_CONTEXT (jerry_init_flags) &= (uint32_t) ~JERRY_INIT_DEBUGGER;
addr.sin_family = AF_INET;
addr.sin_port = htons (JERRY_DEBUGGER_PORT);
addr.sin_addr.s_addr = INADDR_ANY;
if ((server_socket = socket (AF_INET, SOCK_STREAM, 0)) == -1)
{
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", strerror (errno));
return false;
}
int opt_value = 1;
if (setsockopt (server_socket, SOL_SOCKET, SO_REUSEADDR, &opt_value, sizeof (int)) == -1)
{
close (server_socket);
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", strerror (errno));
return false;
}
if (bind (server_socket, (struct sockaddr *)&addr, sizeof (struct sockaddr)) == -1)
{
close (server_socket);
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", strerror (errno));
return false;
}
if (listen (server_socket, 1) == -1)
{
close (server_socket);
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", strerror (errno));
return false;
}
jerry_port_log (JERRY_LOG_LEVEL_DEBUG, "Waiting for client connection\n");
JERRY_CONTEXT (debugger_connection) = accept (server_socket, (struct sockaddr *)&addr, &sin_size);
if (JERRY_CONTEXT (debugger_connection) == -1)
{
close (server_socket);
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", strerror (errno));
return false;
}
close (server_socket);
/* Enable debugger flag again. */
JERRY_CONTEXT (jerry_init_flags) |= JERRY_INIT_DEBUGGER;
bool is_handshake_ok = false;
JMEM_DEFINE_LOCAL_ARRAY (request_buffer_p, 1024, uint8_t);
is_handshake_ok = jerry_process_handshake (JERRY_CONTEXT (debugger_connection),
request_buffer_p);
JMEM_FINALIZE_LOCAL_ARRAY (request_buffer_p);
if (!is_handshake_ok)
{
jerry_debugger_close_connection ();
return false;
}
if (!jerry_debugger_send_configuration (JERRY_DEBUGGER_MAX_BUFFER_SIZE - sizeof (jerry_debugger_receive_header_t)))
{
return false;
}
/* Set non-blocking mode. */
int socket_flags = fcntl (JERRY_CONTEXT (debugger_connection), F_GETFL, 0);
if (socket_flags < 0)
{
jerry_debugger_close_connection_tcp (true);
return false;
}
if (fcntl (JERRY_CONTEXT (debugger_connection), F_SETFL, socket_flags | O_NONBLOCK) == -1)
{
jerry_debugger_close_connection_tcp (true);
return false;
}
jerry_port_log (JERRY_LOG_LEVEL_DEBUG, "Connected from: %s\n", inet_ntoa (addr.sin_addr));
JERRY_CONTEXT (debugger_stop_exec) = true;
JERRY_CONTEXT (debugger_stop_context) = NULL;
return true;
} /* jerry_debugger_accept_connection */
/**
* Close the socket connection to the client.
*/
inline void __attr_always_inline___
jerry_debugger_close_connection (void)
{
jerry_debugger_close_connection_tcp (false);
} /* jerry_debugger_close_connection */
/**
* Send message to the client side
*
* @return true - if the data was sent successfully to the debugger client,
* false - otherwise
*/
inline bool __attr_always_inline___
jerry_debugger_send (size_t data_size) /**< data size */
{
return jerry_debugger_send_tcp (JERRY_CONTEXT (debugger_send_buffer), data_size);
} /* jerry_debugger_send */
/**
* Receive message from the client.
*
* Note:
* If the function returns with true, the value of
* JERRY_CONTEXT (debugger_stop_exec) should be ignored.
*
* @return true - if execution should be resumed,
* false - otherwise
*/
bool
jerry_debugger_receive (void)
{
JERRY_ASSERT (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER);
JERRY_CONTEXT (debugger_message_delay) = JERRY_DEBUGGER_MESSAGE_FREQUENCY;
uint8_t *recv_buffer_p = JERRY_CONTEXT (debugger_receive_buffer);
bool resume_exec = false;
while (true)
{
uint32_t offset = JERRY_CONTEXT (debugger_receive_buffer_offset);
ssize_t byte_recv = recv (JERRY_CONTEXT (debugger_connection),
recv_buffer_p + offset,
JERRY_DEBUGGER_MAX_BUFFER_SIZE - offset,
0);
if (byte_recv <= 0)
{
if (byte_recv < 0 && errno != EWOULDBLOCK)
{
jerry_debugger_close_connection_tcp (true);
return true;
}
return resume_exec;
}
JERRY_CONTEXT (debugger_receive_buffer_offset) += (uint32_t) byte_recv;
if (JERRY_CONTEXT (debugger_receive_buffer_offset) < sizeof (jerry_debugger_receive_header_t))
{
return resume_exec;
}
const size_t max_packet_size = JERRY_DEBUGGER_MAX_BUFFER_SIZE - sizeof (jerry_debugger_receive_header_t);
JERRY_ASSERT (max_packet_size < 126);
if ((recv_buffer_p[0] & ~JERRY_DEBUGGER_WEBSOCKET_OPCODE_MASK) != JERRY_DEBUGGER_WEBSOCKET_FIN_BIT
|| (recv_buffer_p[1] & JERRY_DEBUGGER_WEBSOCKET_LENGTH_MASK) >= max_packet_size
|| !(recv_buffer_p[1] & JERRY_DEBUGGER_WEBSOCKET_MASK_BIT))
{
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Unsupported Websocket message.\n");
jerry_debugger_close_connection ();
return true;
}
if ((recv_buffer_p[0] & JERRY_DEBUGGER_WEBSOCKET_OPCODE_MASK) != JERRY_DEBUGGER_WEBSOCKET_BINARY_FRAME)
{
jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Unsupported Websocket opcode.\n");
jerry_debugger_close_connection ();
return true;
}
uint32_t message_size = (uint32_t) (recv_buffer_p[1] & JERRY_DEBUGGER_WEBSOCKET_LENGTH_MASK);
uint32_t message_total_size = (uint32_t) (message_size + sizeof (jerry_debugger_receive_header_t));
if (JERRY_CONTEXT (debugger_receive_buffer_offset) < message_total_size)
{
return resume_exec;
}
/* Unmask data bytes. */
uint8_t *data_p = recv_buffer_p + sizeof (jerry_debugger_receive_header_t);
const uint8_t *mask_p = data_p - JERRY_DEBUGGER_WEBSOCKET_MASK_SIZE;
const uint8_t *mask_end_p = data_p;
const uint8_t *data_end_p = data_p + message_size;
while (data_p < data_end_p)
{
/* Invert certain bits with xor operation. */
*data_p = *data_p ^ *mask_p;
data_p++;
mask_p++;
if (mask_p >= mask_end_p)
{
mask_p -= JERRY_DEBUGGER_WEBSOCKET_MASK_SIZE;
}
}
if (!jerry_debugger_process_message (recv_buffer_p + sizeof (jerry_debugger_receive_header_t),
message_size,
&resume_exec))
{
return true;
}
if (message_total_size < JERRY_CONTEXT (debugger_receive_buffer_offset))
{
memcpy (recv_buffer_p,
recv_buffer_p + message_total_size,
JERRY_CONTEXT (debugger_receive_buffer_offset) - message_total_size);
}
JERRY_CONTEXT (debugger_receive_buffer_offset) -= message_total_size;
}
} /* jerry_debugger_receive */
#endif /* JERRY_DEBUGGER */

View File

@ -0,0 +1,86 @@
/* 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.
*/
#ifndef JERRY_DEBUGGER_WS_H
#define JERRY_DEBUGGER_WS_H
#ifdef JERRY_DEBUGGER
#include "ecma-globals.h"
/* JerryScript debugger protocol is a simplified version of RFC-6455 (WebSockets). */
/**
* Maximum number of bytes transmitted or received.
*/
#define JERRY_DEBUGGER_MAX_BUFFER_SIZE 128
/**
* Last fragment of a Websocket package.
*/
#define JERRY_DEBUGGER_WEBSOCKET_FIN_BIT 0x80
/**
* WebSocket opcode types.
*/
typedef enum
{
JERRY_DEBUGGER_WEBSOCKET_TEXT_FRAME = 1, /**< text frame */
JERRY_DEBUGGER_WEBSOCKET_BINARY_FRAME = 2, /**< binary frame */
JERRY_DEBUGGER_WEBSOCKET_CLOSE_CONNECTION = 8, /**< close connection */
JERRY_DEBUGGER_WEBSOCKET_PING = 9, /**< ping (keep alive) frame */
JERRY_DEBUGGER_WEBSOCKET_PONG = 10, /**< reply to ping frame */
} jerry_websocket_opcode_type_t;
/**
* Header for outgoing packets.
*/
typedef struct
{
uint8_t ws_opcode; /**< Websocket opcode */
uint8_t size; /**< size of the message */
} jerry_debugger_send_header_t;
/**
* Initialize the header of an outgoing message.
*/
#define JERRY_DEBUGGER_INIT_SEND_MESSAGE(message_p) \
(message_p)->header.ws_opcode = JERRY_DEBUGGER_WEBSOCKET_FIN_BIT | JERRY_DEBUGGER_WEBSOCKET_BINARY_FRAME;
/**
* Set the size of an outgoing message from type.
*/
#define JERRY_DEBUGGER_SET_SEND_MESSAGE_SIZE_FROM_TYPE(message_p, type) \
(message_p)->header.size = (uint8_t) (sizeof (type) - sizeof (jerry_debugger_send_header_t));
/**
* Set the size of an outgoing message.
*/
#define JERRY_DEBUGGER_SET_SEND_MESSAGE_SIZE(message_p, byte_size) \
(message_p)->header.size = (uint8_t) (byte_size);
bool jerry_debugger_accept_connection (void);
void jerry_debugger_close_connection (void);
bool jerry_debugger_send (size_t data_size);
bool jerry_debugger_receive (void);
void jerry_debugger_compute_sha1 (const uint8_t *input1, size_t input1_len,
const uint8_t *input2, size_t input2_len,
uint8_t output[20]);
#endif /* JERRY_DEBUGGER */
#endif /* JERRY_DEBUGGER_WS_H */

View File

@ -0,0 +1,446 @@
/* 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 */

View File

@ -0,0 +1,210 @@
/* 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.
*/
#ifndef JERRY_DEBUGGER_H
#define JERRY_DEBUGGER_H
#ifdef JERRY_DEBUGGER
#include "jerry-debugger-ws.h"
#include "ecma-globals.h"
/* JerryScript debugger protocol is a simplified version of RFC-6455 (WebSockets). */
/**
* Frequency of calling jerry_debugger_receive() by the VM.
*/
#define JERRY_DEBUGGER_MESSAGE_FREQUENCY 5
/**
* Limited resources available for the engine, so it is important to
* check the maximum buffer size. It needs to be between 64 and 256 bytes.
*/
#if JERRY_DEBUGGER_MAX_BUFFER_SIZE < 64 || JERRY_DEBUGGER_MAX_BUFFER_SIZE > 256
#error Please define the MAX_BUFFER_SIZE between 64 and 256 bytes.
#endif /* JERRY_DEBUGGER_MAX_BUFFER_SIZE < 64 || JERRY_DEBUGGER_MAX_BUFFER_SIZE > 256 */
/**
* Calculate the maximum number of items for a given type
* which can be transmitted by one message.
*/
#define JERRY_DEBUGGER_MAX_SIZE(type) \
((JERRY_DEBUGGER_MAX_BUFFER_SIZE - sizeof (jerry_debugger_send_header_t) - 1) / sizeof (type))
/**
* Types for the package.
*/
typedef enum
{
/* Messages sent by the server to client. */
JERRY_DEBUGGER_CONFIGURATION = 1, /**< debugger configuration */
JERRY_DEBUGGER_PARSE_ERROR = 2, /**< parse error */
JERRY_DEBUGGER_BYTE_CODE_CP = 3, /**< byte code compressed pointer */
JERRY_DEBUGGER_PARSE_FUNCTION = 4, /**< parsing a new function */
JERRY_DEBUGGER_BREAKPOINT_LIST = 5, /**< list of line offsets */
JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST = 6, /**< list of byte code offsets */
JERRY_DEBUGGER_RESOURCE_NAME = 7, /**< resource name fragment */
JERRY_DEBUGGER_RESOURCE_NAME_END = 8, /**< resource name fragment */
JERRY_DEBUGGER_FUNCTION_NAME = 9, /**< function name fragment */
JERRY_DEBUGGER_FUNCTION_NAME_END = 10, /**< function name fragment */
JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP = 11, /**< invalidate byte code compressed pointer */
JERRY_DEBUGGER_BREAKPOINT_HIT = 12, /**< notify breakpoint hit */
JERRY_DEBUGGER_BACKTRACE = 13, /**< backtrace data */
JERRY_DEBUGGER_BACKTRACE_END = 14, /**< last backtrace data */
/* Messages sent by the client to server. */
JERRY_DEBUGGER_FREE_BYTE_CODE_CP = 1, /**< free byte code compressed pointer */
JERRY_DEBUGGER_UPDATE_BREAKPOINT = 2, /**< update breakpoint status */
JERRY_DEBUGGER_STOP = 3, /**< stop execution */
JERRY_DEBUGGER_CONTINUE = 4, /**< continue execution */
JERRY_DEBUGGER_STEP = 5, /**< next breakpoint, step into functions */
JERRY_DEBUGGER_NEXT = 6, /**< next breakpoint in the same context */
JERRY_DEBUGGER_GET_BACKTRACE = 7, /**< get backtrace */
} jerry_debugger_header_type_t;
/**
* Delayed free of byte code data.
*/
typedef struct
{
uint16_t size;
jmem_cpointer_t prev_cp;
jmem_cpointer_t next_cp;
} jerry_debugger_byte_code_free_t;
/**
* Outgoing message: JerryScript configuration.
*/
typedef struct
{
jerry_debugger_send_header_t header; /**< message header */
uint8_t type; /**< type of the message */
uint8_t max_message_size; /**< maximum incoming message size */
uint8_t cpointer_size; /**< size of compressed pointers */
uint8_t little_endian; /**< little endian machine */
} jerry_debugger_send_configuration_t;
/**
* Outgoing message: message without arguments.
*/
typedef struct
{
jerry_debugger_send_header_t header; /**< message header */
uint8_t type; /**< type of the message */
} jerry_debugger_send_type_t;
/**
* Incoming message: message without arguments.
*/
typedef struct
{
uint8_t type; /**< type of the message */
} jerry_debugger_receive_type_t;
/**
* Outgoing message: string (Source file name or function name).
*/
typedef struct
{
jerry_debugger_send_header_t header; /**< message header */
uint8_t type; /**< type of the message */
uint8_t string[JERRY_DEBUGGER_MAX_SIZE (uint8_t)]; /**< string data */
} jerry_debugger_send_string_t;
/**
* Outgoing message: byte code compressed pointer.
*/
typedef struct
{
jerry_debugger_send_header_t header; /**< message header */
uint8_t type; /**< type of the message */
uint8_t byte_code_cp[sizeof (jmem_cpointer_t)]; /**< byte code compressed pointer */
} jerry_debugger_send_byte_code_cp_t;
/**
* Incoming message: byte code compressed pointer.
*/
typedef struct
{
uint8_t type; /**< type of the message */
uint8_t byte_code_cp[sizeof (jmem_cpointer_t)]; /**< byte code compressed pointer */
} jerry_debugger_receive_byte_code_cp_t;
/**
* Incoming message: update (enable/disable) breakpoint status.
*/
typedef struct
{
uint8_t type; /**< type of the message */
uint8_t is_set_breakpoint; /**< set or clear breakpoint */
uint8_t byte_code_cp[sizeof (jmem_cpointer_t)]; /**< byte code compressed pointer */
uint8_t offset[sizeof (uint32_t)]; /**< breakpoint offset */
} jerry_debugger_receive_update_breakpoint_t;
/**
* Outgoing message: notify breakpoint hit.
*/
typedef struct
{
jerry_debugger_send_header_t header; /**< message header */
uint8_t type; /**< type of the message */
uint8_t byte_code_cp[sizeof (jmem_cpointer_t)]; /**< byte code compressed pointer */
uint8_t offset[sizeof (uint32_t)]; /**< breakpoint offset */
} jerry_debugger_send_breakpoint_hit_t;
/**
* Stack frame descriptor for sending backtrace information.
*/
typedef struct
{
uint8_t byte_code_cp[sizeof (jmem_cpointer_t)]; /**< byte code compressed pointer */
uint8_t offset[sizeof (uint32_t)]; /**< last breakpoint offset */
} jerry_debugger_frame_t;
/**
* Outgoing message: backtrace information.
*/
typedef struct
{
jerry_debugger_send_header_t header; /**< message header */
uint8_t type; /**< type of the message */
jerry_debugger_frame_t frames[JERRY_DEBUGGER_MAX_SIZE (jerry_debugger_frame_t)]; /**< frames */
} jerry_debugger_send_backtrace_t;
/**
* Incoming message: get backtrace.
*/
typedef struct
{
uint8_t type; /**< type of the message */
uint8_t max_depth[sizeof (uint32_t)]; /**< maximum depth (0 - unlimited) */
} jerry_debugger_receive_get_backtrace_t;
void jerry_debugger_free_unreferenced_byte_code (void);
bool jerry_debugger_process_message (uint8_t *recv_buffer_p, uint32_t message_size, bool *resume_exec_p);
void jerry_debugger_breakpoint_hit (void);
void jerry_debugger_send_type (jerry_debugger_header_type_t type);
bool jerry_debugger_send_configuration (uint8_t max_message_size);
void jerry_debugger_send_data (jerry_debugger_header_type_t type, const void *data, size_t size);
void jerry_debugger_send_string (uint8_t message_type, const jerry_char_t *string_p, size_t string_length);
void jerry_debugger_send_function_name (const jerry_char_t *function_name_p, size_t function_name_length);
bool jerry_debugger_send_function_cp (jerry_debugger_header_type_t type, ecma_compiled_code_t *compiled_code_p);
void jerry_debugger_send_source_file_name (const jerry_char_t *file_name_p, size_t file_name_length);
#endif /* JERRY_DEBUGGER */
#endif /* JERRY_DEBUGGER_H */

View File

@ -0,0 +1,370 @@
/* 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.
*/
/*
* FIPS-180-1 compliant SHA-1 implementation
*
* Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* 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.
*
* This file is part of mbed TLS (https://tls.mbed.org)
*/
/*
* The SHA-1 standard was published by NIST in 1993.
*
* http://www.itl.nist.gov/fipspubs/fip180-1.htm
*/
#ifdef JERRY_DEBUGGER
#include "jerry-debugger.h"
/**
* SHA-1 context structure.
*/
typedef struct
{
uint32_t total[2]; /**< number of bytes processed */
uint32_t state[5]; /**< intermediate digest state */
uint8_t buffer[64]; /**< data block being processed */
} jerry_sha1_context;
/* 32-bit integer manipulation macros (big endian). */
#define JERRY_SHA1_GET_UINT32_BE(n, b, i) \
{ \
(n) = (((uint32_t) (b)[(i) + 0]) << 24) \
| (((uint32_t) (b)[(i) + 1]) << 16) \
| (((uint32_t) (b)[(i) + 2]) << 8) \
| ((uint32_t) (b)[(i) + 3]); \
}
#define JERRY_SHA1_PUT_UINT32_BE(n, b, i) \
{ \
(b)[(i) + 0] = (uint8_t) ((n) >> 24); \
(b)[(i) + 1] = (uint8_t) ((n) >> 16); \
(b)[(i) + 2] = (uint8_t) ((n) >> 8); \
(b)[(i) + 3] = (uint8_t) ((n)); \
}
/**
* Initialize SHA-1 context.
*/
static void
jerry_sha1_init (jerry_sha1_context *sha1_context_p) /**< SHA-1 context */
{
memset (sha1_context_p, 0, sizeof (jerry_sha1_context));
sha1_context_p->total[0] = 0;
sha1_context_p->total[1] = 0;
sha1_context_p->state[0] = 0x67452301;
sha1_context_p->state[1] = 0xEFCDAB89;
sha1_context_p->state[2] = 0x98BADCFE;
sha1_context_p->state[3] = 0x10325476;
sha1_context_p->state[4] = 0xC3D2E1F0;
} /* jerry_sha1_init */
#define JERRY_SHA1_P(a, b, c, d, e, x) \
do { \
e += JERRY_SHA1_SHIFT (a, 5) + JERRY_SHA1_F (b, c, d) + K + x; \
b = JERRY_SHA1_SHIFT (b, 30); \
} while (0)
/**
* Update SHA-1 internal buffer status.
*/
static void
jerry_sha1_process (jerry_sha1_context *sha1_context_p, /**< SHA-1 context */
const uint8_t data[64]) /**< data buffer */
{
uint32_t temp, W[16], A, B, C, D, E;
JERRY_SHA1_GET_UINT32_BE (W[0], data, 0);
JERRY_SHA1_GET_UINT32_BE (W[1], data, 4);
JERRY_SHA1_GET_UINT32_BE (W[2], data, 8);
JERRY_SHA1_GET_UINT32_BE (W[3], data, 12);
JERRY_SHA1_GET_UINT32_BE (W[4], data, 16);
JERRY_SHA1_GET_UINT32_BE (W[5], data, 20);
JERRY_SHA1_GET_UINT32_BE (W[6], data, 24);
JERRY_SHA1_GET_UINT32_BE (W[7], data, 28);
JERRY_SHA1_GET_UINT32_BE (W[8], data, 32);
JERRY_SHA1_GET_UINT32_BE (W[9], data, 36);
JERRY_SHA1_GET_UINT32_BE (W[10], data, 40);
JERRY_SHA1_GET_UINT32_BE (W[11], data, 44);
JERRY_SHA1_GET_UINT32_BE (W[12], data, 48);
JERRY_SHA1_GET_UINT32_BE (W[13], data, 52);
JERRY_SHA1_GET_UINT32_BE (W[14], data, 56);
JERRY_SHA1_GET_UINT32_BE (W[15], data, 60);
#define JERRY_SHA1_SHIFT(x, n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)))
#define JERRY_SHA1_R(t) \
( \
temp = W[(t - 3) & 0x0F] ^ W[(t - 8) & 0x0F] ^ W[(t - 14) & 0x0F] ^ W[t & 0x0F], \
W[t & 0x0F] = JERRY_SHA1_SHIFT (temp, 1) \
)
A = sha1_context_p->state[0];
B = sha1_context_p->state[1];
C = sha1_context_p->state[2];
D = sha1_context_p->state[3];
E = sha1_context_p->state[4];
uint32_t K = 0x5A827999;
#define JERRY_SHA1_F(x, y, z) (z ^ (x & (y ^ z)))
JERRY_SHA1_P (A, B, C, D, E, W[0]);
JERRY_SHA1_P (E, A, B, C, D, W[1]);
JERRY_SHA1_P (D, E, A, B, C, W[2]);
JERRY_SHA1_P (C, D, E, A, B, W[3]);
JERRY_SHA1_P (B, C, D, E, A, W[4]);
JERRY_SHA1_P (A, B, C, D, E, W[5]);
JERRY_SHA1_P (E, A, B, C, D, W[6]);
JERRY_SHA1_P (D, E, A, B, C, W[7]);
JERRY_SHA1_P (C, D, E, A, B, W[8]);
JERRY_SHA1_P (B, C, D, E, A, W[9]);
JERRY_SHA1_P (A, B, C, D, E, W[10]);
JERRY_SHA1_P (E, A, B, C, D, W[11]);
JERRY_SHA1_P (D, E, A, B, C, W[12]);
JERRY_SHA1_P (C, D, E, A, B, W[13]);
JERRY_SHA1_P (B, C, D, E, A, W[14]);
JERRY_SHA1_P (A, B, C, D, E, W[15]);
JERRY_SHA1_P (E, A, B, C, D, JERRY_SHA1_R (16));
JERRY_SHA1_P (D, E, A, B, C, JERRY_SHA1_R (17));
JERRY_SHA1_P (C, D, E, A, B, JERRY_SHA1_R (18));
JERRY_SHA1_P (B, C, D, E, A, JERRY_SHA1_R (19));
#undef JERRY_SHA1_F
K = 0x6ED9EBA1;
#define JERRY_SHA1_F(x, y, z) (x ^ y ^ z)
JERRY_SHA1_P (A, B, C, D, E, JERRY_SHA1_R (20));
JERRY_SHA1_P (E, A, B, C, D, JERRY_SHA1_R (21));
JERRY_SHA1_P (D, E, A, B, C, JERRY_SHA1_R (22));
JERRY_SHA1_P (C, D, E, A, B, JERRY_SHA1_R (23));
JERRY_SHA1_P (B, C, D, E, A, JERRY_SHA1_R (24));
JERRY_SHA1_P (A, B, C, D, E, JERRY_SHA1_R (25));
JERRY_SHA1_P (E, A, B, C, D, JERRY_SHA1_R (26));
JERRY_SHA1_P (D, E, A, B, C, JERRY_SHA1_R (27));
JERRY_SHA1_P (C, D, E, A, B, JERRY_SHA1_R (28));
JERRY_SHA1_P (B, C, D, E, A, JERRY_SHA1_R (29));
JERRY_SHA1_P (A, B, C, D, E, JERRY_SHA1_R (30));
JERRY_SHA1_P (E, A, B, C, D, JERRY_SHA1_R (31));
JERRY_SHA1_P (D, E, A, B, C, JERRY_SHA1_R (32));
JERRY_SHA1_P (C, D, E, A, B, JERRY_SHA1_R (33));
JERRY_SHA1_P (B, C, D, E, A, JERRY_SHA1_R (34));
JERRY_SHA1_P (A, B, C, D, E, JERRY_SHA1_R (35));
JERRY_SHA1_P (E, A, B, C, D, JERRY_SHA1_R (36));
JERRY_SHA1_P (D, E, A, B, C, JERRY_SHA1_R (37));
JERRY_SHA1_P (C, D, E, A, B, JERRY_SHA1_R (38));
JERRY_SHA1_P (B, C, D, E, A, JERRY_SHA1_R (39));
#undef JERRY_SHA1_F
K = 0x8F1BBCDC;
#define JERRY_SHA1_F(x, y, z) ((x & y) | (z & (x | y)))
JERRY_SHA1_P (A, B, C, D, E, JERRY_SHA1_R (40));
JERRY_SHA1_P (E, A, B, C, D, JERRY_SHA1_R (41));
JERRY_SHA1_P (D, E, A, B, C, JERRY_SHA1_R (42));
JERRY_SHA1_P (C, D, E, A, B, JERRY_SHA1_R (43));
JERRY_SHA1_P (B, C, D, E, A, JERRY_SHA1_R (44));
JERRY_SHA1_P (A, B, C, D, E, JERRY_SHA1_R (45));
JERRY_SHA1_P (E, A, B, C, D, JERRY_SHA1_R (46));
JERRY_SHA1_P (D, E, A, B, C, JERRY_SHA1_R (47));
JERRY_SHA1_P (C, D, E, A, B, JERRY_SHA1_R (48));
JERRY_SHA1_P (B, C, D, E, A, JERRY_SHA1_R (49));
JERRY_SHA1_P (A, B, C, D, E, JERRY_SHA1_R (50));
JERRY_SHA1_P (E, A, B, C, D, JERRY_SHA1_R (51));
JERRY_SHA1_P (D, E, A, B, C, JERRY_SHA1_R (52));
JERRY_SHA1_P (C, D, E, A, B, JERRY_SHA1_R (53));
JERRY_SHA1_P (B, C, D, E, A, JERRY_SHA1_R (54));
JERRY_SHA1_P (A, B, C, D, E, JERRY_SHA1_R (55));
JERRY_SHA1_P (E, A, B, C, D, JERRY_SHA1_R (56));
JERRY_SHA1_P (D, E, A, B, C, JERRY_SHA1_R (57));
JERRY_SHA1_P (C, D, E, A, B, JERRY_SHA1_R (58));
JERRY_SHA1_P (B, C, D, E, A, JERRY_SHA1_R (59));
#undef JERRY_SHA1_F
K = 0xCA62C1D6;
#define JERRY_SHA1_F(x, y, z) (x ^ y ^ z)
JERRY_SHA1_P (A, B, C, D, E, JERRY_SHA1_R (60));
JERRY_SHA1_P (E, A, B, C, D, JERRY_SHA1_R (61));
JERRY_SHA1_P (D, E, A, B, C, JERRY_SHA1_R (62));
JERRY_SHA1_P (C, D, E, A, B, JERRY_SHA1_R (63));
JERRY_SHA1_P (B, C, D, E, A, JERRY_SHA1_R (64));
JERRY_SHA1_P (A, B, C, D, E, JERRY_SHA1_R (65));
JERRY_SHA1_P (E, A, B, C, D, JERRY_SHA1_R (66));
JERRY_SHA1_P (D, E, A, B, C, JERRY_SHA1_R (67));
JERRY_SHA1_P (C, D, E, A, B, JERRY_SHA1_R (68));
JERRY_SHA1_P (B, C, D, E, A, JERRY_SHA1_R (69));
JERRY_SHA1_P (A, B, C, D, E, JERRY_SHA1_R (70));
JERRY_SHA1_P (E, A, B, C, D, JERRY_SHA1_R (71));
JERRY_SHA1_P (D, E, A, B, C, JERRY_SHA1_R (72));
JERRY_SHA1_P (C, D, E, A, B, JERRY_SHA1_R (73));
JERRY_SHA1_P (B, C, D, E, A, JERRY_SHA1_R (74));
JERRY_SHA1_P (A, B, C, D, E, JERRY_SHA1_R (75));
JERRY_SHA1_P (E, A, B, C, D, JERRY_SHA1_R (76));
JERRY_SHA1_P (D, E, A, B, C, JERRY_SHA1_R (77));
JERRY_SHA1_P (C, D, E, A, B, JERRY_SHA1_R (78));
JERRY_SHA1_P (B, C, D, E, A, JERRY_SHA1_R (79));
#undef JERRY_SHA1_F
sha1_context_p->state[0] += A;
sha1_context_p->state[1] += B;
sha1_context_p->state[2] += C;
sha1_context_p->state[3] += D;
sha1_context_p->state[4] += E;
#undef JERRY_SHA1_SHIFT
#undef JERRY_SHA1_R
} /* jerry_sha1_process */
#undef JERRY_SHA1_P
/**
* SHA-1 update buffer.
*/
static void
jerry_sha1_update (jerry_sha1_context *sha1_context_p, /**< SHA-1 context */
const uint8_t *source_p, /**< source buffer */
size_t source_length) /**< length of source buffer */
{
size_t fill;
uint32_t left;
if (source_length == 0)
{
return;
}
left = sha1_context_p->total[0] & 0x3F;
fill = 64 - left;
sha1_context_p->total[0] += (uint32_t) source_length;
/* Check overflow. */
if (sha1_context_p->total[0] < (uint32_t) source_length)
{
sha1_context_p->total[1]++;
}
if (left && source_length >= fill)
{
memcpy ((void *) (sha1_context_p->buffer + left), source_p, fill);
jerry_sha1_process (sha1_context_p, sha1_context_p->buffer);
source_p += fill;
source_length -= fill;
left = 0;
}
while (source_length >= 64)
{
jerry_sha1_process (sha1_context_p, source_p);
source_p += 64;
source_length -= 64;
}
if (source_length > 0)
{
memcpy ((void *) (sha1_context_p->buffer + left), source_p, source_length);
}
} /* jerry_sha1_update */
/**
* SHA-1 final digest.
*/
static void
jerry_sha1_finish (jerry_sha1_context *sha1_context_p, /**< SHA-1 context */
uint8_t destination_p[20]) /**< result */
{
uint8_t buffer[16];
uint32_t high = (sha1_context_p->total[0] >> 29) | (sha1_context_p->total[1] << 3);
uint32_t low = (sha1_context_p->total[0] << 3);
uint32_t last = sha1_context_p->total[0] & 0x3F;
uint32_t padn = (last < 56) ? (56 - last) : (120 - last);
memset (buffer, 0, sizeof (buffer));
buffer[0] = 0x80;
while (padn > sizeof (buffer))
{
jerry_sha1_update (sha1_context_p, buffer, sizeof (buffer));
buffer[0] = 0;
padn -= (uint32_t) sizeof (buffer);
}
jerry_sha1_update (sha1_context_p, buffer, padn);
JERRY_SHA1_PUT_UINT32_BE (high, buffer, 0);
JERRY_SHA1_PUT_UINT32_BE (low, buffer, 4);
jerry_sha1_update (sha1_context_p, buffer, 8);
JERRY_SHA1_PUT_UINT32_BE (sha1_context_p->state[0], destination_p, 0);
JERRY_SHA1_PUT_UINT32_BE (sha1_context_p->state[1], destination_p, 4);
JERRY_SHA1_PUT_UINT32_BE (sha1_context_p->state[2], destination_p, 8);
JERRY_SHA1_PUT_UINT32_BE (sha1_context_p->state[3], destination_p, 12);
JERRY_SHA1_PUT_UINT32_BE (sha1_context_p->state[4], destination_p, 16);
} /* jerry_sha1_finish */
#undef JERRY_SHA1_GET_UINT32_BE
#undef JERRY_SHA1_PUT_UINT32_BE
/**
* Computes the SHA-1 value of the combination of the two input buffers.
*/
void
jerry_debugger_compute_sha1 (const uint8_t *source1_p, /**< first part of the input */
size_t source1_length, /**< length of the first part */
const uint8_t *source2_p, /**< second part of the input */
size_t source2_length, /**< length of the second part */
uint8_t destination_p[20]) /**< result */
{
JMEM_DEFINE_LOCAL_ARRAY (sha1_context_p, 1, jerry_sha1_context);
jerry_sha1_init (sha1_context_p);
jerry_sha1_update (sha1_context_p, source1_p, source1_length);
jerry_sha1_update (sha1_context_p, source2_p, source2_length);
jerry_sha1_finish (sha1_context_p, destination_p);
JMEM_FINALIZE_LOCAL_ARRAY (sha1_context_p);
} /* jerry_debugger_compute_sha1 */
#endif /* JERRY_DEBUGGER */

View File

@ -19,6 +19,10 @@
#include "ecma-helpers.h"
#include "ecma-lcache.h"
#include "ecma-property-hashmap.h"
#ifdef JERRY_DEBUGGER
#include "jcontext.h"
#include "jerry-debugger.h"
#endif /* JERRY_DEBUGGER */
#include "jrt-bit-fields.h"
#include "byte-code.h"
#include "re-compiler.h"
@ -1470,6 +1474,37 @@ ecma_bytecode_deref (ecma_compiled_code_t *bytecode_p) /**< byte code pointer */
ecma_bytecode_deref (bytecode_literal_p);
}
}
#ifdef JERRY_DEBUGGER
if (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
{
if (jerry_debugger_send_function_cp (JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP, bytecode_p))
{
/* Delay the byte code free until the debugger client is notified.
* If the connection is aborted the pointer is still freed by
* jerry_debugger_close_connection(). */
jerry_debugger_byte_code_free_t *byte_code_free_p = (jerry_debugger_byte_code_free_t *) bytecode_p;
jmem_cpointer_t byte_code_free_head = JERRY_CONTEXT (debugger_byte_code_free_head);
byte_code_free_p->prev_cp = ECMA_NULL_POINTER;
byte_code_free_p->next_cp = byte_code_free_head;
JMEM_CP_SET_NON_NULL_POINTER (JERRY_CONTEXT (debugger_byte_code_free_head),
byte_code_free_p);
if (byte_code_free_head != 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_head);
next_byte_code_free_p->prev_cp = JERRY_CONTEXT (debugger_byte_code_free_head);
}
return;
}
}
#endif /* JERRY_DEBUGGER */
}
else
{

View File

@ -20,6 +20,7 @@
#define JCONTEXT_H
#include "ecma-builtins.h"
#include "jerry-debugger.h"
#include "jmem.h"
#include "re-bytecode.h"
#include "vm-defines.h"
@ -81,6 +82,17 @@ typedef struct
uint8_t re_cache_idx; /**< evicted item index when regex cache is full (round-robin) */
#endif /* !CONFIG_DISABLE_REGEXP_BUILTIN */
#ifdef JERRY_DEBUGGER
uint32_t debugger_message_delay; /**< call receive message when reaches zero */
uint8_t debugger_send_buffer[JERRY_DEBUGGER_MAX_BUFFER_SIZE]; /**< buffer for sending messages */
uint8_t debugger_receive_buffer[JERRY_DEBUGGER_MAX_BUFFER_SIZE]; /**< buffer for receiving messages */
jmem_cpointer_t debugger_byte_code_free_head; /**< head of byte code free linked list */
uint32_t debugger_receive_buffer_offset; /**< receive buffer offset */
int debugger_connection; /**< hold the file descriptor for socket communication */
bool debugger_stop_exec; /**< stop at the next breakpoint regardless it is enabled */
vm_frame_ctx_t *debugger_stop_context; /**< stop only if the current context is equal to this context */
#endif /* JERRY_DEBUGGER */
#ifdef JMEM_STATS
jmem_heap_stats_t jmem_heap_stats; /**< heap's memory usage statistics */
jmem_pools_stats_t jmem_pools_stats; /**< pools' memory usage statistics */

View File

@ -50,6 +50,7 @@ typedef enum
JERRY_INIT_SHOW_REGEXP_OPCODES = (1u << 1), /**< dump regexp byte-code to log after compilation */
JERRY_INIT_MEM_STATS = (1u << 2), /**< dump memory statistics */
JERRY_INIT_MEM_STATS_SEPARATE = (1u << 3), /**< dump memory statistics and reset peak values after parse */
JERRY_INIT_DEBUGGER = (1u << 4), /**< enable all features required by debugging */
} jerry_init_flag_t;
/**
@ -182,6 +183,8 @@ void jerry_gc (void);
*/
bool jerry_run_simple (const jerry_char_t *script_source_p, size_t script_source_size, jerry_init_flag_t flags);
jerry_value_t jerry_parse (const jerry_char_t *source_p, size_t source_size, bool is_strict);
jerry_value_t jerry_parse_named_resource (const jerry_char_t *name_p, size_t name_length,
const jerry_char_t *source_p, size_t source_size, bool is_strict);
jerry_value_t jerry_run (const jerry_value_t func_val);
jerry_value_t jerry_eval (const jerry_char_t *source_p, size_t source_size, bool is_strict);

View File

@ -31,6 +31,7 @@
#include "ecma-objects-general.h"
#include "jcontext.h"
#include "jerry-api.h"
#include "jerry-debugger.h"
#include "js-parser.h"
#include "re-compiler.h"
@ -154,6 +155,13 @@ jerry_init (jerry_init_flag_t flags) /**< combination of Jerry flags */
jmem_init ();
ecma_init ();
#ifdef JERRY_DEBUGGER
if (flags & JERRY_INIT_DEBUGGER)
{
jerry_debugger_accept_connection ();
}
#endif /* JERRY_DEBUGGER */
} /* jerry_init */
/**
@ -165,6 +173,14 @@ jerry_cleanup (void)
jerry_assert_api_available ();
ecma_finalize ();
#ifdef JERRY_DEBUGGER
if (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
{
jerry_debugger_close_connection ();
}
#endif /* JERRY_DEBUGGER */
jmem_finalize ();
jerry_make_api_unavailable ();
} /* jerry_cleanup */
@ -296,6 +312,35 @@ jerry_parse (const jerry_char_t *source_p, /**< script source */
#endif /* JERRY_JS_PARSER */
} /* jerry_parse */
/**
* Parse script and construct an ECMAScript function. The lexical
* environment is set to the global lexical environment. The name
* (usually a file name) is also passed to this function which is
* used by the debugger to find the source code.
*
* @return function object value - if script was parsed successfully,
* thrown error - otherwise
*/
jerry_value_t
jerry_parse_named_resource (const jerry_char_t *name_p, /**< name (usually a file name) */
size_t name_length, /**< length of name */
const jerry_char_t *source_p, /**< script source */
size_t source_size, /**< script source size */
bool is_strict) /**< strict mode */
{
#ifdef JERRY_DEBUGGER
if (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
{
jerry_debugger_send_string (JERRY_DEBUGGER_RESOURCE_NAME, name_p, name_length);
}
#else /* JERRY_DEBUGGER */
JERRY_UNUSED (name_p);
JERRY_UNUSED (name_length);
#endif /* JERRY_DEBUGGER */
return jerry_parse (source_p, source_size, is_strict);
} /* jerry_parse_named_resource */
/**
* Run an EcmaScript function created by jerry_parse.
*

View File

@ -16,6 +16,8 @@
#ifndef BYTE_CODE_H
#define BYTE_CODE_H
#include "ecma-globals.h"
/** \addtogroup parser Parser
* @{
*
@ -204,6 +206,20 @@
/* PARSER_TRY_CONTEXT_STACK_ALLOCATION must be <= 3 */
#define PARSER_TRY_CONTEXT_STACK_ALLOCATION 2
#ifdef JERRY_DEBUGGER
#define CBC_BREAKPOINT_OPCODES \
CBC_OPCODE (CBC_BREAKPOINT_ENABLED, CBC_NO_FLAG, 0, \
VM_OC_BREAKPOINT_ENABLED) \
CBC_OPCODE (CBC_BREAKPOINT_DISABLED, CBC_NO_FLAG, 0, \
VM_OC_BREAKPOINT_DISABLED) \
#else /* !JERRY_DEBUGGER */
#define CBC_BREAKPOINT_OPCODES
#endif /* JERRY_DEBUGGER */
/**
* Opcode definitions.
*/
@ -315,6 +331,7 @@
VM_OC_RET) \
CBC_OPCODE (CBC_RETURN_WITH_LITERAL, CBC_HAS_LITERAL_ARG, 0, \
VM_OC_RET | VM_OC_GET_LITERAL) \
CBC_BREAKPOINT_OPCODES \
\
/* Unary opcodes. */ \
CBC_UNARY_OPERATION (CBC_PLUS, \

View File

@ -19,6 +19,7 @@
#include "common.h"
#include "byte-code.h"
#include "jerry-debugger.h"
#include "js-parser.h"
#include "js-parser-limits.h"
#include "js-lexer.h"
@ -190,6 +191,16 @@ typedef struct parser_branch_node_t
parser_branch_t branch; /**< branch */
} parser_branch_node_t;
#ifdef JERRY_DEBUGGER
/**
* Extra information for each breakpoint.
*/
typedef struct
{
uint32_t value; /**< line or offset of the breakpoint */
} parser_breakpoint_info_t;
#endif /* JERRY_DEBUGGER */
/**
* Those members of a context which needs
* to be saved when a sub-function is parsed.
@ -270,6 +281,12 @@ typedef struct
int is_show_opcodes; /**< show opcodes */
uint32_t total_byte_code_size; /**< total byte code size */
#endif /* PARSER_DUMP_BYTE_CODE */
#ifdef JERRY_DEBUGGER
parser_breakpoint_info_t breakpoint_info[JERRY_DEBUGGER_MAX_SIZE (parser_list_t)]; /**< extra data for breakpoints */
uint16_t breakpoint_info_count; /**< current breakpoint index */
parser_line_counter_t last_breakpoint_line; /**< last line where breakpoint was inserted */
#endif /* JERRY_DEBUGGER */
} parser_context_t;
/**
@ -428,6 +445,15 @@ ecma_compiled_code_t *parser_parse_function (parser_context_t *context_p, uint32
void parser_raise_error (parser_context_t *context_p, parser_error_t error);
/* Debug functions. */
#ifdef JERRY_DEBUGGER
void parser_append_breakpoint_info (parser_context_t *context_p, jerry_debugger_header_type_t type, uint32_t value);
void parser_send_breakpoints (parser_context_t *context_p, jerry_debugger_header_type_t type);
#endif /* JERRY_DEBUGGER */
/**
* @}
* @}

View File

@ -17,6 +17,10 @@
#ifdef JERRY_JS_PARSER
#ifdef JERRY_DEBUGGER
#include "jcontext.h"
#endif /*JERRY_DEBUGGER */
/** \addtogroup parser Parser
* @{
*
@ -310,6 +314,10 @@ parser_parse_var_statement (parser_context_t *context_p) /**< context */
JERRY_ASSERT (context_p->token.type == LEXER_LITERAL
&& context_p->token.lit_location.type == LEXER_IDENT_LITERAL);
#ifdef JERRY_DEBUGGER
parser_line_counter_t ident_line_counter = context_p->line;
#endif /* JERRY_DEBUGGER */
context_p->lit_object.literal_p->status_flags |= LEXER_FLAG_VAR;
parser_emit_cbc_literal_from_token (context_p, CBC_PUSH_LITERAL);
@ -318,6 +326,30 @@ parser_parse_var_statement (parser_context_t *context_p) /**< context */
if (context_p->token.type == LEXER_ASSIGN)
{
#ifdef JERRY_DEBUGGER
if (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
{
if (ident_line_counter != context_p->last_breakpoint_line)
{
JERRY_DEBUG_MSG ("Insert var breakpoint: %d (%d)\n", ident_line_counter, context_p->last_breakpoint_line);
JERRY_ASSERT (context_p->last_cbc_opcode == CBC_PUSH_LITERAL);
cbc_argument_t last_cbc = context_p->last_cbc;
context_p->last_cbc_opcode = PARSER_CBC_UNAVAILABLE;
parser_emit_cbc (context_p, CBC_BREAKPOINT_DISABLED);
parser_flush_cbc (context_p);
parser_append_breakpoint_info (context_p, JERRY_DEBUGGER_BREAKPOINT_LIST, ident_line_counter);
context_p->last_cbc_opcode = CBC_PUSH_LITERAL;
context_p->last_cbc = last_cbc;
context_p->last_breakpoint_line = ident_line_counter;
}
}
#endif /* JERRY_DEBUGGER */
parser_parse_expression (context_p,
PARSE_EXPR_STATEMENT | PARSE_EXPR_NO_COMMA | PARSE_EXPR_HAS_LITERAL);
}
@ -369,6 +401,14 @@ parser_parse_function_statement (parser_context_t *context_p) /**< context */
status_flags |= PARSER_HAS_NON_STRICT_ARG;
}
#ifdef JERRY_DEBUGGER
if (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
{
jerry_debugger_send_function_name ((jerry_char_t *) name_p->u.char_p,
name_p->prop.length);
}
#endif /* JERRY_DEBUGGER */
if (name_p->status_flags & LEXER_FLAG_INITIALIZED)
{
if (!(name_p->status_flags & (LEXER_FLAG_FUNCTION_NAME | LEXER_FLAG_FUNCTION_ARGUMENT)))
@ -1567,6 +1607,14 @@ parser_parse_statements (parser_context_t *context_p) /**< context */
parser_stack_push_uint8 (context_p, PARSER_STATEMENT_START);
parser_stack_iterator_init (context_p, &context_p->last_statement);
#ifdef JERRY_DEBUGGER
/* Set lexical enviroment for the debugger. */
if (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
{
context_p->status_flags |= PARSER_LEXICAL_ENV_NEEDED;
}
#endif /* JERRY_DEBUGGER */
while (context_p->token.type == LEXER_LITERAL
&& context_p->token.lit_location.type == LEXER_STRING_LITERAL)
{
@ -1654,6 +1702,28 @@ parser_parse_statements (parser_context_t *context_p) /**< context */
JERRY_ASSERT (context_p->stack_depth == context_p->context_stack_depth);
#endif /* !JERRY_NDEBUG */
#ifdef JERRY_DEBUGGER
if (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
{
if (context_p->line != context_p->last_breakpoint_line
&& context_p->token.type != LEXER_SEMICOLON
&& context_p->token.type != LEXER_LEFT_BRACE
&& context_p->token.type != LEXER_RIGHT_BRACE
&& context_p->token.type != LEXER_KEYW_VAR
&& context_p->token.type != LEXER_KEYW_FUNCTION
&& context_p->token.type != LEXER_KEYW_CASE
&& context_p->token.type != LEXER_KEYW_DEFAULT)
{
parser_emit_cbc (context_p, CBC_BREAKPOINT_DISABLED);
parser_flush_cbc (context_p);
parser_append_breakpoint_info (context_p, JERRY_DEBUGGER_BREAKPOINT_LIST, context_p->line);
context_p->last_breakpoint_line = context_p->line;
}
}
#endif /* JERRY_DEBUGGER */
switch (context_p->token.type)
{
case LEXER_SEMICOLON:

View File

@ -17,6 +17,7 @@
#include "ecma-helpers.h"
#include "ecma-literal-storage.h"
#include "jcontext.h"
#include "jerry-debugger.h"
#include "js-parser-internal.h"
#ifdef JERRY_JS_PARSER
@ -1271,6 +1272,15 @@ parser_post_processing (parser_context_t *context_p) /**< context */
JERRY_ASSERT (context_p->literal_count <= PARSER_MAXIMUM_NUMBER_OF_LITERALS);
#ifdef JERRY_DEBUGGER
if ((JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
&& context_p->breakpoint_info_count > 0)
{
parser_send_breakpoints (context_p, JERRY_DEBUGGER_BREAKPOINT_LIST);
JERRY_ASSERT (context_p->breakpoint_info_count == 0);
}
#endif /* JERRY_DEBUGGER */
initializers_length = parser_compute_indicies (context_p,
&ident_end,
&uninitialized_var_end,
@ -1573,6 +1583,14 @@ parser_post_processing (parser_context_t *context_p) /**< context */
PARSER_NEXT_BYTE_UPDATE (page_p, offset, real_offset);
flags = cbc_flags[opcode];
#ifdef JERRY_DEBUGGER
if (opcode == CBC_BREAKPOINT_DISABLED)
{
uint32_t offset = (uint32_t) (((uint8_t *) dst_p) - ((uint8_t *) compiled_code_p) - 1);
parser_append_breakpoint_info (context_p, JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST, offset);
}
#endif /* JERRY_DEBUGGER */
if (opcode == CBC_EXT_OPCODE)
{
cbc_ext_opcode_t ext_opcode;
@ -1668,6 +1686,15 @@ parser_post_processing (parser_context_t *context_p) /**< context */
}
}
#ifdef JERRY_DEBUGGER
if ((JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
&& context_p->breakpoint_info_count > 0)
{
parser_send_breakpoints (context_p, JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST);
JERRY_ASSERT (context_p->breakpoint_info_count == 0);
}
#endif /* JERRY_DEBUGGER */
if (!(context_p->status_flags & PARSER_NO_END_LABEL))
{
*dst_p++ = CBC_RETURN_WITH_BLOCK;
@ -1770,6 +1797,13 @@ parser_post_processing (parser_context_t *context_p) /**< context */
compiled_code_p);
}
#ifdef JERRY_DEBUGGER
if (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
{
jerry_debugger_send_function_cp (JERRY_DEBUGGER_BYTE_CODE_CP, compiled_code_p);
}
#endif /* JERRY_DEBUGGER */
return compiled_code_p;
} /* parser_post_processing */
@ -1861,6 +1895,11 @@ parser_parse_source (const uint8_t *source_p, /**< valid UTF-8 source code */
}
#endif /* PARSER_DUMP_BYTE_CODE */
#ifdef JERRY_DEBUGGER
context.breakpoint_info_count = 0;
context.last_breakpoint_line = 0;
#endif /* JERRY_DEBUGGER */
PARSER_TRY (context.try_buffer)
{
/* Pushing a dummy value ensures the stack is never empty.
@ -1948,6 +1987,15 @@ parser_parse_function (parser_context_t *context_p, /**< context */
JERRY_ASSERT (context_p->last_cbc_opcode == PARSER_CBC_UNAVAILABLE);
#ifdef JERRY_DEBUGGER
if ((JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
&& context_p->breakpoint_info_count > 0)
{
parser_send_breakpoints (context_p, JERRY_DEBUGGER_BREAKPOINT_LIST);
context_p->breakpoint_info_count = 0;
}
#endif /* JERRY_DEBUGGER */
/* Save private part of the context. */
saved_context.status_flags = context_p->status_flags;
@ -1998,6 +2046,21 @@ parser_parse_function (parser_context_t *context_p, /**< context */
}
#endif /* PARSER_DUMP_BYTE_CODE */
#ifdef JERRY_DEBUGGER
if (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
{
if (context_p->line != context_p->last_breakpoint_line)
{
parser_emit_cbc (context_p, CBC_BREAKPOINT_DISABLED);
parser_flush_cbc (context_p);
parser_append_breakpoint_info (context_p, JERRY_DEBUGGER_BREAKPOINT_LIST, context_p->line);
context_p->last_breakpoint_line = context_p->line;
}
}
#endif /* JERRY_DEBUGGER */
lexer_next_token (context_p);
if (context_p->status_flags & PARSER_IS_FUNC_EXPRESSION
@ -2008,6 +2071,14 @@ parser_parse_function (parser_context_t *context_p, /**< context */
&context_p->token.lit_location,
LEXER_IDENT_LITERAL);
#ifdef JERRY_DEBUGGER
if (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
{
jerry_debugger_send_function_name ((jerry_char_t *) context_p->lit_object.literal_p->u.char_p,
context_p->lit_object.literal_p->prop.length);
}
#endif /* JERRY_DEBUGGER */
/* The arguments object is created later than the binding to the
* function expression name, so there is no need to assign special flags. */
if (context_p->lit_object.type != LEXER_LITERAL_OBJECT_ARGUMENTS)
@ -2025,6 +2096,13 @@ parser_parse_function (parser_context_t *context_p, /**< context */
lexer_next_token (context_p);
}
#ifdef JERRY_DEBUGGER
if (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
{
jerry_debugger_send_type (JERRY_DEBUGGER_PARSE_FUNCTION);
}
#endif /* JERRY_DEBUGGER */
if (context_p->token.type != LEXER_LEFT_PAREN)
{
parser_raise_error (context_p, PARSER_ERR_ARGUMENT_LIST_EXPECTED);
@ -2223,6 +2301,46 @@ parser_raise_error (parser_context_t *context_p, /**< context */
JERRY_ASSERT (0);
} /* parser_raise_error */
#ifdef JERRY_DEBUGGER
/**
* Append a breakpoint info.
*/
void
parser_append_breakpoint_info (parser_context_t *context_p, /**< context */
jerry_debugger_header_type_t type, /**< message type */
uint32_t value) /**< line or offset of the breakpoint */
{
JERRY_ASSERT (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER);
if (context_p->breakpoint_info_count >= JERRY_DEBUGGER_MAX_SIZE (parser_list_t))
{
parser_send_breakpoints (context_p, type);
}
context_p->breakpoint_info[context_p->breakpoint_info_count].value = value;
context_p->breakpoint_info_count = (uint16_t) (context_p->breakpoint_info_count + 1);
} /* parser_append_breakpoint_info */
/**
* Send current breakpoint list.
*/
void
parser_send_breakpoints (parser_context_t *context_p, /**< context */
jerry_debugger_header_type_t type) /**< message type */
{
JERRY_ASSERT (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER);
JERRY_ASSERT (context_p->breakpoint_info_count > 0);
jerry_debugger_send_data (type,
context_p->breakpoint_info,
context_p->breakpoint_info_count * sizeof (parser_breakpoint_info_t));
context_p->breakpoint_info_count = 0;
} /* parser_send_breakpoints */
#endif /* JERRY_DEBUGGER */
#define PARSE_ERR_POS_START " [line: "
#define PARSE_ERR_POS_START_SIZE ((uint32_t) sizeof (PARSE_ERR_POS_START) - 1)
#define PARSE_ERR_POS_MIDDLE ", column: "
@ -2253,6 +2371,13 @@ parser_parse_script (const uint8_t *source_p, /**< source code */
if (!*bytecode_data_p)
{
#ifdef JERRY_DEBUGGER
if (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER)
{
jerry_debugger_send_type (JERRY_DEBUGGER_PARSE_ERROR);
}
#endif /* JERRY_DEBUGGER */
if (parser_error.error == PARSER_ERR_OUT_OF_MEMORY)
{
/* It is unlikely that memory can be allocated in an out-of-memory

View File

@ -39,7 +39,7 @@ typedef const uint8_t *vm_instr_counter_t;
/**
* Context of interpreter, related to a JS stack frame
*/
typedef struct
typedef struct vm_frame_ctx_t
{
const ecma_compiled_code_t *bytecode_header_p; /**< currently executed byte-code data */
uint8_t *byte_code_p; /**< current byte code pointer */
@ -48,6 +48,7 @@ typedef struct
ecma_value_t *stack_top_p; /**< stack top pointer */
jmem_cpointer_t *literal_start_p; /**< literal list start pointer */
ecma_object_t *lex_env_p; /**< current lexical environment */
struct vm_frame_ctx_t *prev_context_p; /**< previous context */
ecma_value_t this_binding; /**< this binding */
ecma_value_t call_block_result; /**< preserve block result during a call */
uint16_t context_depth; /**< current context depth */

View File

@ -2305,6 +2305,59 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */
JERRY_ASSERT (frame_ctx_p->registers_p + register_end + frame_ctx_p->context_depth == stack_top_p);
continue;
}
case VM_OC_BREAKPOINT_ENABLED:
{
#ifdef JERRY_DEBUGGER
if (!(JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER))
{
continue;
}
frame_ctx_p->byte_code_p = byte_code_start_p;
jerry_debugger_breakpoint_hit ();
#endif /* JERRY_DEBUGGER */
continue;
}
case VM_OC_BREAKPOINT_DISABLED:
{
#ifdef JERRY_DEBUGGER
if (!(JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER))
{
continue;
}
frame_ctx_p->byte_code_p = byte_code_start_p;
if (JERRY_CONTEXT (debugger_stop_exec)
&& (JERRY_CONTEXT (debugger_stop_context) == NULL
|| JERRY_CONTEXT (debugger_stop_context) == JERRY_CONTEXT (vm_top_context_p)))
{
jerry_debugger_breakpoint_hit ();
continue;
}
if (JERRY_CONTEXT (debugger_message_delay) > 0)
{
JERRY_CONTEXT (debugger_message_delay)--;
continue;
}
JERRY_CONTEXT (debugger_message_delay) = JERRY_DEBUGGER_MESSAGE_FREQUENCY;
if (jerry_debugger_receive ())
{
continue;
}
if (JERRY_CONTEXT (debugger_stop_exec))
{
JERRY_ASSERT (JERRY_CONTEXT (debugger_stop_context) == NULL);
jerry_debugger_breakpoint_hit ();
}
#endif /* JERRY_DEBUGGER */
continue;
}
default:
{
JERRY_UNREACHABLE ();
@ -2532,7 +2585,6 @@ vm_execute (vm_frame_ctx_t *frame_ctx_p, /**< frame context */
{
const ecma_compiled_code_t *bytecode_header_p = frame_ctx_p->bytecode_header_p;
ecma_value_t completion_value;
vm_frame_ctx_t *prev_context_p;
uint16_t argument_end;
uint16_t register_end;
@ -2577,7 +2629,6 @@ vm_execute (vm_frame_ctx_t *frame_ctx_p, /**< frame context */
JERRY_CONTEXT (is_direct_eval_form_call) = false;
prev_context_p = JERRY_CONTEXT (vm_top_context_p);
JERRY_CONTEXT (vm_top_context_p) = frame_ctx_p;
vm_init_loop (frame_ctx_p);
@ -2608,7 +2659,16 @@ vm_execute (vm_frame_ctx_t *frame_ctx_p, /**< frame context */
ecma_fast_free_value (frame_ctx_p->registers_p[i]);
}
JERRY_CONTEXT (vm_top_context_p) = prev_context_p;
#ifdef JERRY_DEBUGGER
if (JERRY_CONTEXT (debugger_stop_context) == JERRY_CONTEXT (vm_top_context_p))
{
/* The engine will stop when the next breakpoint is reached. */
JERRY_ASSERT (JERRY_CONTEXT (debugger_stop_exec));
JERRY_CONTEXT (debugger_stop_context) = NULL;
}
#endif /* JERRY_DEBUGGER */
JERRY_CONTEXT (vm_top_context_p) = frame_ctx_p->prev_context_p;
return completion_value;
} /* vm_execute */
@ -2654,6 +2714,7 @@ vm_run (const ecma_compiled_code_t *bytecode_header_p, /**< byte-code data heade
frame_ctx.byte_code_p = (uint8_t *) literal_p;
frame_ctx.byte_code_start_p = (uint8_t *) literal_p;
frame_ctx.lex_env_p = lex_env_p;
frame_ctx.prev_context_p = JERRY_CONTEXT (vm_top_context_p);
frame_ctx.this_binding = this_binding_value;
frame_ctx.context_depth = 0;
frame_ctx.is_eval_code = is_eval_code;

View File

@ -203,6 +203,8 @@ typedef enum
VM_OC_FINALLY, /**< finally */
VM_OC_CONTEXT_END, /**< context end */
VM_OC_JUMP_AND_EXIT_CONTEXT, /**< jump and exit context */
VM_OC_BREAKPOINT_ENABLED, /**< enabled breakpoint for debugger */
VM_OC_BREAKPOINT_DISABLED, /**< disabled breakpoint for debugger */
} vm_oc_types;
/**

View File

@ -0,0 +1,949 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>
JerryScript HTML (WebSocket) Debugger Client
</title>
<style>
body {
text-align: center;
}
textarea {
margin-bottom: 30px;
}
input {
margin-top: 10px;
width: 657px;
}
</style>
</head>
<body>
<h2>JerryScript HTML (WebSocket) Debugger Client</h2>
<textarea id="log" rows="20" cols="80"></textarea><br>
Getting help: type 'help' in the command line below.<br>
<input id="command" type="text" onkeypress="debuggerCommand(event); return true;">
<script>
var JERRY_DEBUGGER_CONFIGURATION = 1;
var JERRY_DEBUGGER_PARSE_ERROR = 2;
var JERRY_DEBUGGER_BYTE_CODE_CP = 3;
var JERRY_DEBUGGER_PARSE_FUNCTION = 4;
var JERRY_DEBUGGER_BREAKPOINT_LIST = 5;
var JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST = 6;
var JERRY_DEBUGGER_RESOURCE_NAME = 7;
var JERRY_DEBUGGER_RESOURCE_NAME_END = 8;
var JERRY_DEBUGGER_FUNCTION_NAME = 9;
var JERRY_DEBUGGER_FUNCTION_NAME_END = 10;
var JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP = 11;
var JERRY_DEBUGGER_BREAKPOINT_HIT = 12;
var JERRY_DEBUGGER_BACKTRACE = 13;
var JERRY_DEBUGGER_BACKTRACE_END = 14;
var JERRY_DEBUGGER_FREE_BYTE_CODE_CP = 1;
var JERRY_DEBUGGER_UPDATE_BREAKPOINT = 2;
var JERRY_DEBUGGER_STOP = 3;
var JERRY_DEBUGGER_CONTINUE = 4;
var JERRY_DEBUGGER_STEP = 5;
var JERRY_DEBUGGER_NEXT = 6;
var JERRY_DEBUGGER_GET_BACKTRACE = 7;
var textBox = document.getElementById("log");
var commandBox = document.getElementById("command");
var socket = null;
textBox.value = ""
commandBox.value = "connect localhost"
function appendLog(str)
{
textBox.value += str + "\n";
}
var debuggerObj = null;
function DebuggerClient(ipAddr)
{
appendLog("ws://" + ipAddr + ":5001/jerry-debugger");
var parseObj = null;
var maxMessageSize = 0;
var cpointerSize = 0;
var littleEndian = true;
var functions = { };
var lineList = new Multimap();
var activeBreakpoints = { };
var nextBreakpointIndex = 1;
var backtraceFrame = 0;
function assert(expr)
{
if (!expr)
{
throw new Error("Assertion failed.");
}
}
/* Concat the two arrays. The first byte (opcode) of nextArray is ignored. */
function concatUint8Arrays(baseArray, nextArray)
{
if (nextArray.byteLength <= 1)
{
/* Nothing to append. */
return baseArray;
}
if (!baseArray)
{
/* Cut the first byte (opcode). */
return nextArray.slice(1);
}
var baseLength = baseArray.byteLength;
var nextLength = nextArray.byteLength - 1;
var result = new Uint8Array(baseArray.byteLength + nextArray.byteLength);
result.set(nextArray, baseArray.byteLength - 1);
/* This set overwrites the opcode. */
result.set(baseArray);
return result;
}
function cesu8ToString(array)
{
if (!array)
{
return "";
}
var length = array.byteLength;
var i = 0;
var result = "";
while (i < length)
{
var chr = array[i];
++i;
if (chr >= 0x7f)
{
if (chr & 0x20)
{
/* Three byte long character. */
chr = ((chr & 0xf) << 12) | ((array[i] & 0x3f) << 6) | (array[i + 1] & 0x3f);
i += 2;
}
else
{
/* Two byte long character. */
chr = ((chr & 0x1f) << 6) | (array[i] & 0x3f);
++i;
}
}
result += String.fromCharCode(chr);
}
return result;
}
function breakpointToString(breakpoint)
{
var name = breakpoint.func.name;
var result = breakpoint.func.resource;
if (!result)
{
result = "<unknown>";
}
result += ":" + breakpoint.line;
if (breakpoint.func.name)
{
result += " (in " + breakpoint.func.name + ")";
}
return result;
}
function Multimap()
{
/* Each item is an array of items. */
var map = { };
this.get = function(key)
{
var item = map[key];
return item ? item : [ ];
}
this.insert = function(key, value)
{
var item = map[key];
if (item)
{
item.push(value);
return;
}
map[key] = [ value ];
}
this.delete = function(key, value)
{
var array = map[key];
assert(array);
var newLength = array.length - 1;
var i = array.indexOf(value);
assert(i != -1);
array.splice(i, 1);
array.length = newLength;
}
}
var socket = new WebSocket("ws://localhost:5001/jerry-debugger");
socket.binaryType = 'arraybuffer';
function abortConnection(message)
{
assert(socket && debuggerObj);
socket.close();
socket = null;
debuggerObj = null;
appendLog("Abort connection: " + message);
throw new Error(message);
}
socket.onerror = function(event)
{
if (socket)
{
socket = null;
debuggerObj = null;
appendLog("Connection closed.");
}
}
socket.onclose = socket.onerror;
socket.onopen = function(event)
{
appendLog("Connection created.");
}
function getFormatSize(format)
{
var length = 0;
for (var i = 0; i < format.length; i++)
{
if (format[i] == "B")
{
length++;
continue;
}
if (format[i] == "C")
{
length += cpointerSize;
continue;
}
assert(format[i] == "I")
length += 4;
}
return length;
}
function decodeMessage(format, message, offset)
{
/* Format: B=byte I=int32 C=cpointer.
* Returns an array of decoded numbers. */
var result = []
var value;
if (!offset)
{
offset = 0;
}
if (offset + getFormatSize(format) > message.byteLength)
{
abortConnection("received message too short.");
}
for (var i = 0; i < format.length; i++)
{
if (format[i] == "B")
{
result.push(message[offset])
offset++;
continue;
}
if (format[i] == "C" && cpointerSize == 2)
{
if (littleEndian)
{
value = message[offset] | (message[offset + 1] << 8);
}
else
{
value = (message[offset] << 8) | message[offset + 1];
}
result.push(value);
offset += 2;
continue;
}
assert(format[i] == "I" || (format[i] == "C" && cpointerSize == 4));
if (littleEndian)
{
value = (message[offset] | (message[offset + 1] << 8)
| (message[offset + 2] << 16) | (message[offset + 3] << 24));
}
else
{
value = ((message[offset] << 24) | (message[offset + 1] << 16)
| (message[offset + 2] << 8) | message[offset + 3] << 24);
}
result.push(value);
offset += 4;
}
return result;
}
function encodeMessage(format, values)
{
/* Format: B=byte I=int32 C=cpointer.
* Sends a message after the encoding is completed. */
var length = getFormatSize(format);
var message = new Uint8Array(length);
var offset = 0;
for (var i = 0; i < format.length; i++)
{
var value = values[i];
if (format[i] == "B")
{
message[offset] = value;
offset++;
continue;
}
if (format[i] == "C" && cpointerSize == 2)
{
if (littleEndian)
{
message[offset] = value & 0xff;
message[offset + 1] = (value >> 8) & 0xff;
}
else
{
message[offset] = (value >> 8) & 0xff;
message[offset + 1] = value & 0xff;
}
offset += 2;
continue;
}
if (littleEndian)
{
message[offset] = value & 0xff;
message[offset + 1] = (value >> 8) & 0xff;
message[offset + 2] = (value >> 16) & 0xff;
message[offset + 3] = (value >> 24) & 0xff;
}
else
{
message[offset] = (value >> 24) & 0xff;
message[offset + 1] = (value >> 16) & 0xff;
message[offset + 2] = (value >> 8) & 0xff;
message[offset + 3] = value & 0xff;
}
offset += 4;
}
socket.send(message);
}
this.encodeMessage = encodeMessage;
function ParseSource()
{
var resourceName = null;
var functionName = null;
var stack = [ { name: '', lines: [], offsets: [] } ];
var newFunctions = [ ];
this.receive = function(message)
{
switch (message[0])
{
case JERRY_DEBUGGER_PARSE_ERROR:
{
/* Parse error occured in JerryScript. */
parseObj = null;
return;
}
case JERRY_DEBUGGER_RESOURCE_NAME:
case JERRY_DEBUGGER_RESOURCE_NAME_END:
{
if ((typeof resourceName) == "string")
{
abortConnection("unexpected message.");
}
resourceName = concatUint8Arrays(resourceName, message);
if (message[0] == JERRY_DEBUGGER_RESOURCE_NAME_END)
{
resourceName = cesu8ToString(resourceName);
}
return;
}
case JERRY_DEBUGGER_FUNCTION_NAME:
case JERRY_DEBUGGER_FUNCTION_NAME_END:
{
functionName = concatUint8Arrays(functionName, message);
return;
}
case JERRY_DEBUGGER_PARSE_FUNCTION:
{
if (resourceName == null)
{
resourceName = "";
}
else if ((typeof resourceName) != "string")
{
abortConnection("unexpected message.");
}
stack.push({ name: cesu8ToString(functionName), resource: resourceName, lines: [], offsets: [] });
functionName = null;
return;
}
case JERRY_DEBUGGER_BREAKPOINT_LIST:
case JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST:
{
var array;
if (message.byteLength < 1 + 4)
{
abortConnection("message too short.");
}
if (message[0] == JERRY_DEBUGGER_BREAKPOINT_LIST)
{
array = stack[stack.length - 1].lines;
}
else
{
array = stack[stack.length - 1].offsets;
}
for (var i = 1; i < message.byteLength; i += 4)
{
array.push(decodeMessage("I", message, i)[0]);
}
return;
}
case JERRY_DEBUGGER_BYTE_CODE_CP:
{
var func = stack.pop();
func.byte_code_cp = decodeMessage("C", message, 1)[0];
lines = {}
offsets = {}
func.firstLine = (func.lines.length > 0) ? func.lines[0] : -1;
for (var i = 0; i < func.lines.length; i++)
{
var breakpoint = { line: func.lines[i], offset: func.offsets[i], func: func, activeIndex: -1 };
lines[breakpoint.line] = breakpoint;
offsets[breakpoint.offset] = breakpoint;
}
func.lines = lines;
func.offsets = offsets;
newFunctions.push(func);
if (stack.length > 0)
{
return;
}
func.resource = resourceName;
break;
}
default:
{
abortConnection("unexpected message.");
return;
}
}
for (var i = 0; i < newFunctions.length; i++)
{
var func = newFunctions[i];
functions[func.byte_code_cp] = func
for (var j in func.lines)
{
lineList.insert(j, func);
}
}
parseObj = null;
}
}
socket.onmessage = function(event)
{
var message = new Uint8Array(event.data);
if (message.byteLength < 1)
{
abortConnection("message too short.");
}
if (cpointerSize == 0)
{
if (message[0] != JERRY_DEBUGGER_CONFIGURATION
|| message.byteLength != 4)
{
abortConnection("the first message must be configuration.");
}
maxMessageSize = message[1]
cpointerSize = message[2]
littleEndian = (message[3] != 0);
if (cpointerSize != 2 && cpointerSize != 4)
{
abortConnection("compressed pointer must be 2 or 4 byte long.");
}
config = false;
return;
}
if (parseObj)
{
parseObj.receive(message)
return;
}
switch (message[0])
{
case JERRY_DEBUGGER_PARSE_ERROR:
case JERRY_DEBUGGER_BYTE_CODE_CP:
case JERRY_DEBUGGER_PARSE_FUNCTION:
case JERRY_DEBUGGER_BREAKPOINT_LIST:
case JERRY_DEBUGGER_RESOURCE_NAME:
case JERRY_DEBUGGER_RESOURCE_NAME_END:
case JERRY_DEBUGGER_FUNCTION_NAME:
case JERRY_DEBUGGER_FUNCTION_NAME_END:
{
parseObj = new ParseSource()
parseObj.receive(message)
return;
}
case JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP:
{
var byte_code_cp = decodeMessage("C", message, 1)[0];
var func = functions[byte_code_cp];
for (var i in func.lines)
{
lineList.delete(i, func);
var breakpoint = func.lines[i];
assert(i == breakpoint.line);
if (breakpoint.activeIndex >= 0)
{
delete activeBreakpoints[breakpoint.activeIndex];
}
}
delete functions[byte_code_cp];
message[0] = JERRY_DEBUGGER_FREE_BYTE_CODE_CP;
socket.send(message);
return;
}
case JERRY_DEBUGGER_BREAKPOINT_HIT:
{
var breakpoint = decodeMessage("CI", message, 1);
breakpoint = functions[breakpoint[0]].offsets[breakpoint[1]];
breakpointIndex = "";
if (breakpoint.activeIndex >= 0)
{
breakpointIndex = "breakpoint:" + breakpoint.activeIndex + " ";
}
appendLog("Stopped at " + breakpointIndex + breakpointToString(breakpoint));
return;
}
case JERRY_DEBUGGER_BACKTRACE:
case JERRY_DEBUGGER_BACKTRACE_END:
{
for (var i = 1; i < message.byteLength; i += cpointerSize + 4)
{
var breakpoint = decodeMessage("CI", message, i);
var func = functions[breakpoint[0]];
var best_offset = -1;
for (var offset in func.offsets)
{
if (offset <= breakpoint[1] && offset > best_offset)
{
best_offset = offset;
}
}
if (best_offset >= 0)
{
breakpoint = func.offsets[best_offset];
appendLog(" frame " + backtraceFrame + ": " + breakpointToString(breakpoint));
}
else if (func.name)
{
appendLog(" frame " + backtraceFrame + ": " + func.name + "()");
}
else
{
appendLog(" frame " + backtraceFrame + ": <unknown>()");
}
++backtraceFrame;
}
if (message[0] == JERRY_DEBUGGER_BACKTRACE_END)
{
backtraceFrame = 0;
}
return;
}
default:
{
abortConnection("unexpected message.");
return;
}
}
}
function insertBreakpoint(breakpoint)
{
if (breakpoint.activeIndex < 0)
{
breakpoint.activeIndex = nextBreakpointIndex;
activeBreakpoints[nextBreakpointIndex] = breakpoint;
nextBreakpointIndex++;
var values = [ JERRY_DEBUGGER_UPDATE_BREAKPOINT,
1,
breakpoint.func.byte_code_cp,
breakpoint.offset ];
encodeMessage("BBCI", values);
}
appendLog("Breakpoint " + breakpoint.activeIndex + " at " + breakpointToString(breakpoint));
}
this.setBreakpoint = function(str)
{
line = /^(.+):([1-9][0-9]*)$/.exec(str);
if (line)
{
var functionList = lineList.get(line[2]);
for (var i = 0; i < functionList.length; ++i)
{
var func = functionList[i];
var resource = func.resource;
if (resource == line[1]
|| resource.endsWith("/" + line[1])
|| resource.endsWith("\\" + line[1]))
{
insertBreakpoint(func.lines[line[2]]);
}
}
}
else
{
for (var i in functions)
{
var func = functions[i];
if (func.name == str && func.firstLine >= 0)
{
insertBreakpoint(func.lines[func.firstLine]);
}
}
}
}
this.deleteBreakpoint = function(index)
{
breakpoint = activeBreakpoints[index];
if (!breakpoint)
{
appendLog("No breakpoint found with index " + index);
return;
}
assert(breakpoint.activeIndex == index);
delete activeBreakpoints[index];
breakpoint.activeIndex = -1;
var values = [ JERRY_DEBUGGER_UPDATE_BREAKPOINT,
0,
breakpoint.func.byte_code_cp,
breakpoint.offset ];
encodeMessage("BBCI", values);
appendLog("Breakpoint " + index + " is deleted.");
}
this.listBreakpoints = function()
{
appendLog("List of active breakpoints:");
var found = false;
for (var i in activeBreakpoints)
{
appendLog(" breakpoint " + i + " at " + breakpointToString(activeBreakpoints[i]));
found = true;
}
if (!found)
{
appendLog(" no active breakpoints");
}
}
this.dump = function()
{
for (var i in functions)
{
var func = functions[i];
var resource = func.resource;
if (resource == '')
{
resource = "<unknown>";
}
appendLog("Function 0x" + Number(i).toString(16) + " '" + func.name + "' at " + resource + ":" + func.firstLine);
for (var j in func.lines)
{
var active = "";
if (func.lines[j].active >= 0)
{
active = " (active: " + func.lines[j].active + ")";
}
appendLog(" Breatpoint line: " + j + " at memory offset: " + func.lines[j].offset + active);
}
}
}
}
function debuggerCommand(event)
{
if (event.keyCode != 13)
{
return true;
}
var command = commandBox.value.trim();
args = /^([a-zA-Z]+)(?:\s+([^\s].*)|)$/.exec(command);
if (!args)
{
appendLog("Invalid command");
document.getElementById("command").value = "";
return true;
}
if (!args[2])
{
args[2] = "";
}
if (args[1] == "help")
{
appendLog("Debugger commands:\n" +
" connect <IP address> - connect to server\n" +
" break|b <file_name:line>|<function_name> - set breakpoint\n" +
" delete|d <id> - delete breakpoint\n" +
" list - list breakpoints\n" +
" continue|c - continue execution\n" +
" step|s - step-in execution\n" +
" next|n - connect to server\n" +
" backtrace|bt <max-depth> - get backtrace\n" +
" dump - dump all breakpoint data");
commandBox.value = "";
return true;
}
if (args[1] == "connect")
{
if (debuggerObj)
{
appendLog("Debugger is connected");
return true;
}
if (args[2] == "")
{
appendLog("IP address expected");
return true;
}
appendLog("Connect to: " + args[2]);
debuggerObj = new DebuggerClient(args[2]);
commandBox.value = "";
return true;
}
if (!debuggerObj)
{
appendLog("Debugger is NOT connected");
commandBox.value = "";
return true;
}
switch(args[1])
{
case "b":
case "break":
debuggerObj.setBreakpoint(args[2]);
break;
case "d":
case "delete":
debuggerObj.deleteBreakpoint(args[2]);
break;
case "stop":
debuggerObj.encodeMessage("B", [ JERRY_DEBUGGER_STOP ]);
break;
case "c":
case "continue":
debuggerObj.encodeMessage("B", [ JERRY_DEBUGGER_CONTINUE ]);
break;
case "s":
case "step":
debuggerObj.encodeMessage("B", [ JERRY_DEBUGGER_STEP ]);
break;
case "n":
case "next":
debuggerObj.encodeMessage("B", [ JERRY_DEBUGGER_NEXT ]);
break;
case "bt":
case "backtrace":
max_depth = 0;
if (args[2])
{
if (/[1-9][0-9]*/.exec(args[2]))
{
max_depth = parseInt(args[2]);
}
else
{
appendLog("Invalid maximum depth argument.");
break;
}
}
appendLog("Backtrace:");
debuggerObj.encodeMessage("BI", [ JERRY_DEBUGGER_GET_BACKTRACE, max_depth ]);
break;
case "list":
debuggerObj.listBreakpoints();
break;
case "dump":
debuggerObj.dump();
break;
default:
appendLog("Unknown command: " + args[1]);
}
commandBox.value = "";
return true;
}
</script>
</body>
</html>

679
jerry-debugger/jerry-client-ws.py Executable file
View File

@ -0,0 +1,679 @@
#!/usr/bin/env python
# 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.
from __future__ import print_function
import socket
import sys
import argparse
import logging
import re
from cmd import Cmd
from struct import *
from pprint import pprint # For the readable stack printing.
# Messages sent by the server to client.
JERRY_DEBUGGER_CONFIGURATION = 1
JERRY_DEBUGGER_PARSE_ERROR = 2
JERRY_DEBUGGER_BYTE_CODE_CP = 3
JERRY_DEBUGGER_PARSE_FUNCTION = 4
JERRY_DEBUGGER_BREAKPOINT_LIST = 5
JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST = 6
JERRY_DEBUGGER_RESOURCE_NAME = 7
JERRY_DEBUGGER_RESOURCE_NAME_END = 8
JERRY_DEBUGGER_FUNCTION_NAME = 9
JERRY_DEBUGGER_FUNCTION_NAME_END = 10
JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP = 11
JERRY_DEBUGGER_BREAKPOINT_HIT = 12
JERRY_DEBUGGER_BACKTRACE = 13
JERRY_DEBUGGER_BACKTRACE_END = 14
# Messages sent by the client to server.
JERRY_DEBUGGER_FREE_BYTE_CODE_CP = 1
JERRY_DEBUGGER_UPDATE_BREAKPOINT = 2
JERRY_DEBUGGER_STOP = 3
JERRY_DEBUGGER_CONTINUE = 4
JERRY_DEBUGGER_STEP = 5
JERRY_DEBUGGER_NEXT = 6
JERRY_DEBUGGER_GET_BACKTRACE = 7
MAX_BUFFER_SIZE = 128
WEBSOCKET_BINARY_FRAME = 2
WEBSOCKET_FIN_BIT = 0x80
def arguments_parse():
parser = argparse.ArgumentParser(description="JerryScript debugger client")
parser.add_argument("address", action="store", nargs="?", default="localhost:5001", help="specify a unique network address for connection (default: %(default)s)")
parser.add_argument("-v", "--verbose", action="store_true", default=False, help="increase verbosity (default: %(default)s)")
args = parser.parse_args()
if args.verbose:
logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.DEBUG)
logging.debug("Debug logging mode: ON")
return args
class JerryBreakpoint(object):
def __init__(self, line, offset, function):
self.line = line
self.offset = offset
self.function = function
self.active_index = -1
def to_string(self):
result = self.function.source
if result == "":
result = "<unknown>"
result += ":%d" % (self.line)
if self.function.name:
result += " (in %s)" % (self.function.name)
return result
def __repr__(self):
return ("Breakpoint(line:%d, offset:%d, active_index:%d)"
% (self.line, self.offset, self.active_index))
class JerryFunction(object):
def __init__(self, byte_code_cp, source, name, lines, offsets):
self.byte_code_cp = byte_code_cp
self.source = source
self.name = name
self.lines = {}
self.offsets = {}
self.first_line = -1
if len > 0:
self.first_line = lines[0]
for i in range(len(lines)):
line = lines[i]
offset = offsets[i]
breakpoint = JerryBreakpoint(line, offset, self)
self.lines[line] = breakpoint
self.offsets[offset] = breakpoint
def __repr__(self):
result = ("Function(byte_code_cp:0x%x, source:\"%s\", name:\"%s\", { "
% (self.byte_code_cp, self.source, self.name))
result += ','.join([str(breakpoint) for breakpoint in self.lines.values()])
return result + " })"
class DebuggerPrompt(Cmd):
def __init__(self, debugger):
Cmd.__init__(self)
self.debugger = debugger
self.stop = False
def precmd(self, line):
self.stop = False
return line
def postcmd(self, stop, line):
return self.stop
def insert_breakpoint(self, args):
if args == "":
print("Error: Breakpoint index expected")
else:
set_breakpoint(self.debugger, args)
def do_break(self, args):
""" Insert breakpoints on the given lines """
self.insert_breakpoint(args)
def do_b(self, args):
""" Insert breakpoints on the given lines """
self.insert_breakpoint(args)
def exec_command(self, args, command_id):
self.stop = True
if args != "":
print("Error: No argument expected")
else:
self.debugger.send_command(command_id)
def do_continue(self, args):
""" Continue execution """
self.exec_command(args, JERRY_DEBUGGER_CONTINUE)
def do_c(self, args):
""" Continue execution """
self.exec_command(args, JERRY_DEBUGGER_CONTINUE)
def do_step(self, args):
""" Next breakpoint, step into functions """
self.exec_command(args, JERRY_DEBUGGER_STEP)
def do_s(self, args):
""" Next breakpoint, step into functions """
self.exec_command(args, JERRY_DEBUGGER_STEP)
def do_next(self, args):
""" Next breakpoint in the same context """
self.exec_command(args, JERRY_DEBUGGER_NEXT)
def do_n(self, args):
""" Next breakpoint in the same context """
self.exec_command(args, JERRY_DEBUGGER_NEXT)
def do_list(self, args):
""" Lists the available breakpoints """
if args != "":
print("Error: No argument expected")
return
for breakpoint in self.debugger.active_breakpoint_list.values():
source = breakpoint.function.source
print("%d: %s" % (breakpoint.active_index, breakpoint.to_string()))
def do_delete(self, args):
""" Delete the given breakpoint """
if not args:
print("Error: Breakpoint index expected")
return
try:
breakpoint_index = int(args)
except:
print("Error: Integer number expected")
return
if breakpoint_index in self.debugger.active_breakpoint_list:
breakpoint = self.debugger.active_breakpoint_list[breakpoint_index]
del self.debugger.active_breakpoint_list[breakpoint_index]
breakpoint.active_index = -1
self.debugger.send_breakpoint(breakpoint)
else:
print("Error: Breakpoint %d not found" % (breakpoint_index))
def exec_backtrace(self, args):
max_depth = 0
if args:
try:
max_depth = int(args)
if max_depth <= 0:
print("Error: Positive integer number expected")
return
except:
print("Error: Positive integer number expected")
return
message = pack(self.debugger.byte_order + "BBIB" + self.debugger.idx_format,
WEBSOCKET_BINARY_FRAME | WEBSOCKET_FIN_BIT,
WEBSOCKET_FIN_BIT + 1 + 4,
0,
JERRY_DEBUGGER_GET_BACKTRACE,
max_depth)
self.debugger.send_message(message)
self.stop = True
def do_backtrace(self, args):
""" Get backtrace data from debugger """
self.exec_backtrace(args)
def do_bt(self, args):
""" Get backtrace data from debugger """
self.exec_backtrace(args)
def do_dump(self, args):
""" Dump all of the debugger data """
pprint(self.debugger.function_list)
class Multimap(object):
def __init__(self):
self.map = {}
def get(self, key):
if key in self.map:
return self.map[key]
return []
def insert(self, key, value):
if key in self.map:
self.map[key].append(value)
else:
self.map[key] = [value]
def delete(self, key, value):
items = self.map[key]
if len(items) == 1:
del self.map[key]
else:
del items[items.index(value)]
def __repr__(self):
return "Multimap(%s)" % (self.map)
class JerryDebugger(object):
def __init__(self, address):
if ":" not in address:
print("Wrong address settings: Use the 'IP:PORT' format.")
else:
self.host, self.port = address.split(":")
self.port = int(self.port)
print("Address setup: %s:%s" % (self.host, self.port))
self.message_data = b""
self.function_list = {}
self.next_breakpoint_index = 0
self.active_breakpoint_list = {}
self.line_list = Multimap()
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client_socket.connect((self.host, self.port))
self.send_message(b"GET /jerry-debugger HTTP/1.1\r\n" +
b"Upgrade: websocket\r\n" +
b"Connection: Upgrade\r\n" +
b"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n")
result = b""
expected = (b"HTTP/1.1 101 Switching Protocols\r\n" +
b"Upgrade: websocket\r\n" +
b"Connection: Upgrade\r\n" +
b"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n")
len_expected = len(expected)
while len(result) < len_expected:
result += self.client_socket.recv(1024)
len_result = len(result)
if result[0:len_expected] != expected:
raise Exception("Unexpected handshake")
if len_result > len_expected:
result = result[len_expected:]
len_expected = 6
# Network configurations, which has the following struct:
# header [2] - opcode[1], size[1]
# type [1]
# max_message_size [1]
# cpointer_size [1]
# little_endian [1]
while len(result) < len_expected:
result += self.client_socket.recv(1024)
len_result = len(result)
if (ord(result[0]) != WEBSOCKET_BINARY_FRAME | WEBSOCKET_FIN_BIT or
ord(result[1]) != 4 or
ord(result[2]) != JERRY_DEBUGGER_CONFIGURATION):
raise Exception("Unexpected configuration")
self.max_message_size = ord(result[3])
self.cp_size = ord(result[4])
self.little_endian = ord(result[5])
if self.little_endian:
self.byte_order = "<"
logging.debug("Little-endian machine")
else:
self.byte_order = ">"
logging.debug("Big-endian machine")
if self.cp_size == 2:
self.cp_format = "H"
else:
self.cp_format = "I"
self.idx_format = "I"
logging.debug("Compressed pointer size: %d" % (self.cp_size))
if len_result > len_expected:
self.message_data = result[len_expected:]
def __del__(self):
self.client_socket.close()
def send_message(self, message):
size = len(message)
while size > 0:
bytes_send = self.client_socket.send(message)
if bytes_send < size:
message = message[bytes_send:]
size -= bytes_send
def send_breakpoint(self, breakpoint):
message = pack(self.byte_order + "BBIBB" + self.cp_format + self.idx_format,
WEBSOCKET_BINARY_FRAME | WEBSOCKET_FIN_BIT,
WEBSOCKET_FIN_BIT + 1 + 1 + self.cp_size + 4,
0,
JERRY_DEBUGGER_UPDATE_BREAKPOINT,
int(breakpoint.active_index >= 0),
breakpoint.function.byte_code_cp,
breakpoint.offset)
self.send_message(message)
def send_command(self, command):
message = pack(self.byte_order + "BBIB",
WEBSOCKET_BINARY_FRAME | WEBSOCKET_FIN_BIT,
WEBSOCKET_FIN_BIT + 1,
0,
command)
self.send_message(message)
def get_message(self):
if self.message_data is None:
return None
while True:
if len(self.message_data) >= 2:
if ord(self.message_data[0]) != WEBSOCKET_BINARY_FRAME | WEBSOCKET_FIN_BIT:
raise Exception("Unexpected data frame")
size = ord(self.message_data[1])
if size == 0 or size >= 126:
raise Exception("Unexpected data frame")
if len(self.message_data) >= size + 2:
result = self.message_data[0:size + 2]
self.message_data = self.message_data[size + 2:]
return result
data = self.client_socket.recv(MAX_BUFFER_SIZE)
if not data:
self.message_data = None
return None
self.message_data += data
def parse_source(debugger, data):
source_name = ""
function_name = ""
stack = [{"lines": [], "offsets": [], "name": ""}]
new_function_list = {}
while True:
if data is None:
return
buffer_type = ord(data[2])
buffer_size = ord(data[1]) - 1
logging.debug("Parser buffer type: %d, message size: %d" % (buffer_type, buffer_size))
if buffer_type == JERRY_DEBUGGER_PARSE_ERROR:
logging.error("Parser error!")
return
if buffer_type in [JERRY_DEBUGGER_RESOURCE_NAME, JERRY_DEBUGGER_RESOURCE_NAME_END]:
source_name += unpack("%ds" % (buffer_size), data[3:buffer_size+3])[0]
elif buffer_type in [JERRY_DEBUGGER_FUNCTION_NAME, JERRY_DEBUGGER_FUNCTION_NAME_END]:
function_name += unpack("%ds" % (buffer_size), data[3:buffer_size+3])[0]
elif buffer_type == JERRY_DEBUGGER_PARSE_FUNCTION:
logging.debug("Source name: %s, function name: %s" % (source_name, function_name))
stack.append({"name": function_name, "source": source_name, "lines": [], "offsets": []})
function_name = ""
elif buffer_type in [JERRY_DEBUGGER_BREAKPOINT_LIST, JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST]:
name = "lines"
if buffer_type == JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST:
name = "offsets"
logging.debug("Breakpoint %s received" % (name))
buffer_pos = 3
while buffer_size > 0:
line = unpack(debugger.byte_order + debugger.idx_format,
data[buffer_pos: buffer_pos + 4])
stack[-1][name].append(line[0])
buffer_pos += 4
buffer_size -= 4
elif buffer_type == JERRY_DEBUGGER_BYTE_CODE_CP:
byte_code_cp = unpack(debugger.byte_order + debugger.cp_format,
data[3: 3 + debugger.cp_size])[0]
logging.debug("Byte code cptr received: {0x%x}" % (byte_code_cp))
func_desc = stack.pop()
# We know the last item in the list is the general byte code.
if len(stack) == 0:
func_desc["source"] = source_name
function = JerryFunction(byte_code_cp,
func_desc["source"],
func_desc["name"],
func_desc["lines"],
func_desc["offsets"])
new_function_list[byte_code_cp] = function
if len(stack) == 0:
logging.debug("Parse completed.")
break
else:
logging.error("Parser error!")
return
data = debugger.get_message()
# Copy the ready list to the global storage.
debugger.function_list.update(new_function_list)
for function in new_function_list.values():
for line, breakpoint in function.lines.items():
debugger.line_list.insert(line, breakpoint)
def release_function(debugger, data):
byte_code_cp = unpack(debugger.byte_order + debugger.cp_format,
data[3: 3 + debugger.cp_size])[0]
function = debugger.function_list[byte_code_cp]
for line, breakpoint in function.lines.items():
debugger.line_list.delete(line, breakpoint)
if breakpoint.active_index >= 0:
del debugger.active_breakpoint_list[breakpoint.active_index]
del debugger.function_list[byte_code_cp]
message = pack(debugger.byte_order + "BBIB" + debugger.cp_format,
WEBSOCKET_BINARY_FRAME | WEBSOCKET_FIN_BIT,
WEBSOCKET_FIN_BIT + 1 + debugger.cp_size,
0,
JERRY_DEBUGGER_FREE_BYTE_CODE_CP,
byte_code_cp)
debugger.send_message(message)
logging.debug("Function {0x%x} byte-code released" % byte_code_cp)
def enable_breakpoint(debugger, breakpoint):
if breakpoint.active_index < 0:
debugger.next_breakpoint_index += 1
debugger.active_breakpoint_list[debugger.next_breakpoint_index] = breakpoint
breakpoint.active_index = debugger.next_breakpoint_index
debugger.send_breakpoint(breakpoint)
print ("Breakpoint %d at %s"
% (breakpoint.active_index, breakpoint.to_string()))
def set_breakpoint(debugger, string):
line = re.match("(.*):(\\d+)$", string)
found = False
if line:
source = line.group(1)
line = int(line.group(2))
for breakpoint in debugger.line_list.get(line):
func_source = breakpoint.function.source
if (source == func_source or
func_source.endswith("/" + source) or
func_source.endswith("\\" + source)):
enable_breakpoint(debugger, breakpoint)
found = True
else:
for function in debugger.function_list.values():
if function.name == string:
if function.first_line >= 0:
enable_breakpoint(debugger, function.lines[function.first_line])
else:
print("Function %s has no breakpoints." % (string))
found = True
if not found:
print("Breakpoint not found")
return
def main():
args = arguments_parse()
debugger = JerryDebugger(args.address)
logging.debug("Connected to JerryScript on %d port" % (debugger.port))
prompt = DebuggerPrompt(debugger)
prompt.prompt = "(jerry-debugger) "
while True:
data = debugger.get_message()
if not data: # Break the while loop if there is no more data.
break
buffer_type = ord(data[2])
buffer_size = ord(data[1]) - 1
logging.debug("Main buffer type: %d, message size: %d" % (buffer_type, buffer_size))
if buffer_type in [JERRY_DEBUGGER_PARSE_ERROR,
JERRY_DEBUGGER_BYTE_CODE_CP,
JERRY_DEBUGGER_PARSE_FUNCTION,
JERRY_DEBUGGER_BREAKPOINT_LIST,
JERRY_DEBUGGER_RESOURCE_NAME,
JERRY_DEBUGGER_RESOURCE_NAME_END,
JERRY_DEBUGGER_FUNCTION_NAME,
JERRY_DEBUGGER_FUNCTION_NAME_END]:
parse_source(debugger, data)
elif buffer_type == JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP:
release_function(debugger, data)
elif buffer_type == JERRY_DEBUGGER_BREAKPOINT_HIT:
breakpoint_data = unpack(debugger.byte_order + debugger.cp_format + debugger.idx_format, data[3:])
function = debugger.function_list[breakpoint_data[0]]
breakpoint = function.offsets[breakpoint_data[1]]
breakpoint_index = ""
if breakpoint.active_index >= 0:
breakpoint_index = " breakpoint:%d" % (breakpoint.active_index)
print("Stopped at%s %s" % (breakpoint_index, breakpoint.to_string()))
prompt.cmdloop()
elif buffer_type in [JERRY_DEBUGGER_BACKTRACE, JERRY_DEBUGGER_BACKTRACE_END]:
frame_index = 0
while True:
buffer_pos = 3
while buffer_size > 0:
breakpoint_data = unpack(debugger.byte_order + debugger.cp_format + debugger.idx_format,
data[buffer_pos: buffer_pos + debugger.cp_size + 4])
function = debugger.function_list[breakpoint_data[0]]
best_offset = -1
for offset in function.offsets:
if offset <= breakpoint_data[1] and offset > best_offset:
best_offset = offset
if best_offset >= 0:
breakpoint = function.offsets[best_offset]
print("Frame %d: %s" % (frame_index, breakpoint.to_string()))
elif function.name:
print("Frame %d: %s()" % (frame_index, function.name))
else:
print("Frame %d: <unknown>()" % (frame_index))
frame_index += 1
buffer_pos += 6
buffer_size -= 6
if buffer_type == JERRY_DEBUGGER_BACKTRACE_END:
break
data = debugger.get_message()
buffer_type = ord(data[2])
buffer_size = ord(data[1]) - 1
if buffer_type not in [JERRY_DEBUGGER_BACKTRACE,
JERRY_DEBUGGER_BACKTRACE_END]:
raise Exception("Backtrace data expected")
prompt.cmdloop()
else:
raise Exception("Unknown message")
if __name__ == "__main__":
try:
main()
except socket.error as error_msg:
try:
errno = error_msg.errno
msg = str(error_msg)
except:
errno = error_msg[0]
msg = error_msg[1]
if errno == 111:
sys.exit("Failed to connect to the JerryScript debugger.")
elif errno == 32:
sys.exit("Connection closed.")
else:
sys.exit("Failed to connect to the JerryScript debugger.\nError: %s" % (msg))

View File

@ -124,6 +124,7 @@ print_help (char *name)
" --parse-only\n"
" --show-opcodes\n"
" --show-regexp-opcodes\n"
" --start-debug-server\n"
" --save-snapshot-for-global FILE\n"
" --save-snapshot-for-eval FILE\n"
" --save-literals-list-format FILE\n"
@ -435,6 +436,10 @@ main (int argc,
"Ignoring 'show-regexp-opcodes' option because this feature is disabled!\n");
}
}
else if (!strcmp ("--start-debug-server", argv[i]))
{
flags |= JERRY_INIT_DEBUGGER;
}
else if (!strcmp ("--save-snapshot-for-global", argv[i])
|| !strcmp ("--save-snapshot-for-eval", argv[i]))
{
@ -703,7 +708,11 @@ main (int argc,
}
else
{
ret_value = jerry_parse (source_p, source_size, false);
ret_value = jerry_parse_named_resource ((jerry_char_t *) file_names[i],
strlen (file_names[i]),
source_p,
source_size,
false);
if (!jerry_value_has_error_flag (ret_value) && !is_parse_only)
{

View File

@ -0,0 +1,4 @@
b do_backtrace.js:32
c
bt
c

View File

@ -0,0 +1,9 @@
Address setup: localhost:5001
Stopped at tests/debugger/do_backtrace.js:15
(jerry-debugger) Breakpoint 1 at tests/debugger/do_backtrace.js:32 (in f4)
(jerry-debugger) Stopped at breakpoint:1 tests/debugger/do_backtrace.js:32 (in f4)
(jerry-debugger) Frame 0: tests/debugger/do_backtrace.js:32 (in f4)
Frame 1: tests/debugger/do_backtrace.js:39 (in foo)
Frame 2: tests/debugger/do_backtrace.js:48 (in test)
Frame 3: tests/debugger/do_backtrace.js:60
(jerry-debugger) Connection closed.

View File

@ -0,0 +1,60 @@
// 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.
print("first line of code");
function f1()
{
function f2()
{
return function f3() {
a = 4;
print("funciton f3");
};
}
x =
6;
}
function f4() {
print("function f4");
}
function foo()
{
print("function foo");
var tmp = 4;
f4();
}
print ("var cat");
var cat = 'cat';
function test()
{
print("function test");
foo();
var a = 3;
var b = 5;
var c = a + b;
global_var = c;
return c;
}
var
x =
1;
test();

View File

@ -0,0 +1,7 @@
break do_break.js:28
list
b do_break.js:40
delete 1
next
next
continue

View File

@ -0,0 +1,8 @@
Address setup: localhost:5001
Stopped at tests/debugger/do_break.js:15
(jerry-debugger) Breakpoint not found
(jerry-debugger) (jerry-debugger) Breakpoint not found
(jerry-debugger) Error: Breakpoint 1 not found
(jerry-debugger) Stopped at tests/debugger/do_break.js:42
(jerry-debugger) Stopped at tests/debugger/do_break.js:43
(jerry-debugger) Connection closed.

View File

@ -0,0 +1,60 @@
// 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.
print("first line of code");
function f1()
{
function f2()
{
return function f3() {
a = 4;
print("funciton f3");
};
}
x =
6;
}
function f4() {
print("function f4");
}
function foo()
{
print("function foo");
var tmp = 4;
f4();
}
print ("var cat");
var cat = 'cat';
function test()
{
print("function test");
foo();
var a = 3;
var b = 5;
var c = a + b;
global_var = c;
return c;
}
var
x =
1;
test();

View File

@ -0,0 +1,2 @@
dump
c

View File

@ -0,0 +1,10 @@
Address setup: localhost:5001
Stopped at tests/debugger/do_dump.js:15
(jerry-debugger) {68: Function(byte_code_cp:0x44, source:"tests/debugger/do_dump.js", name:"f4", { Breakpoint(line:32, offset:17, active_index:-1),Breakpoint(line:31, offset:16, active_index:-1) }),
79: Function(byte_code_cp:0x4f, source:"tests/debugger/do_dump.js", name:"f1", { Breakpoint(line:17, offset:21, active_index:-1),Breakpoint(line:27, offset:22, active_index:-1) }),
102: Function(byte_code_cp:0x66, source:"tests/debugger/do_dump.js", name:"f2", { Breakpoint(line:19, offset:14, active_index:-1),Breakpoint(line:21, offset:15, active_index:-1) }),
105: Function(byte_code_cp:0x69, source:"tests/debugger/do_dump.js", name:"foo", { Breakpoint(line:35, offset:20, active_index:-1),Breakpoint(line:37, offset:21, active_index:-1),Breakpoint(line:38, offset:26, active_index:-1),Breakpoint(line:39, offset:31, active_index:-1) }),
125: Function(byte_code_cp:0x7d, source:"tests/debugger/do_dump.js", name:"f3", { Breakpoint(line:22, offset:25, active_index:-1),Breakpoint(line:23, offset:30, active_index:-1) }),
131: Function(byte_code_cp:0x83, source:"tests/debugger/do_dump.js", name:"", { Breakpoint(line:57, offset:63, active_index:-1),Breakpoint(line:42, offset:54, active_index:-1),Breakpoint(line:43, offset:59, active_index:-1),Breakpoint(line:60, offset:68, active_index:-1),Breakpoint(line:15, offset:49, active_index:-1) }),
154: Function(byte_code_cp:0x9a, source:"tests/debugger/do_dump.js", name:"test", { Breakpoint(line:45, offset:26, active_index:-1),Breakpoint(line:47, offset:27, active_index:-1),Breakpoint(line:48, offset:32, active_index:-1),Breakpoint(line:49, offset:36, active_index:-1),Breakpoint(line:50, offset:41, active_index:-1),Breakpoint(line:51, offset:46, active_index:-1),Breakpoint(line:52, offset:52, active_index:-1),Breakpoint(line:53, offset:56, active_index:-1) })}
(jerry-debugger) Connection closed.

60
tests/debugger/do_dump.js Normal file
View File

@ -0,0 +1,60 @@
// 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.
print("first line of code");
function f1()
{
function f2()
{
return function f3() {
a = 4;
print("funciton f3");
};
}
x =
6;
}
function f4() {
print("function f4");
}
function foo()
{
print("function foo");
var tmp = 4;
f4();
}
print ("var cat");
var cat = 'cat';
function test()
{
print("function test");
foo();
var a = 3;
var b = 5;
var c = a + b;
global_var = c;
return c;
}
var
x =
1;
test();

View File

@ -0,0 +1,8 @@
break do_step.js:25
step
backtrace
b do_step.js:26
c
s
bt
c

View File

@ -0,0 +1,12 @@
Address setup: localhost:5001
Stopped at tests/debugger/do_step.js:25
(jerry-debugger) Breakpoint 1 at tests/debugger/do_step.js:25
(jerry-debugger) Stopped at tests/debugger/do_step.js:15 (in f1)
(jerry-debugger) Frame 0: tests/debugger/do_step.js:15 (in f1)
Frame 1: tests/debugger/do_step.js:25
(jerry-debugger) Breakpoint 2 at tests/debugger/do_step.js:26
(jerry-debugger) Stopped at breakpoint:2 tests/debugger/do_step.js:26
(jerry-debugger) Stopped at tests/debugger/do_step.js:20 (in f2)
(jerry-debugger) Frame 0: tests/debugger/do_step.js:20 (in f2)
Frame 1: tests/debugger/do_step.js:26
(jerry-debugger) Connection closed.

26
tests/debugger/do_step.js Normal file
View File

@ -0,0 +1,26 @@
// 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.
function f1()
{
var i = 1;
}
function f2()
{
var y = 2;
}
f1();
f2();

View File

@ -56,6 +56,7 @@ def get_arguments():
parser.add_argument('-j', '--jobs', metavar='N', action='store', type=int, default=multiprocessing.cpu_count() + 1, help='Allowed N build jobs at once (default: %(default)s)')
parser.add_argument('--jerry-cmdline', metavar='X', choices=['ON', 'OFF'], default='ON', type=str.upper, help='build jerry command line tool (%(choices)s; default: %(default)s)')
parser.add_argument('--jerry-cmdline-minimal', metavar='X', choices=['ON', 'OFF'], default='OFF', type=str.upper, help='build minimal version of the jerry command line tool (%(choices)s; default: %(default)s)')
parser.add_argument('--jerry-debugger', metavar='X', choices=['ON', 'OFF'], default='OFF', type=str.upper, help='enable the jerry debugger (%(choices)s; default: %(default)s)')
parser.add_argument('--jerry-libc', metavar='X', choices=['ON', 'OFF'], default='ON', type=str.upper, help='build and use jerry-libc (%(choices)s; default: %(default)s)')
parser.add_argument('--jerry-libm', metavar='X', choices=['ON', 'OFF'], default='ON', type=str.upper, help='build and use jerry-libm (%(choices)s; default: %(default)s)')
parser.add_argument('--js-parser', metavar='X', choices=['ON', 'OFF'], default='ON', type=str.upper, help='enable js-parser (%(choices)s; default: %(default)s)')
@ -117,6 +118,7 @@ def generate_build_options(arguments):
build_options.append('-DFEATURE_PROFILE=%s' % PROFILE)
build_options.append('-DFEATURE_DEBUGGER=%s' % arguments.jerry_debugger)
build_options.append('-DFEATURE_SNAPSHOT_EXEC=%s' % arguments.snapshot_exec)
build_options.append('-DFEATURE_SNAPSHOT_SAVE=%s' % arguments.snapshot_save)
build_options.append('-DFEATURE_SYSTEM_ALLOCATOR=%s' % arguments.system_allocator)

View File

@ -34,6 +34,7 @@ parser.add_argument('--check-doxygen', action='store_true', default=False, help=
parser.add_argument('--check-vera', action='store_true', default=False, help='Run vera check')
parser.add_argument('--check-license', action='store_true', default=False, help='Run license check')
parser.add_argument('--buildoption-test', action='store_true', default=False, help='Run buildoption-test')
parser.add_argument('--jerry-debugger', action='store_true', default=False, help='Run jerry-debugger tests')
parser.add_argument('--jerry-tests', action='store_true', default=False, help='Run jerry-tests')
parser.add_argument('--jerry-test-suite', action='store_true', default=False, help='Run jerry-test-suite')
parser.add_argument('--unittests', action='store_true', default=False, help='Run unittests')
@ -96,6 +97,11 @@ test262_test_suite_options = [
Options('test262_tests'),
]
# Test options for jerry-debugger
debugger_test_options = [
Options('jerry_debugger_tests', ['--debug', '--jerry-debugger=on', '--jerry-libc=off']),
]
# Test options for buildoption-test
jerry_buildoptions = [
Options('buildoption_test-lto', ['--lto=on']),
@ -145,6 +151,30 @@ def run_check(runnable):
return ret
def run_jerry_debugger_tests():
ret_build = ret_test = 0
for job in debugger_test_options:
ret_build = create_binary(job.build_args)
if ret_build:
break
for file in os.listdir(DEBUGGER_TESTS_DIR):
if file.endswith(".js"):
test_case, _ = os.path.splitext(file)
test_case_path = os.path.join (DEBUGGER_TESTS_DIR, test_case)
test_cmd = [
DEBUGGER_TEST_RUNNER_SCRIPT,
get_binary_path(job.out_dir),
DEBUGGER_CLIENT_SCRIPT,
os.path.relpath (test_case_path, PROJECT_DIR),
]
if job.test_args:
test_cmd.extend(job.test_args)
ret_test |= run_check(test_cmd)
return ret_build | ret_test
def run_jerry_tests():
ret_build = ret_test = 0
for job in jerry_tests_options:
@ -242,6 +272,9 @@ def main():
if not ret and (script_args.all or script_args.check_license):
ret = run_check([LICENSE_SCRIPT])
if not ret and (script_args.all or script_args.jerry_debugger):
ret = run_jerry_debugger_tests()
if not ret and (script_args.all or script_args.jerry_tests):
ret = run_jerry_tests()

View File

@ -0,0 +1,38 @@
#!/bin/bash
# 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.
JERRY=$1
DEBUGGER_CLIENT=$2
TEST_CASE=$3
START_DEBUG_SERVER="${JERRY} ${TEST_CASE}.js --start-debug-server &"
echo "$START_DEBUG_SERVER"
eval "$START_DEBUG_SERVER"
sleep 1s
RESULT=$((cat "${TEST_CASE}.cmd" | ${DEBUGGER_CLIENT}) 2>&1)
DIFF=$(diff -u0 ${TEST_CASE}.expected <(echo "$RESULT"))
if [ -n "$DIFF" ]
then
echo "$DIFF"
echo "${TEST_CASE} failed"
exit 1
fi
echo "${TEST_CASE} passed"
exit 0

View File

@ -18,6 +18,7 @@ from os import path
TOOLS_DIR = path.dirname(path.abspath(__file__))
PROJECT_DIR = path.normpath(path.join(TOOLS_DIR, '..'))
DEBUGGER_TESTS_DIR = path.join(PROJECT_DIR, 'tests/debugger')
JERRY_TESTS_DIR = path.join(PROJECT_DIR, 'tests/jerry')
JERRY_TEST_SUITE_DIR = path.join(PROJECT_DIR, 'tests/jerry-test-suite')
JERRY_TEST_SUITE_MINIMAL_LIST = path.join(PROJECT_DIR, 'tests/jerry-test-suite/minimal-profile-list')
@ -26,6 +27,8 @@ TEST262_TEST_SUITE_DIR = path.join(PROJECT_DIR, 'tests/test262')
BUILD_SCRIPT = path.join(TOOLS_DIR, 'build.py')
CPPCHECK_SCRIPT = path.join(TOOLS_DIR, 'check-cppcheck.sh')
DEBUGGER_CLIENT_SCRIPT = path.join(PROJECT_DIR, 'jerry-debugger/jerry-client-ws.py')
DEBUGGER_TEST_RUNNER_SCRIPT = path.join(TOOLS_DIR, 'runners/run-debugger-test.sh')
DOXYGEN_SCRIPT = path.join(TOOLS_DIR, 'check-doxygen.sh')
LICENSE_SCRIPT = path.join(TOOLS_DIR, 'check-license.py')
SIGNED_OFF_SCRIPT = path.join(TOOLS_DIR, 'check-signed-off.sh')