[Debugger] Create a simplified transmission layer (#2781)

It supports the raw packet sending over a TCP/IP protocol

JerryScript-DCO-1.0-Signed-off-by: Robert Sipka rsipka.uszeged@partner.samsung.com
This commit is contained in:
Robert Sipka
2019-03-18 11:00:15 +01:00
committed by GitHub
parent fbd734ed28
commit 3d3e6fdf58
10 changed files with 328 additions and 35 deletions
+2 -1
View File
@@ -69,7 +69,8 @@ debugger can be enabled by calling `jerryx_debugger_after_connect
(jerryx_debugger_tcp_create (debug_port) && jerryx_debugger_ws_create ())`
after the `jerry_init ()` function. It initializes the debugger and
blocks until a client connects. (Custom transport layers may be
implemented and initialized similarly.)
implemented and initialized similarly. Currently, `jerryx_debugger_rp_create()` for
raw packet transport layer is also available.)
The resource name provided to `jerry_parse ()` is used by the client
to identify the resource name of the source code. This resource name
+1 -1
View File
@@ -250,7 +250,7 @@ def src_check_args(args):
def main():
args = jerry_client_main.arguments_parse()
debugger = jerry_client_main.JerryDebugger(args.address)
debugger = jerry_client_main.JerryDebugger(args.address, args.channel)
debugger.non_interactive = args.non_interactive
logging.debug("Connected to JerryScript on %d port", debugger.port)
+16 -5
View File
@@ -22,6 +22,8 @@ import select
import struct
import sys
from jerry_client_websocket import WebSocket
from jerry_client_rawpacket import RawPacket
from jerry_client_tcp import Socket
# Expected debugger protocol version.
JERRY_DEBUGGER_VERSION = 8
@@ -134,7 +136,8 @@ def arguments_parse():
help="set exception config, usage 1: [Enable] or 0: [Disable]")
parser.add_argument("--client-source", action="store", default=[], type=str, nargs="+",
help="specify a javascript source file to execute")
parser.add_argument("--channel", choices=["websocket", "rawpacket"], default="websocket",
help="specify the communication channel (default: %(default)s)")
args = parser.parse_args()
if args.verbose:
@@ -262,8 +265,8 @@ class DebuggerAction(object):
class JerryDebugger(object):
# pylint: disable=too-many-instance-attributes,too-many-statements,too-many-public-methods,no-self-use
def __init__(self, address):
# pylint: disable=too-many-instance-attributes,too-many-statements,too-many-public-methods,no-self-use,redefined-variable-type
def __init__(self, address, channel):
if ":" not in address:
self.host = address
@@ -301,8 +304,15 @@ class JerryDebugger(object):
self.non_interactive = False
self.current_out = b""
self.current_log = b""
self.channel = None
self.channel = WebSocket(address=(self.host, self.port))
protocol = Socket()
if channel == "websocket":
self.channel = WebSocket(address=(self.host, self.port), protocol=protocol)
elif channel == "rawpacket":
self.channel = RawPacket(address=(self.host, self.port), protocol=protocol)
else:
raise Exception("Unsupported communication channel")
config_size = 8
# The server will send the configuration message after connection established
@@ -342,7 +352,8 @@ class JerryDebugger(object):
logging.debug("Compressed pointer size: %d", self.cp_size)
def __del__(self):
self.channel.close()
if self.channel is not None:
self.channel.close()
def _exec_command(self, command_id):
self.send_command(command_id)
+91
View File
@@ -0,0 +1,91 @@
#!/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.
import struct
MAX_BUFFER_SIZE = 256
class RawPacket(object):
""" Simplified transmission layer. """
def __init__(self, address, protocol):
self.protocol = protocol
self.data_buffer = b""
self.address = address
def connect(self, config_size):
""" Create connection. """
self.protocol.connect(self.address)
self.data_buffer = b""
# It will return with the Network configurations, which has the following struct:
# header [1] - size[1]
# configuration [config_size]
len_expected = config_size + 1
while len(self.data_buffer) < len_expected:
self.data_buffer += self.protocol.receive_data()
expected = struct.pack("B", config_size)
if self.data_buffer[0:1] != expected:
raise Exception("Unexpected configuration")
result = self.data_buffer[1:len_expected]
self.data_buffer = self.data_buffer[len_expected:]
return result
def close(self):
""" Close connection. """
self.protocol.close()
def send_message(self, _, data):
""" Send message. """
msg_size = len(data)
while msg_size > 0:
bytes_send = self.protocol.send_data(data)
if bytes_send < msg_size:
data = data[bytes_send:]
msg_size -= bytes_send
def get_message(self, blocking):
""" Receive message. """
# Connection was closed
if self.data_buffer is None:
return None
while True:
if len(self.data_buffer) >= 1:
size = ord(self.data_buffer[0])
if size == 0:
raise Exception("Unexpected data frame")
if len(self.data_buffer) >= size + 1:
result = self.data_buffer[1:size + 1]
self.data_buffer = self.data_buffer[size + 1:]
return result
if not blocking and not self.protocol.ready():
return b''
received_data = self.protocol.receive_data(MAX_BUFFER_SIZE)
if not received_data:
return None
self.data_buffer += received_data
+1 -2
View File
@@ -15,14 +15,13 @@
# limitations under the License.
import struct
from jerry_client_tcp import Socket
MAX_BUFFER_SIZE = 128
WEBSOCKET_BINARY_FRAME = 2
WEBSOCKET_FIN_BIT = 0x80
class WebSocket(object):
def __init__(self, address, protocol=Socket()):
def __init__(self, address, protocol):
self.data_buffer = b""
self.protocol = protocol
+159
View File
@@ -0,0 +1,159 @@
/* 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 "jerryscript-ext/debugger.h"
#include "jext-common.h"
#ifdef JERRY_DEBUGGER
/* A simplified transmission layer. */
/**
* Size of the raw packet header.
*/
#define JERRYX_DEBUGGER_RAWPACKET_HEADER_SIZE 1
/**
* Maximum message size with 1 byte size field.
*/
#define JERRYX_DEBUGGER_RAWPACKET_ONE_BYTE_LEN_MAX 255
/**
* Header for incoming packets.
*/
typedef struct
{
uint8_t size; /**< size of the message */
} jerryx_rawpacket_receive_header_t;
/**
* Close a tcp connection.
*/
static void
jerryx_debugger_rp_close (jerry_debugger_transport_header_t *header_p) /**< header for the transport interface */
{
JERRYX_ASSERT (!jerry_debugger_transport_is_connected ());
jerry_heap_free ((void *) header_p, sizeof (jerry_debugger_transport_header_t));
} /* jerryx_debugger_rp_close */
/**
* Send data over a simple raw packet connection.
*
* @return true - if the data has been sent successfully
* false - otherwise
*/
static bool
jerryx_debugger_rp_send (jerry_debugger_transport_header_t *header_p, /**< header for the transport interface */
uint8_t *message_p, /**< message to be sent */
size_t message_length) /**< message length in bytes */
{
JERRYX_ASSERT (message_length <= JERRYX_DEBUGGER_RAWPACKET_ONE_BYTE_LEN_MAX);
message_p[-1] = (uint8_t) message_length;
return header_p->next_p->send (header_p->next_p, message_p - 1, message_length + 1);
} /* jerryx_debugger_rp_send */
/**
* Receive data from a rawpacket connection.
*
* @return true - if data has been received successfully
* false - otherwise
*/
static bool
jerryx_debugger_rp_receive (jerry_debugger_transport_header_t *header_p, /**< header for the transport interface */
jerry_debugger_transport_receive_context_t *receive_context_p) /**< receive context */
{
if (!header_p->next_p->receive (header_p->next_p, receive_context_p))
{
return false;
}
if (receive_context_p->message_p == NULL)
{
return true;
}
JERRYX_ASSERT (receive_context_p->message_length >= sizeof (jerryx_rawpacket_receive_header_t));
uint8_t *message_p = receive_context_p->message_p;
size_t message_length = (size_t) (message_p[0]);
size_t message_total_length = receive_context_p->message_total_length;
if (message_total_length == 0)
{
size_t new_total_length = message_length + sizeof (jerryx_rawpacket_receive_header_t);
receive_context_p->message_total_length = new_total_length;
}
else
{
/* Datagram packet. */
JERRYX_ASSERT (receive_context_p->message_length == (message_length + sizeof (jerryx_rawpacket_receive_header_t)));
}
message_p += sizeof (jerryx_rawpacket_receive_header_t);
receive_context_p->message_p = message_p;
receive_context_p->message_length = message_length;
return true;
} /* jerryx_debugger_rp_receive */
/**
* Initialize a simple raw packet transmission layer.
*
* @return true - if the connection succeeded
* false - otherwise
*/
bool
jerryx_debugger_rp_create (void)
{
const size_t interface_size = sizeof (jerry_debugger_transport_header_t);
jerry_debugger_transport_header_t *header_p;
header_p = (jerry_debugger_transport_header_t *) jerry_heap_alloc (interface_size);
if (!header_p)
{
return false;
}
header_p->close = jerryx_debugger_rp_close;
header_p->send = jerryx_debugger_rp_send;
header_p->receive = jerryx_debugger_rp_receive;
jerry_debugger_transport_add (header_p,
JERRYX_DEBUGGER_RAWPACKET_HEADER_SIZE,
JERRYX_DEBUGGER_RAWPACKET_ONE_BYTE_LEN_MAX,
JERRYX_DEBUGGER_RAWPACKET_HEADER_SIZE,
JERRYX_DEBUGGER_RAWPACKET_ONE_BYTE_LEN_MAX);
return true;
} /* jerryx_debugger_rp_create */
#else /* !JERRY_DEBUGGER */
/**
* Dummy function when debugger is disabled.
*
* @return false
*/
bool
jerryx_debugger_rp_create (void)
{
return false;
} /* jerryx_debugger_rp_create */
#endif /* JERRY_DEBUGGER */
@@ -35,6 +35,7 @@ bool jerryx_debugger_tcp_create (uint16_t port);
* Message encoding interfaces.
*/
bool jerryx_debugger_ws_create (void);
bool jerryx_debugger_rp_create (void);
#ifdef __cplusplus
}
+35 -7
View File
@@ -317,6 +317,7 @@ typedef enum
OPT_SHOW_RE_OP,
OPT_DEBUG_SERVER,
OPT_DEBUG_PORT,
OPT_DEBUG_CHANNEL,
OPT_DEBUGGER_WAIT_SOURCE,
OPT_EXEC_SNAP,
OPT_EXEC_SNAP_FUNC,
@@ -346,6 +347,8 @@ static const cli_opt_t main_opts[] =
.help = "start debug server and wait for a connecting client"),
CLI_OPT_DEF (.id = OPT_DEBUG_PORT, .longopt = "debug-port", .meta = "NUM",
.help = "debug server port (default: 5001)"),
CLI_OPT_DEF (.id = OPT_DEBUG_CHANNEL, .longopt = "debug-channel", .meta = "[websocket|rawpacket]",
.help = "Specify the debugger transmission channel (default: websocket)"),
CLI_OPT_DEF (.id = OPT_DEBUGGER_WAIT_SOURCE, .longopt = "debugger-wait-source",
.help = "wait for an executable source from the client"),
CLI_OPT_DEF (.id = OPT_EXEC_SNAP, .longopt = "exec-snapshot", .meta = "FILE",
@@ -418,14 +421,23 @@ context_alloc (size_t size,
*/
static void
init_engine (jerry_init_flag_t flags, /**< initialized flags for the engine */
bool debug_server, /**< enable the debugger init or not */
char *debug_channel, /**< enable the debugger init or not */
uint16_t debug_port) /**< the debugger port */
{
jerry_init (flags);
if (debug_server)
if (strcmp (debug_channel, ""))
{
jerryx_debugger_after_connect (jerryx_debugger_tcp_create (debug_port)
&& jerryx_debugger_ws_create ());
bool tcp_created = jerryx_debugger_tcp_create (debug_port);
if (!strcmp (debug_channel, "rawpacket"))
{
jerryx_debugger_after_connect (tcp_created && jerryx_debugger_rp_create ());
}
else
{
assert (!strcmp (debug_channel, "websocket"));
jerryx_debugger_after_connect (tcp_created && jerryx_debugger_ws_create ());
}
}
register_js_function ("assert", jerryx_handler_assert);
@@ -451,6 +463,7 @@ main (int argc,
bool start_debug_server = false;
uint16_t debug_port = 5001;
char *debug_channel = "websocket";
bool is_repl_mode = false;
bool is_wait_mode = false;
@@ -519,6 +532,16 @@ main (int argc,
}
break;
}
case OPT_DEBUG_CHANNEL:
{
if (check_feature (JERRY_FEATURE_DEBUGGER, cli_state.arg))
{
debug_channel = (char *) cli_consume_string (&cli_state);
check_usage (!strcmp (debug_channel, "websocket") || !strcmp (debug_channel, "rawpacket"),
argv[0], "Error: invalid value for --debug-channel: ", cli_state.arg);
}
break;
}
case OPT_DEBUGGER_WAIT_SOURCE:
{
if (check_feature (JERRY_FEATURE_DEBUGGER, cli_state.arg))
@@ -612,7 +635,12 @@ main (int argc,
#endif /* JERRY_ENABLE_EXTERNAL_CONTEXT */
init_engine (flags, start_debug_server, debug_port);
if (!start_debug_server)
{
debug_channel = "";
}
init_engine (flags, debug_channel, debug_port);
jerry_value_t ret_value = jerry_create_undefined ();
@@ -730,7 +758,7 @@ main (int argc,
break;
}
init_engine (flags, true, debug_port);
init_engine (flags, debug_channel, debug_port);
ret_value = jerry_create_undefined ();
}
@@ -774,7 +802,7 @@ main (int argc,
jerry_cleanup ();
init_engine (flags, true, debug_port);
init_engine (flags, debug_channel, debug_port);
ret_value = jerry_create_undefined ();
}
+15 -13
View File
@@ -326,21 +326,23 @@ def run_jerry_debugger_tests(options):
if ret_build:
break
for test_file in os.listdir(settings.DEBUGGER_TESTS_DIR):
if test_file.endswith(".cmd"):
test_case, _ = os.path.splitext(test_file)
test_case_path = os.path.join(settings.DEBUGGER_TESTS_DIR, test_case)
test_cmd = [
settings.DEBUGGER_TEST_RUNNER_SCRIPT,
get_binary_path(build_dir_path),
settings.DEBUGGER_CLIENT_SCRIPT,
os.path.relpath(test_case_path, settings.PROJECT_DIR)
]
for channel in ["websocket", "rawpacket"]:
for test_file in os.listdir(settings.DEBUGGER_TESTS_DIR):
if test_file.endswith(".cmd"):
test_case, _ = os.path.splitext(test_file)
test_case_path = os.path.join(settings.DEBUGGER_TESTS_DIR, test_case)
test_cmd = [
settings.DEBUGGER_TEST_RUNNER_SCRIPT,
get_binary_path(build_dir_path),
channel,
settings.DEBUGGER_CLIENT_SCRIPT,
os.path.relpath(test_case_path, settings.PROJECT_DIR)
]
if job.test_args:
test_cmd.extend(job.test_args)
if job.test_args:
test_cmd.extend(job.test_args)
ret_test |= run_check(test_cmd)
ret_test |= run_check(test_cmd)
return ret_build | ret_test
+7 -6
View File
@@ -15,8 +15,9 @@
# limitations under the License.
JERRY=$1
DEBUGGER_CLIENT=$2
TEST_CASE=$3
CHANNEL=$2
DEBUGGER_CLIENT=$3
TEST_CASE=$4
CLIENT_ARGS=""
TERM_NORMAL='\033[0m'
@@ -24,14 +25,14 @@ TERM_RED='\033[1;31m'
TERM_GREEN='\033[1;32m'
if [[ $TEST_CASE == *"client_source"* ]]; then
START_DEBUG_SERVER="${JERRY} --start-debug-server --debugger-wait-source &"
START_DEBUG_SERVER="${JERRY} --start-debug-server --debug-channel ${CHANNEL} --debugger-wait-source &"
if [[ $TEST_CASE == *"client_source_multiple"* ]]; then
CLIENT_ARGS="--client-source ${TEST_CASE}_2.js ${TEST_CASE}_1.js"
else
CLIENT_ARGS="--client-source ${TEST_CASE}.js"
fi
else
START_DEBUG_SERVER="${JERRY} ${TEST_CASE}.js --start-debug-server &"
START_DEBUG_SERVER="${JERRY} ${TEST_CASE}.js --start-debug-server --debug-channel ${CHANNEL} &"
fi
echo "$START_DEBUG_SERVER"
@@ -40,11 +41,11 @@ sleep 1s
RESULT_TEMP=`mktemp ${TEST_CASE}.out.XXXXXXXXXX`
(cat "${TEST_CASE}.cmd" | ${DEBUGGER_CLIENT} --non-interactive ${CLIENT_ARGS}) >${RESULT_TEMP} 2>&1
(cat "${TEST_CASE}.cmd" | ${DEBUGGER_CLIENT} --channel ${CHANNEL} --non-interactive ${CLIENT_ARGS}) >${RESULT_TEMP} 2>&1
if [[ $TEST_CASE == *"restart"* ]]; then
CONTINUE_CASE=$(sed "s/restart/continue/g" <<< "$TEST_CASE")
(cat "${CONTINUE_CASE}.cmd" | ${DEBUGGER_CLIENT} --non-interactive ${CLIENT_ARGS}) >>${RESULT_TEMP} 2>&1
(cat "${CONTINUE_CASE}.cmd" | ${DEBUGGER_CLIENT} --channel ${CHANNEL} --non-interactive ${CLIENT_ARGS}) >>${RESULT_TEMP} 2>&1
fi
diff -U0 ${TEST_CASE}.expected ${RESULT_TEMP}