From 359589231351673de88e1af3dd35c873e6ee5db5 Mon Sep 17 00:00:00 2001 From: Levente Orban Date: Wed, 26 Oct 2016 09:49:27 +0200 Subject: [PATCH] Initial version of JerryScript debugger 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 --- .travis.yml | 1 + docs/02.API-REFERENCE.md | 36 + docs/07.DEBUGGER.md | 72 ++ jerry-core/CMakeLists.txt | 17 + jerry-core/debugger/jerry-debugger-ws.c | 547 +++++++++++++ jerry-core/debugger/jerry-debugger-ws.h | 86 ++ jerry-core/debugger/jerry-debugger.c | 446 ++++++++++ jerry-core/debugger/jerry-debugger.h | 210 +++++ jerry-core/debugger/jerry-sha1.c | 370 +++++++++ jerry-core/ecma/base/ecma-helpers.c | 35 + jerry-core/jcontext/jcontext.h | 12 + jerry-core/jerry-api.h | 3 + jerry-core/jerry.c | 45 + jerry-core/parser/js/byte-code.h | 17 + jerry-core/parser/js/js-parser-internal.h | 26 + jerry-core/parser/js/js-parser-statm.c | 70 ++ jerry-core/parser/js/js-parser.c | 125 +++ jerry-core/vm/vm-defines.h | 3 +- jerry-core/vm/vm.c | 67 +- jerry-core/vm/vm.h | 2 + jerry-debugger/jerry-client-ws.html | 949 ++++++++++++++++++++++ jerry-debugger/jerry-client-ws.py | 679 ++++++++++++++++ jerry-main/main-unix.c | 11 +- tests/debugger/do_backtrace.cmd | 4 + tests/debugger/do_backtrace.expected | 9 + tests/debugger/do_backtrace.js | 60 ++ tests/debugger/do_break.cmd | 7 + tests/debugger/do_break.expected | 8 + tests/debugger/do_break.js | 60 ++ tests/debugger/do_dump.cmd | 2 + tests/debugger/do_dump.expected | 10 + tests/debugger/do_dump.js | 60 ++ tests/debugger/do_step.cmd | 8 + tests/debugger/do_step.expected | 12 + tests/debugger/do_step.js | 26 + tools/build.py | 2 + tools/run-tests.py | 33 + tools/runners/run-debugger-test.sh | 38 + tools/settings.py | 3 + 39 files changed, 4166 insertions(+), 5 deletions(-) create mode 100644 docs/07.DEBUGGER.md create mode 100644 jerry-core/debugger/jerry-debugger-ws.c create mode 100644 jerry-core/debugger/jerry-debugger-ws.h create mode 100644 jerry-core/debugger/jerry-debugger.c create mode 100644 jerry-core/debugger/jerry-debugger.h create mode 100644 jerry-core/debugger/jerry-sha1.c create mode 100644 jerry-debugger/jerry-client-ws.html create mode 100755 jerry-debugger/jerry-client-ws.py create mode 100644 tests/debugger/do_backtrace.cmd create mode 100644 tests/debugger/do_backtrace.expected create mode 100644 tests/debugger/do_backtrace.js create mode 100644 tests/debugger/do_break.cmd create mode 100644 tests/debugger/do_break.expected create mode 100644 tests/debugger/do_break.js create mode 100644 tests/debugger/do_dump.cmd create mode 100644 tests/debugger/do_dump.expected create mode 100644 tests/debugger/do_dump.js create mode 100644 tests/debugger/do_step.cmd create mode 100644 tests/debugger/do_step.expected create mode 100644 tests/debugger/do_step.js create mode 100755 tools/runners/run-debugger-test.sh diff --git a/.travis.yml b/.travis.yml index e6d7eac838..8ddc780440 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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" diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index fdaac5965e..9d16c64b12 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -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 diff --git a/docs/07.DEBUGGER.md b/docs/07.DEBUGGER.md new file mode 100644 index 0000000000..c84645a068 --- /dev/null +++ b/docs/07.DEBUGGER.md @@ -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. \ No newline at end of file diff --git a/jerry-core/CMakeLists.txt b/jerry-core/CMakeLists.txt index 0d730e6d36..29ed064ea7 100644 --- a/jerry-core/CMakeLists.txt +++ b/jerry-core/CMakeLists.txt @@ -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) diff --git a/jerry-core/debugger/jerry-debugger-ws.c b/jerry-core/debugger/jerry-debugger-ws.c new file mode 100644 index 0000000000..6869c81870 --- /dev/null +++ b/jerry-core/debugger/jerry-debugger-ws.c @@ -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 +#include +#include +#include + +#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 */ diff --git a/jerry-core/debugger/jerry-debugger-ws.h b/jerry-core/debugger/jerry-debugger-ws.h new file mode 100644 index 0000000000..cc7b816ce2 --- /dev/null +++ b/jerry-core/debugger/jerry-debugger-ws.h @@ -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 */ diff --git a/jerry-core/debugger/jerry-debugger.c b/jerry-core/debugger/jerry-debugger.c new file mode 100644 index 0000000000..140ee892a8 --- /dev/null +++ b/jerry-core/debugger/jerry-debugger.c @@ -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 */ diff --git a/jerry-core/debugger/jerry-debugger.h b/jerry-core/debugger/jerry-debugger.h new file mode 100644 index 0000000000..ea26bbe917 --- /dev/null +++ b/jerry-core/debugger/jerry-debugger.h @@ -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 */ diff --git a/jerry-core/debugger/jerry-sha1.c b/jerry-core/debugger/jerry-sha1.c new file mode 100644 index 0000000000..2da6331d2a --- /dev/null +++ b/jerry-core/debugger/jerry-sha1.c @@ -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 */ diff --git a/jerry-core/ecma/base/ecma-helpers.c b/jerry-core/ecma/base/ecma-helpers.c index 43f725f7b7..4f48863f8f 100644 --- a/jerry-core/ecma/base/ecma-helpers.c +++ b/jerry-core/ecma/base/ecma-helpers.c @@ -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 { diff --git a/jerry-core/jcontext/jcontext.h b/jerry-core/jcontext/jcontext.h index a19954f247..ec0e06f1d9 100644 --- a/jerry-core/jcontext/jcontext.h +++ b/jerry-core/jcontext/jcontext.h @@ -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 */ diff --git a/jerry-core/jerry-api.h b/jerry-core/jerry-api.h index 31436e249c..76f26b02b4 100644 --- a/jerry-core/jerry-api.h +++ b/jerry-core/jerry-api.h @@ -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); diff --git a/jerry-core/jerry.c b/jerry-core/jerry.c index 661e346244..b105f51873 100644 --- a/jerry-core/jerry.c +++ b/jerry-core/jerry.c @@ -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. * diff --git a/jerry-core/parser/js/byte-code.h b/jerry-core/parser/js/byte-code.h index e396cc7eee..ad3d13e35e 100644 --- a/jerry-core/parser/js/byte-code.h +++ b/jerry-core/parser/js/byte-code.h @@ -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, \ diff --git a/jerry-core/parser/js/js-parser-internal.h b/jerry-core/parser/js/js-parser-internal.h index 878a7b46a3..e5e03038d0 100644 --- a/jerry-core/parser/js/js-parser-internal.h +++ b/jerry-core/parser/js/js-parser-internal.h @@ -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 */ + /** * @} * @} diff --git a/jerry-core/parser/js/js-parser-statm.c b/jerry-core/parser/js/js-parser-statm.c index a27616b411..96d54926e9 100644 --- a/jerry-core/parser/js/js-parser-statm.c +++ b/jerry-core/parser/js/js-parser-statm.c @@ -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: diff --git a/jerry-core/parser/js/js-parser.c b/jerry-core/parser/js/js-parser.c index 11beda7032..ea9a7eed46 100644 --- a/jerry-core/parser/js/js-parser.c +++ b/jerry-core/parser/js/js-parser.c @@ -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 diff --git a/jerry-core/vm/vm-defines.h b/jerry-core/vm/vm-defines.h index 5bf98128ec..7ae91ee172 100644 --- a/jerry-core/vm/vm-defines.h +++ b/jerry-core/vm/vm-defines.h @@ -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 */ diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index decc021165..af11798981 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -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; diff --git a/jerry-core/vm/vm.h b/jerry-core/vm/vm.h index 1362451a79..d243b8d1dd 100644 --- a/jerry-core/vm/vm.h +++ b/jerry-core/vm/vm.h @@ -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; /** diff --git a/jerry-debugger/jerry-client-ws.html b/jerry-debugger/jerry-client-ws.html new file mode 100644 index 0000000000..454c9ed370 --- /dev/null +++ b/jerry-debugger/jerry-client-ws.html @@ -0,0 +1,949 @@ + + + + + +JerryScript HTML (WebSocket) Debugger Client + + + + + +

JerryScript HTML (WebSocket) Debugger Client

+ +
+ +Getting help: type 'help' in the command line below.
+ + + + + + diff --git a/jerry-debugger/jerry-client-ws.py b/jerry-debugger/jerry-client-ws.py new file mode 100755 index 0000000000..9c5af02137 --- /dev/null +++ b/jerry-debugger/jerry-client-ws.py @@ -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 = "" + + 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: ()" % (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)) diff --git a/jerry-main/main-unix.c b/jerry-main/main-unix.c index f29eaea490..e28ea0e117 100644 --- a/jerry-main/main-unix.c +++ b/jerry-main/main-unix.c @@ -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) { diff --git a/tests/debugger/do_backtrace.cmd b/tests/debugger/do_backtrace.cmd new file mode 100644 index 0000000000..b4fb018894 --- /dev/null +++ b/tests/debugger/do_backtrace.cmd @@ -0,0 +1,4 @@ +b do_backtrace.js:32 +c +bt +c diff --git a/tests/debugger/do_backtrace.expected b/tests/debugger/do_backtrace.expected new file mode 100644 index 0000000000..7500b59ba4 --- /dev/null +++ b/tests/debugger/do_backtrace.expected @@ -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. diff --git a/tests/debugger/do_backtrace.js b/tests/debugger/do_backtrace.js new file mode 100644 index 0000000000..6e8b748b52 --- /dev/null +++ b/tests/debugger/do_backtrace.js @@ -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(); diff --git a/tests/debugger/do_break.cmd b/tests/debugger/do_break.cmd new file mode 100644 index 0000000000..1a99d95b9c --- /dev/null +++ b/tests/debugger/do_break.cmd @@ -0,0 +1,7 @@ +break do_break.js:28 +list +b do_break.js:40 +delete 1 +next +next +continue diff --git a/tests/debugger/do_break.expected b/tests/debugger/do_break.expected new file mode 100644 index 0000000000..88269dc586 --- /dev/null +++ b/tests/debugger/do_break.expected @@ -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. diff --git a/tests/debugger/do_break.js b/tests/debugger/do_break.js new file mode 100644 index 0000000000..6e8b748b52 --- /dev/null +++ b/tests/debugger/do_break.js @@ -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(); diff --git a/tests/debugger/do_dump.cmd b/tests/debugger/do_dump.cmd new file mode 100644 index 0000000000..9d8cc0fd18 --- /dev/null +++ b/tests/debugger/do_dump.cmd @@ -0,0 +1,2 @@ +dump +c diff --git a/tests/debugger/do_dump.expected b/tests/debugger/do_dump.expected new file mode 100644 index 0000000000..9355065ccf --- /dev/null +++ b/tests/debugger/do_dump.expected @@ -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. diff --git a/tests/debugger/do_dump.js b/tests/debugger/do_dump.js new file mode 100644 index 0000000000..6e8b748b52 --- /dev/null +++ b/tests/debugger/do_dump.js @@ -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(); diff --git a/tests/debugger/do_step.cmd b/tests/debugger/do_step.cmd new file mode 100644 index 0000000000..00af797dec --- /dev/null +++ b/tests/debugger/do_step.cmd @@ -0,0 +1,8 @@ +break do_step.js:25 +step +backtrace +b do_step.js:26 +c +s +bt +c diff --git a/tests/debugger/do_step.expected b/tests/debugger/do_step.expected new file mode 100644 index 0000000000..edbb4c3cf9 --- /dev/null +++ b/tests/debugger/do_step.expected @@ -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. diff --git a/tests/debugger/do_step.js b/tests/debugger/do_step.js new file mode 100644 index 0000000000..4f084409ce --- /dev/null +++ b/tests/debugger/do_step.js @@ -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(); diff --git a/tools/build.py b/tools/build.py index e5c3db2948..5692a5a6f1 100755 --- a/tools/build.py +++ b/tools/build.py @@ -56,6 +56,7 @@ def devhelp(help): 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) diff --git a/tools/run-tests.py b/tools/run-tests.py index 690455c64e..5b9a012d84 100755 --- a/tools/run-tests.py +++ b/tools/run-tests.py @@ -34,6 +34,7 @@ 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 @@ def __init__(self, name = '', build_args = None, test_args = None): 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() diff --git a/tools/runners/run-debugger-test.sh b/tools/runners/run-debugger-test.sh new file mode 100755 index 0000000000..2fdf3cde83 --- /dev/null +++ b/tools/runners/run-debugger-test.sh @@ -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 diff --git a/tools/settings.py b/tools/settings.py index 0579177b9a..9a4cd94698 100755 --- a/tools/settings.py +++ b/tools/settings.py @@ -18,6 +18,7 @@ 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 @@ 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')