Akos Kiss 18283d22c0 Make debugger port runtime configurable
Convert debug server port from a compile-time constant to a context
variable. This enables each engine instance (either running in the
same process or in different processes) to listen for connections
at different ports, i.e., multiple engines can be debugged at the
same time on the same machine.

JerryScript-DCO-1.0-Signed-off-by: Akos Kiss akiss@inf.u-szeged.hu
2017-07-06 07:08:00 +09:00

552 lines
15 KiB
C

/* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "debugger.h"
#include "jcontext.h"
#include "jerryscript-port.h"
#ifdef JERRY_DEBUGGER
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
/**
* 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 (debugger_flags) & JERRY_DEBUGGER_CONNECTED);
JERRY_CONTEXT (debugger_flags) = (uint8_t) JERRY_DEBUGGER_VM_IGNORE;
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 (debugger_flags) & JERRY_DEBUGGER_CONNECTED);
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 (void)
{
int server_socket;
struct sockaddr_in addr;
socklen_t sin_size = sizeof (struct sockaddr_in);
addr.sin_family = AF_INET;
addr.sin_port = htons (JERRY_CONTEXT (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);
JERRY_CONTEXT (debugger_flags) = (uint8_t) (JERRY_CONTEXT (debugger_flags) | JERRY_DEBUGGER_CONNECTED);
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_RECEIVE_SIZE))
{
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_flags) = (uint8_t) (JERRY_CONTEXT (debugger_flags) | JERRY_DEBUGGER_VM_STOP);
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 */
JERRY_STATIC_ASSERT (JERRY_DEBUGGER_MAX_RECEIVE_SIZE < 126,
maximum_debug_message_receive_size_must_be_smaller_than_126);
/**
* Receive message from the client.
*
* Note:
* If the function returns with true, the value of
* JERRY_DEBUGGER_VM_STOP flag should be ignored.
*
* @return true - if execution should be resumed,
* false - otherwise
*/
bool
jerry_debugger_receive (void)
{
JERRY_ASSERT (JERRY_CONTEXT (debugger_flags) & JERRY_DEBUGGER_CONNECTED);
JERRY_CONTEXT (debugger_message_delay) = JERRY_DEBUGGER_MESSAGE_FREQUENCY;
uint8_t *recv_buffer_p = JERRY_CONTEXT (debugger_receive_buffer);
bool resume_exec = false;
uint8_t expected_message_type = 0;
void *message_data = NULL;
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 (errno != EWOULDBLOCK)
{
jerry_debugger_close_connection_tcp (true);
return true;
}
byte_recv = 0;
}
offset += (uint32_t) byte_recv;
JERRY_CONTEXT (debugger_receive_buffer_offset) = (uint16_t) offset;
if (offset < sizeof (jerry_debugger_receive_header_t))
{
if (expected_message_type != 0)
{
continue;
}
return resume_exec;
}
if ((recv_buffer_p[0] & ~JERRY_DEBUGGER_WEBSOCKET_OPCODE_MASK) != JERRY_DEBUGGER_WEBSOCKET_FIN_BIT
|| (recv_buffer_p[1] & JERRY_DEBUGGER_WEBSOCKET_LENGTH_MASK) > JERRY_DEBUGGER_MAX_RECEIVE_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 (offset < message_total_size)
{
if (expected_message_type != 0)
{
continue;
}
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;
}
}
/* The jerry_debugger_process_message function is inlined
* so passing these arguments is essentially free. */
if (!jerry_debugger_process_message (recv_buffer_p + sizeof (jerry_debugger_receive_header_t),
message_size,
&resume_exec,
&expected_message_type,
&message_data))
{
return true;
}
if (message_total_size < offset)
{
memmove (recv_buffer_p,
recv_buffer_p + message_total_size,
offset - message_total_size);
}
JERRY_CONTEXT (debugger_receive_buffer_offset) = (uint16_t) (offset - message_total_size);
}
} /* jerry_debugger_receive */
#endif /* JERRY_DEBUGGER */