[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:
+2
-1
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,6 +352,7 @@ class JerryDebugger(object):
|
||||
logging.debug("Compressed pointer size: %d", self.cp_size)
|
||||
|
||||
def __del__(self):
|
||||
if self.channel is not None:
|
||||
self.channel.close()
|
||||
|
||||
def _exec_command(self, command_id):
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 ();
|
||||
}
|
||||
|
||||
@@ -326,6 +326,7 @@ def run_jerry_debugger_tests(options):
|
||||
if ret_build:
|
||||
break
|
||||
|
||||
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)
|
||||
@@ -333,6 +334,7 @@ def run_jerry_debugger_tests(options):
|
||||
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)
|
||||
]
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user