Initial version of JerryScript debugger (#1557)
The debugger supports setting breakpoints, execution control (step, next, continue) and getting backtrace. The communication is WebSocket-based, so a browser can communicate with JerryScript without any intermediate application. JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com JerryScript-DCO-1.0-Signed-off-by: Levente Orban orbanl@inf.u-szeged.hu
This commit is contained in:
committed by
Tilmann Scheller
parent
453066fcf1
commit
025a99ccbb
@@ -0,0 +1,949 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>
|
||||
JerryScript HTML (WebSocket) Debugger Client
|
||||
</title>
|
||||
<style>
|
||||
body {
|
||||
text-align: center;
|
||||
}
|
||||
textarea {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
input {
|
||||
margin-top: 10px;
|
||||
width: 657px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>JerryScript HTML (WebSocket) Debugger Client</h2>
|
||||
|
||||
<textarea id="log" rows="20" cols="80"></textarea><br>
|
||||
|
||||
Getting help: type 'help' in the command line below.<br>
|
||||
|
||||
<input id="command" type="text" onkeypress="debuggerCommand(event); return true;">
|
||||
<script>
|
||||
var JERRY_DEBUGGER_CONFIGURATION = 1;
|
||||
var JERRY_DEBUGGER_PARSE_ERROR = 2;
|
||||
var JERRY_DEBUGGER_BYTE_CODE_CP = 3;
|
||||
var JERRY_DEBUGGER_PARSE_FUNCTION = 4;
|
||||
var JERRY_DEBUGGER_BREAKPOINT_LIST = 5;
|
||||
var JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST = 6;
|
||||
var JERRY_DEBUGGER_RESOURCE_NAME = 7;
|
||||
var JERRY_DEBUGGER_RESOURCE_NAME_END = 8;
|
||||
var JERRY_DEBUGGER_FUNCTION_NAME = 9;
|
||||
var JERRY_DEBUGGER_FUNCTION_NAME_END = 10;
|
||||
var JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP = 11;
|
||||
var JERRY_DEBUGGER_BREAKPOINT_HIT = 12;
|
||||
var JERRY_DEBUGGER_BACKTRACE = 13;
|
||||
var JERRY_DEBUGGER_BACKTRACE_END = 14;
|
||||
|
||||
var JERRY_DEBUGGER_FREE_BYTE_CODE_CP = 1;
|
||||
var JERRY_DEBUGGER_UPDATE_BREAKPOINT = 2;
|
||||
var JERRY_DEBUGGER_STOP = 3;
|
||||
var JERRY_DEBUGGER_CONTINUE = 4;
|
||||
var JERRY_DEBUGGER_STEP = 5;
|
||||
var JERRY_DEBUGGER_NEXT = 6;
|
||||
var JERRY_DEBUGGER_GET_BACKTRACE = 7;
|
||||
|
||||
var textBox = document.getElementById("log");
|
||||
var commandBox = document.getElementById("command");
|
||||
var socket = null;
|
||||
|
||||
textBox.value = ""
|
||||
commandBox.value = "connect localhost"
|
||||
|
||||
function appendLog(str)
|
||||
{
|
||||
textBox.value += str + "\n";
|
||||
}
|
||||
|
||||
var debuggerObj = null;
|
||||
|
||||
function DebuggerClient(ipAddr)
|
||||
{
|
||||
appendLog("ws://" + ipAddr + ":5001/jerry-debugger");
|
||||
|
||||
var parseObj = null;
|
||||
var maxMessageSize = 0;
|
||||
var cpointerSize = 0;
|
||||
var littleEndian = true;
|
||||
var functions = { };
|
||||
var lineList = new Multimap();
|
||||
var activeBreakpoints = { };
|
||||
var nextBreakpointIndex = 1;
|
||||
var backtraceFrame = 0;
|
||||
|
||||
function assert(expr)
|
||||
{
|
||||
if (!expr)
|
||||
{
|
||||
throw new Error("Assertion failed.");
|
||||
}
|
||||
}
|
||||
|
||||
/* Concat the two arrays. The first byte (opcode) of nextArray is ignored. */
|
||||
function concatUint8Arrays(baseArray, nextArray)
|
||||
{
|
||||
if (nextArray.byteLength <= 1)
|
||||
{
|
||||
/* Nothing to append. */
|
||||
return baseArray;
|
||||
}
|
||||
|
||||
if (!baseArray)
|
||||
{
|
||||
/* Cut the first byte (opcode). */
|
||||
return nextArray.slice(1);
|
||||
}
|
||||
|
||||
var baseLength = baseArray.byteLength;
|
||||
var nextLength = nextArray.byteLength - 1;
|
||||
|
||||
var result = new Uint8Array(baseArray.byteLength + nextArray.byteLength);
|
||||
result.set(nextArray, baseArray.byteLength - 1);
|
||||
|
||||
/* This set overwrites the opcode. */
|
||||
result.set(baseArray);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function cesu8ToString(array)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var length = array.byteLength;
|
||||
|
||||
var i = 0;
|
||||
var result = "";
|
||||
|
||||
while (i < length)
|
||||
{
|
||||
var chr = array[i];
|
||||
|
||||
++i;
|
||||
|
||||
if (chr >= 0x7f)
|
||||
{
|
||||
if (chr & 0x20)
|
||||
{
|
||||
/* Three byte long character. */
|
||||
chr = ((chr & 0xf) << 12) | ((array[i] & 0x3f) << 6) | (array[i + 1] & 0x3f);
|
||||
i += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Two byte long character. */
|
||||
chr = ((chr & 0x1f) << 6) | (array[i] & 0x3f);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
result += String.fromCharCode(chr);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function breakpointToString(breakpoint)
|
||||
{
|
||||
var name = breakpoint.func.name;
|
||||
|
||||
var result = breakpoint.func.resource;
|
||||
|
||||
if (!result)
|
||||
{
|
||||
result = "<unknown>";
|
||||
}
|
||||
|
||||
result += ":" + breakpoint.line;
|
||||
|
||||
if (breakpoint.func.name)
|
||||
{
|
||||
result += " (in " + breakpoint.func.name + ")";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function Multimap()
|
||||
{
|
||||
/* Each item is an array of items. */
|
||||
|
||||
var map = { };
|
||||
|
||||
this.get = function(key)
|
||||
{
|
||||
var item = map[key];
|
||||
return item ? item : [ ];
|
||||
}
|
||||
|
||||
this.insert = function(key, value)
|
||||
{
|
||||
var item = map[key];
|
||||
|
||||
if (item)
|
||||
{
|
||||
item.push(value);
|
||||
return;
|
||||
}
|
||||
|
||||
map[key] = [ value ];
|
||||
}
|
||||
|
||||
this.delete = function(key, value)
|
||||
{
|
||||
var array = map[key];
|
||||
|
||||
assert(array);
|
||||
|
||||
var newLength = array.length - 1;
|
||||
var i = array.indexOf(value);
|
||||
|
||||
assert(i != -1);
|
||||
|
||||
array.splice(i, 1);
|
||||
array.length = newLength;
|
||||
}
|
||||
}
|
||||
|
||||
var socket = new WebSocket("ws://localhost:5001/jerry-debugger");
|
||||
socket.binaryType = 'arraybuffer';
|
||||
|
||||
function abortConnection(message)
|
||||
{
|
||||
assert(socket && debuggerObj);
|
||||
|
||||
socket.close();
|
||||
socket = null;
|
||||
debuggerObj = null;
|
||||
|
||||
appendLog("Abort connection: " + message);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
socket.onerror = function(event)
|
||||
{
|
||||
if (socket)
|
||||
{
|
||||
socket = null;
|
||||
debuggerObj = null;
|
||||
appendLog("Connection closed.");
|
||||
}
|
||||
}
|
||||
socket.onclose = socket.onerror;
|
||||
|
||||
socket.onopen = function(event)
|
||||
{
|
||||
appendLog("Connection created.");
|
||||
}
|
||||
|
||||
function getFormatSize(format)
|
||||
{
|
||||
var length = 0;
|
||||
|
||||
for (var i = 0; i < format.length; i++)
|
||||
{
|
||||
if (format[i] == "B")
|
||||
{
|
||||
length++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (format[i] == "C")
|
||||
{
|
||||
length += cpointerSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(format[i] == "I")
|
||||
|
||||
length += 4;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
function decodeMessage(format, message, offset)
|
||||
{
|
||||
/* Format: B=byte I=int32 C=cpointer.
|
||||
* Returns an array of decoded numbers. */
|
||||
|
||||
var result = []
|
||||
var value;
|
||||
|
||||
if (!offset)
|
||||
{
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
if (offset + getFormatSize(format) > message.byteLength)
|
||||
{
|
||||
abortConnection("received message too short.");
|
||||
}
|
||||
|
||||
for (var i = 0; i < format.length; i++)
|
||||
{
|
||||
if (format[i] == "B")
|
||||
{
|
||||
result.push(message[offset])
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (format[i] == "C" && cpointerSize == 2)
|
||||
{
|
||||
if (littleEndian)
|
||||
{
|
||||
value = message[offset] | (message[offset + 1] << 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = (message[offset] << 8) | message[offset + 1];
|
||||
}
|
||||
|
||||
result.push(value);
|
||||
offset += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(format[i] == "I" || (format[i] == "C" && cpointerSize == 4));
|
||||
|
||||
if (littleEndian)
|
||||
{
|
||||
value = (message[offset] | (message[offset + 1] << 8)
|
||||
| (message[offset + 2] << 16) | (message[offset + 3] << 24));
|
||||
}
|
||||
else
|
||||
{
|
||||
value = ((message[offset] << 24) | (message[offset + 1] << 16)
|
||||
| (message[offset + 2] << 8) | message[offset + 3] << 24);
|
||||
}
|
||||
|
||||
result.push(value);
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function encodeMessage(format, values)
|
||||
{
|
||||
/* Format: B=byte I=int32 C=cpointer.
|
||||
* Sends a message after the encoding is completed. */
|
||||
|
||||
var length = getFormatSize(format);
|
||||
var message = new Uint8Array(length);
|
||||
var offset = 0;
|
||||
|
||||
for (var i = 0; i < format.length; i++)
|
||||
{
|
||||
var value = values[i];
|
||||
|
||||
if (format[i] == "B")
|
||||
{
|
||||
message[offset] = value;
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (format[i] == "C" && cpointerSize == 2)
|
||||
{
|
||||
if (littleEndian)
|
||||
{
|
||||
message[offset] = value & 0xff;
|
||||
message[offset + 1] = (value >> 8) & 0xff;
|
||||
}
|
||||
else
|
||||
{
|
||||
message[offset] = (value >> 8) & 0xff;
|
||||
message[offset + 1] = value & 0xff;
|
||||
}
|
||||
|
||||
offset += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (littleEndian)
|
||||
{
|
||||
message[offset] = value & 0xff;
|
||||
message[offset + 1] = (value >> 8) & 0xff;
|
||||
message[offset + 2] = (value >> 16) & 0xff;
|
||||
message[offset + 3] = (value >> 24) & 0xff;
|
||||
}
|
||||
else
|
||||
{
|
||||
message[offset] = (value >> 24) & 0xff;
|
||||
message[offset + 1] = (value >> 16) & 0xff;
|
||||
message[offset + 2] = (value >> 8) & 0xff;
|
||||
message[offset + 3] = value & 0xff;
|
||||
}
|
||||
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
socket.send(message);
|
||||
}
|
||||
|
||||
this.encodeMessage = encodeMessage;
|
||||
|
||||
function ParseSource()
|
||||
{
|
||||
var resourceName = null;
|
||||
var functionName = null;
|
||||
var stack = [ { name: '', lines: [], offsets: [] } ];
|
||||
var newFunctions = [ ];
|
||||
|
||||
this.receive = function(message)
|
||||
{
|
||||
switch (message[0])
|
||||
{
|
||||
case JERRY_DEBUGGER_PARSE_ERROR:
|
||||
{
|
||||
/* Parse error occured in JerryScript. */
|
||||
parseObj = null;
|
||||
return;
|
||||
}
|
||||
|
||||
case JERRY_DEBUGGER_RESOURCE_NAME:
|
||||
case JERRY_DEBUGGER_RESOURCE_NAME_END:
|
||||
{
|
||||
if ((typeof resourceName) == "string")
|
||||
{
|
||||
abortConnection("unexpected message.");
|
||||
}
|
||||
|
||||
resourceName = concatUint8Arrays(resourceName, message);
|
||||
|
||||
if (message[0] == JERRY_DEBUGGER_RESOURCE_NAME_END)
|
||||
{
|
||||
resourceName = cesu8ToString(resourceName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case JERRY_DEBUGGER_FUNCTION_NAME:
|
||||
case JERRY_DEBUGGER_FUNCTION_NAME_END:
|
||||
{
|
||||
functionName = concatUint8Arrays(functionName, message);
|
||||
return;
|
||||
}
|
||||
|
||||
case JERRY_DEBUGGER_PARSE_FUNCTION:
|
||||
{
|
||||
if (resourceName == null)
|
||||
{
|
||||
resourceName = "";
|
||||
}
|
||||
else if ((typeof resourceName) != "string")
|
||||
{
|
||||
abortConnection("unexpected message.");
|
||||
}
|
||||
|
||||
stack.push({ name: cesu8ToString(functionName), resource: resourceName, lines: [], offsets: [] });
|
||||
functionName = null;
|
||||
return;
|
||||
}
|
||||
|
||||
case JERRY_DEBUGGER_BREAKPOINT_LIST:
|
||||
case JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST:
|
||||
{
|
||||
var array;
|
||||
|
||||
if (message.byteLength < 1 + 4)
|
||||
{
|
||||
abortConnection("message too short.");
|
||||
}
|
||||
|
||||
if (message[0] == JERRY_DEBUGGER_BREAKPOINT_LIST)
|
||||
{
|
||||
array = stack[stack.length - 1].lines;
|
||||
}
|
||||
else
|
||||
{
|
||||
array = stack[stack.length - 1].offsets;
|
||||
}
|
||||
|
||||
for (var i = 1; i < message.byteLength; i += 4)
|
||||
{
|
||||
array.push(decodeMessage("I", message, i)[0]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case JERRY_DEBUGGER_BYTE_CODE_CP:
|
||||
{
|
||||
var func = stack.pop();
|
||||
func.byte_code_cp = decodeMessage("C", message, 1)[0];
|
||||
|
||||
lines = {}
|
||||
offsets = {}
|
||||
|
||||
func.firstLine = (func.lines.length > 0) ? func.lines[0] : -1;
|
||||
|
||||
for (var i = 0; i < func.lines.length; i++)
|
||||
{
|
||||
var breakpoint = { line: func.lines[i], offset: func.offsets[i], func: func, activeIndex: -1 };
|
||||
|
||||
lines[breakpoint.line] = breakpoint;
|
||||
offsets[breakpoint.offset] = breakpoint;
|
||||
}
|
||||
|
||||
func.lines = lines;
|
||||
func.offsets = offsets;
|
||||
|
||||
newFunctions.push(func);
|
||||
|
||||
if (stack.length > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
func.resource = resourceName;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
abortConnection("unexpected message.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < newFunctions.length; i++)
|
||||
{
|
||||
var func = newFunctions[i];
|
||||
|
||||
functions[func.byte_code_cp] = func
|
||||
|
||||
for (var j in func.lines)
|
||||
{
|
||||
lineList.insert(j, func);
|
||||
}
|
||||
}
|
||||
|
||||
parseObj = null;
|
||||
}
|
||||
}
|
||||
|
||||
socket.onmessage = function(event)
|
||||
{
|
||||
var message = new Uint8Array(event.data);
|
||||
|
||||
if (message.byteLength < 1)
|
||||
{
|
||||
abortConnection("message too short.");
|
||||
}
|
||||
|
||||
if (cpointerSize == 0)
|
||||
{
|
||||
if (message[0] != JERRY_DEBUGGER_CONFIGURATION
|
||||
|| message.byteLength != 4)
|
||||
{
|
||||
abortConnection("the first message must be configuration.");
|
||||
}
|
||||
|
||||
maxMessageSize = message[1]
|
||||
cpointerSize = message[2]
|
||||
littleEndian = (message[3] != 0);
|
||||
|
||||
if (cpointerSize != 2 && cpointerSize != 4)
|
||||
{
|
||||
abortConnection("compressed pointer must be 2 or 4 byte long.");
|
||||
}
|
||||
|
||||
config = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (parseObj)
|
||||
{
|
||||
parseObj.receive(message)
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message[0])
|
||||
{
|
||||
case JERRY_DEBUGGER_PARSE_ERROR:
|
||||
case JERRY_DEBUGGER_BYTE_CODE_CP:
|
||||
case JERRY_DEBUGGER_PARSE_FUNCTION:
|
||||
case JERRY_DEBUGGER_BREAKPOINT_LIST:
|
||||
case JERRY_DEBUGGER_RESOURCE_NAME:
|
||||
case JERRY_DEBUGGER_RESOURCE_NAME_END:
|
||||
case JERRY_DEBUGGER_FUNCTION_NAME:
|
||||
case JERRY_DEBUGGER_FUNCTION_NAME_END:
|
||||
{
|
||||
parseObj = new ParseSource()
|
||||
parseObj.receive(message)
|
||||
return;
|
||||
}
|
||||
|
||||
case JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP:
|
||||
{
|
||||
var byte_code_cp = decodeMessage("C", message, 1)[0];
|
||||
var func = functions[byte_code_cp];
|
||||
|
||||
for (var i in func.lines)
|
||||
{
|
||||
lineList.delete(i, func);
|
||||
|
||||
var breakpoint = func.lines[i];
|
||||
|
||||
assert(i == breakpoint.line);
|
||||
|
||||
if (breakpoint.activeIndex >= 0)
|
||||
{
|
||||
delete activeBreakpoints[breakpoint.activeIndex];
|
||||
}
|
||||
}
|
||||
|
||||
delete functions[byte_code_cp];
|
||||
|
||||
message[0] = JERRY_DEBUGGER_FREE_BYTE_CODE_CP;
|
||||
socket.send(message);
|
||||
return;
|
||||
}
|
||||
|
||||
case JERRY_DEBUGGER_BREAKPOINT_HIT:
|
||||
{
|
||||
var breakpoint = decodeMessage("CI", message, 1);
|
||||
|
||||
breakpoint = functions[breakpoint[0]].offsets[breakpoint[1]];
|
||||
|
||||
breakpointIndex = "";
|
||||
|
||||
if (breakpoint.activeIndex >= 0)
|
||||
{
|
||||
breakpointIndex = "breakpoint:" + breakpoint.activeIndex + " ";
|
||||
}
|
||||
|
||||
appendLog("Stopped at " + breakpointIndex + breakpointToString(breakpoint));
|
||||
return;
|
||||
}
|
||||
|
||||
case JERRY_DEBUGGER_BACKTRACE:
|
||||
case JERRY_DEBUGGER_BACKTRACE_END:
|
||||
{
|
||||
for (var i = 1; i < message.byteLength; i += cpointerSize + 4)
|
||||
{
|
||||
var breakpoint = decodeMessage("CI", message, i);
|
||||
var func = functions[breakpoint[0]];
|
||||
var best_offset = -1;
|
||||
|
||||
for (var offset in func.offsets)
|
||||
{
|
||||
if (offset <= breakpoint[1] && offset > best_offset)
|
||||
{
|
||||
best_offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_offset >= 0)
|
||||
{
|
||||
breakpoint = func.offsets[best_offset];
|
||||
appendLog(" frame " + backtraceFrame + ": " + breakpointToString(breakpoint));
|
||||
}
|
||||
else if (func.name)
|
||||
{
|
||||
appendLog(" frame " + backtraceFrame + ": " + func.name + "()");
|
||||
}
|
||||
else
|
||||
{
|
||||
appendLog(" frame " + backtraceFrame + ": <unknown>()");
|
||||
}
|
||||
|
||||
++backtraceFrame;
|
||||
}
|
||||
|
||||
if (message[0] == JERRY_DEBUGGER_BACKTRACE_END)
|
||||
{
|
||||
backtraceFrame = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
abortConnection("unexpected message.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function insertBreakpoint(breakpoint)
|
||||
{
|
||||
if (breakpoint.activeIndex < 0)
|
||||
{
|
||||
breakpoint.activeIndex = nextBreakpointIndex;
|
||||
activeBreakpoints[nextBreakpointIndex] = breakpoint;
|
||||
nextBreakpointIndex++;
|
||||
|
||||
var values = [ JERRY_DEBUGGER_UPDATE_BREAKPOINT,
|
||||
1,
|
||||
breakpoint.func.byte_code_cp,
|
||||
breakpoint.offset ];
|
||||
|
||||
encodeMessage("BBCI", values);
|
||||
}
|
||||
|
||||
appendLog("Breakpoint " + breakpoint.activeIndex + " at " + breakpointToString(breakpoint));
|
||||
}
|
||||
|
||||
this.setBreakpoint = function(str)
|
||||
{
|
||||
line = /^(.+):([1-9][0-9]*)$/.exec(str);
|
||||
|
||||
if (line)
|
||||
{
|
||||
var functionList = lineList.get(line[2]);
|
||||
|
||||
for (var i = 0; i < functionList.length; ++i)
|
||||
{
|
||||
var func = functionList[i];
|
||||
var resource = func.resource;
|
||||
|
||||
if (resource == line[1]
|
||||
|| resource.endsWith("/" + line[1])
|
||||
|| resource.endsWith("\\" + line[1]))
|
||||
{
|
||||
insertBreakpoint(func.lines[line[2]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i in functions)
|
||||
{
|
||||
var func = functions[i];
|
||||
|
||||
if (func.name == str && func.firstLine >= 0)
|
||||
{
|
||||
insertBreakpoint(func.lines[func.firstLine]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.deleteBreakpoint = function(index)
|
||||
{
|
||||
breakpoint = activeBreakpoints[index];
|
||||
|
||||
if (!breakpoint)
|
||||
{
|
||||
appendLog("No breakpoint found with index " + index);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(breakpoint.activeIndex == index);
|
||||
|
||||
delete activeBreakpoints[index];
|
||||
breakpoint.activeIndex = -1;
|
||||
|
||||
var values = [ JERRY_DEBUGGER_UPDATE_BREAKPOINT,
|
||||
0,
|
||||
breakpoint.func.byte_code_cp,
|
||||
breakpoint.offset ];
|
||||
|
||||
encodeMessage("BBCI", values);
|
||||
|
||||
appendLog("Breakpoint " + index + " is deleted.");
|
||||
}
|
||||
|
||||
this.listBreakpoints = function()
|
||||
{
|
||||
appendLog("List of active breakpoints:");
|
||||
var found = false;
|
||||
|
||||
for (var i in activeBreakpoints)
|
||||
{
|
||||
appendLog(" breakpoint " + i + " at " + breakpointToString(activeBreakpoints[i]));
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
appendLog(" no active breakpoints");
|
||||
}
|
||||
}
|
||||
|
||||
this.dump = function()
|
||||
{
|
||||
for (var i in functions)
|
||||
{
|
||||
var func = functions[i];
|
||||
var resource = func.resource;
|
||||
|
||||
if (resource == '')
|
||||
{
|
||||
resource = "<unknown>";
|
||||
}
|
||||
|
||||
appendLog("Function 0x" + Number(i).toString(16) + " '" + func.name + "' at " + resource + ":" + func.firstLine);
|
||||
|
||||
for (var j in func.lines)
|
||||
{
|
||||
var active = "";
|
||||
|
||||
if (func.lines[j].active >= 0)
|
||||
{
|
||||
active = " (active: " + func.lines[j].active + ")";
|
||||
}
|
||||
|
||||
appendLog(" Breatpoint line: " + j + " at memory offset: " + func.lines[j].offset + active);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function debuggerCommand(event)
|
||||
{
|
||||
if (event.keyCode != 13)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var command = commandBox.value.trim();
|
||||
|
||||
args = /^([a-zA-Z]+)(?:\s+([^\s].*)|)$/.exec(command);
|
||||
|
||||
if (!args)
|
||||
{
|
||||
appendLog("Invalid command");
|
||||
document.getElementById("command").value = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!args[2])
|
||||
{
|
||||
args[2] = "";
|
||||
}
|
||||
|
||||
if (args[1] == "help")
|
||||
{
|
||||
appendLog("Debugger commands:\n" +
|
||||
" connect <IP address> - connect to server\n" +
|
||||
" break|b <file_name:line>|<function_name> - set breakpoint\n" +
|
||||
" delete|d <id> - delete breakpoint\n" +
|
||||
" list - list breakpoints\n" +
|
||||
" continue|c - continue execution\n" +
|
||||
" step|s - step-in execution\n" +
|
||||
" next|n - connect to server\n" +
|
||||
" backtrace|bt <max-depth> - get backtrace\n" +
|
||||
" dump - dump all breakpoint data");
|
||||
|
||||
commandBox.value = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args[1] == "connect")
|
||||
{
|
||||
if (debuggerObj)
|
||||
{
|
||||
appendLog("Debugger is connected");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args[2] == "")
|
||||
{
|
||||
appendLog("IP address expected");
|
||||
return true;
|
||||
}
|
||||
|
||||
appendLog("Connect to: " + args[2]);
|
||||
|
||||
debuggerObj = new DebuggerClient(args[2]);
|
||||
|
||||
commandBox.value = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!debuggerObj)
|
||||
{
|
||||
appendLog("Debugger is NOT connected");
|
||||
|
||||
commandBox.value = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
switch(args[1])
|
||||
{
|
||||
case "b":
|
||||
case "break":
|
||||
debuggerObj.setBreakpoint(args[2]);
|
||||
break;
|
||||
|
||||
case "d":
|
||||
case "delete":
|
||||
debuggerObj.deleteBreakpoint(args[2]);
|
||||
break;
|
||||
|
||||
case "stop":
|
||||
debuggerObj.encodeMessage("B", [ JERRY_DEBUGGER_STOP ]);
|
||||
break;
|
||||
|
||||
case "c":
|
||||
case "continue":
|
||||
debuggerObj.encodeMessage("B", [ JERRY_DEBUGGER_CONTINUE ]);
|
||||
break;
|
||||
|
||||
case "s":
|
||||
case "step":
|
||||
debuggerObj.encodeMessage("B", [ JERRY_DEBUGGER_STEP ]);
|
||||
break;
|
||||
|
||||
case "n":
|
||||
case "next":
|
||||
debuggerObj.encodeMessage("B", [ JERRY_DEBUGGER_NEXT ]);
|
||||
break;
|
||||
|
||||
case "bt":
|
||||
case "backtrace":
|
||||
max_depth = 0;
|
||||
|
||||
if (args[2])
|
||||
{
|
||||
if (/[1-9][0-9]*/.exec(args[2]))
|
||||
{
|
||||
max_depth = parseInt(args[2]);
|
||||
}
|
||||
else
|
||||
{
|
||||
appendLog("Invalid maximum depth argument.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
appendLog("Backtrace:");
|
||||
|
||||
debuggerObj.encodeMessage("BI", [ JERRY_DEBUGGER_GET_BACKTRACE, max_depth ]);
|
||||
break;
|
||||
|
||||
case "list":
|
||||
debuggerObj.listBreakpoints();
|
||||
break;
|
||||
|
||||
case "dump":
|
||||
debuggerObj.dump();
|
||||
break;
|
||||
|
||||
default:
|
||||
appendLog("Unknown command: " + args[1]);
|
||||
}
|
||||
|
||||
commandBox.value = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Executable
+679
@@ -0,0 +1,679 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright JS Foundation and other contributors, http://js.foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import print_function
|
||||
import socket
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
import re
|
||||
from cmd import Cmd
|
||||
from struct import *
|
||||
from pprint import pprint # For the readable stack printing.
|
||||
|
||||
# Messages sent by the server to client.
|
||||
JERRY_DEBUGGER_CONFIGURATION = 1
|
||||
JERRY_DEBUGGER_PARSE_ERROR = 2
|
||||
JERRY_DEBUGGER_BYTE_CODE_CP = 3
|
||||
JERRY_DEBUGGER_PARSE_FUNCTION = 4
|
||||
JERRY_DEBUGGER_BREAKPOINT_LIST = 5
|
||||
JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST = 6
|
||||
JERRY_DEBUGGER_RESOURCE_NAME = 7
|
||||
JERRY_DEBUGGER_RESOURCE_NAME_END = 8
|
||||
JERRY_DEBUGGER_FUNCTION_NAME = 9
|
||||
JERRY_DEBUGGER_FUNCTION_NAME_END = 10
|
||||
JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP = 11
|
||||
JERRY_DEBUGGER_BREAKPOINT_HIT = 12
|
||||
JERRY_DEBUGGER_BACKTRACE = 13
|
||||
JERRY_DEBUGGER_BACKTRACE_END = 14
|
||||
|
||||
# Messages sent by the client to server.
|
||||
JERRY_DEBUGGER_FREE_BYTE_CODE_CP = 1
|
||||
JERRY_DEBUGGER_UPDATE_BREAKPOINT = 2
|
||||
JERRY_DEBUGGER_STOP = 3
|
||||
JERRY_DEBUGGER_CONTINUE = 4
|
||||
JERRY_DEBUGGER_STEP = 5
|
||||
JERRY_DEBUGGER_NEXT = 6
|
||||
JERRY_DEBUGGER_GET_BACKTRACE = 7
|
||||
|
||||
MAX_BUFFER_SIZE = 128
|
||||
WEBSOCKET_BINARY_FRAME = 2
|
||||
WEBSOCKET_FIN_BIT = 0x80
|
||||
|
||||
|
||||
def arguments_parse():
|
||||
parser = argparse.ArgumentParser(description="JerryScript debugger client")
|
||||
|
||||
parser.add_argument("address", action="store", nargs="?", default="localhost:5001", help="specify a unique network address for connection (default: %(default)s)")
|
||||
parser.add_argument("-v", "--verbose", action="store_true", default=False, help="increase verbosity (default: %(default)s)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.DEBUG)
|
||||
logging.debug("Debug logging mode: ON")
|
||||
|
||||
return args
|
||||
|
||||
|
||||
class JerryBreakpoint(object):
|
||||
|
||||
def __init__(self, line, offset, function):
|
||||
self.line = line
|
||||
self.offset = offset
|
||||
self.function = function
|
||||
self.active_index = -1
|
||||
|
||||
def to_string(self):
|
||||
result = self.function.source
|
||||
|
||||
if result == "":
|
||||
result = "<unknown>"
|
||||
|
||||
result += ":%d" % (self.line)
|
||||
|
||||
if self.function.name:
|
||||
result += " (in %s)" % (self.function.name)
|
||||
return result
|
||||
|
||||
def __repr__(self):
|
||||
return ("Breakpoint(line:%d, offset:%d, active_index:%d)"
|
||||
% (self.line, self.offset, self.active_index))
|
||||
|
||||
|
||||
class JerryFunction(object):
|
||||
|
||||
def __init__(self, byte_code_cp, source, name, lines, offsets):
|
||||
self.byte_code_cp = byte_code_cp
|
||||
self.source = source
|
||||
self.name = name
|
||||
self.lines = {}
|
||||
self.offsets = {}
|
||||
self.first_line = -1
|
||||
|
||||
if len > 0:
|
||||
self.first_line = lines[0]
|
||||
|
||||
for i in range(len(lines)):
|
||||
line = lines[i]
|
||||
offset = offsets[i]
|
||||
breakpoint = JerryBreakpoint(line, offset, self)
|
||||
self.lines[line] = breakpoint
|
||||
self.offsets[offset] = breakpoint
|
||||
|
||||
def __repr__(self):
|
||||
result = ("Function(byte_code_cp:0x%x, source:\"%s\", name:\"%s\", { "
|
||||
% (self.byte_code_cp, self.source, self.name))
|
||||
|
||||
result += ','.join([str(breakpoint) for breakpoint in self.lines.values()])
|
||||
|
||||
return result + " })"
|
||||
|
||||
|
||||
class DebuggerPrompt(Cmd):
|
||||
|
||||
def __init__(self, debugger):
|
||||
Cmd.__init__(self)
|
||||
self.debugger = debugger
|
||||
self.stop = False
|
||||
|
||||
def precmd(self, line):
|
||||
self.stop = False
|
||||
return line
|
||||
|
||||
def postcmd(self, stop, line):
|
||||
return self.stop
|
||||
|
||||
def insert_breakpoint(self, args):
|
||||
if args == "":
|
||||
print("Error: Breakpoint index expected")
|
||||
else:
|
||||
set_breakpoint(self.debugger, args)
|
||||
|
||||
def do_break(self, args):
|
||||
""" Insert breakpoints on the given lines """
|
||||
self.insert_breakpoint(args)
|
||||
|
||||
def do_b(self, args):
|
||||
""" Insert breakpoints on the given lines """
|
||||
self.insert_breakpoint(args)
|
||||
|
||||
def exec_command(self, args, command_id):
|
||||
self.stop = True
|
||||
if args != "":
|
||||
print("Error: No argument expected")
|
||||
else:
|
||||
self.debugger.send_command(command_id)
|
||||
|
||||
def do_continue(self, args):
|
||||
""" Continue execution """
|
||||
self.exec_command(args, JERRY_DEBUGGER_CONTINUE)
|
||||
|
||||
def do_c(self, args):
|
||||
""" Continue execution """
|
||||
self.exec_command(args, JERRY_DEBUGGER_CONTINUE)
|
||||
|
||||
def do_step(self, args):
|
||||
""" Next breakpoint, step into functions """
|
||||
self.exec_command(args, JERRY_DEBUGGER_STEP)
|
||||
|
||||
def do_s(self, args):
|
||||
""" Next breakpoint, step into functions """
|
||||
self.exec_command(args, JERRY_DEBUGGER_STEP)
|
||||
|
||||
def do_next(self, args):
|
||||
""" Next breakpoint in the same context """
|
||||
self.exec_command(args, JERRY_DEBUGGER_NEXT)
|
||||
|
||||
def do_n(self, args):
|
||||
""" Next breakpoint in the same context """
|
||||
self.exec_command(args, JERRY_DEBUGGER_NEXT)
|
||||
|
||||
def do_list(self, args):
|
||||
""" Lists the available breakpoints """
|
||||
if args != "":
|
||||
print("Error: No argument expected")
|
||||
return
|
||||
|
||||
for breakpoint in self.debugger.active_breakpoint_list.values():
|
||||
source = breakpoint.function.source
|
||||
print("%d: %s" % (breakpoint.active_index, breakpoint.to_string()))
|
||||
|
||||
def do_delete(self, args):
|
||||
""" Delete the given breakpoint """
|
||||
if not args:
|
||||
print("Error: Breakpoint index expected")
|
||||
return
|
||||
|
||||
try:
|
||||
breakpoint_index = int(args)
|
||||
except:
|
||||
print("Error: Integer number expected")
|
||||
return
|
||||
|
||||
if breakpoint_index in self.debugger.active_breakpoint_list:
|
||||
breakpoint = self.debugger.active_breakpoint_list[breakpoint_index]
|
||||
del self.debugger.active_breakpoint_list[breakpoint_index]
|
||||
breakpoint.active_index = -1
|
||||
self.debugger.send_breakpoint(breakpoint)
|
||||
else:
|
||||
print("Error: Breakpoint %d not found" % (breakpoint_index))
|
||||
|
||||
def exec_backtrace(self, args):
|
||||
max_depth = 0
|
||||
|
||||
if args:
|
||||
try:
|
||||
max_depth = int(args)
|
||||
if max_depth <= 0:
|
||||
print("Error: Positive integer number expected")
|
||||
return
|
||||
except:
|
||||
print("Error: Positive integer number expected")
|
||||
return
|
||||
|
||||
message = pack(self.debugger.byte_order + "BBIB" + self.debugger.idx_format,
|
||||
WEBSOCKET_BINARY_FRAME | WEBSOCKET_FIN_BIT,
|
||||
WEBSOCKET_FIN_BIT + 1 + 4,
|
||||
0,
|
||||
JERRY_DEBUGGER_GET_BACKTRACE,
|
||||
max_depth)
|
||||
self.debugger.send_message(message)
|
||||
self.stop = True
|
||||
|
||||
def do_backtrace(self, args):
|
||||
""" Get backtrace data from debugger """
|
||||
self.exec_backtrace(args)
|
||||
|
||||
def do_bt(self, args):
|
||||
""" Get backtrace data from debugger """
|
||||
self.exec_backtrace(args)
|
||||
|
||||
def do_dump(self, args):
|
||||
""" Dump all of the debugger data """
|
||||
pprint(self.debugger.function_list)
|
||||
|
||||
|
||||
class Multimap(object):
|
||||
|
||||
def __init__(self):
|
||||
self.map = {}
|
||||
|
||||
def get(self, key):
|
||||
if key in self.map:
|
||||
return self.map[key]
|
||||
return []
|
||||
|
||||
def insert(self, key, value):
|
||||
if key in self.map:
|
||||
self.map[key].append(value)
|
||||
else:
|
||||
self.map[key] = [value]
|
||||
|
||||
def delete(self, key, value):
|
||||
items = self.map[key]
|
||||
|
||||
if len(items) == 1:
|
||||
del self.map[key]
|
||||
else:
|
||||
del items[items.index(value)]
|
||||
|
||||
def __repr__(self):
|
||||
return "Multimap(%s)" % (self.map)
|
||||
|
||||
|
||||
class JerryDebugger(object):
|
||||
|
||||
def __init__(self, address):
|
||||
|
||||
if ":" not in address:
|
||||
print("Wrong address settings: Use the 'IP:PORT' format.")
|
||||
else:
|
||||
self.host, self.port = address.split(":")
|
||||
self.port = int(self.port)
|
||||
print("Address setup: %s:%s" % (self.host, self.port))
|
||||
|
||||
self.message_data = b""
|
||||
self.function_list = {}
|
||||
self.next_breakpoint_index = 0
|
||||
self.active_breakpoint_list = {}
|
||||
self.line_list = Multimap()
|
||||
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.client_socket.connect((self.host, self.port))
|
||||
|
||||
self.send_message(b"GET /jerry-debugger HTTP/1.1\r\n" +
|
||||
b"Upgrade: websocket\r\n" +
|
||||
b"Connection: Upgrade\r\n" +
|
||||
b"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n")
|
||||
result = b""
|
||||
expected = (b"HTTP/1.1 101 Switching Protocols\r\n" +
|
||||
b"Upgrade: websocket\r\n" +
|
||||
b"Connection: Upgrade\r\n" +
|
||||
b"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n")
|
||||
|
||||
len_expected = len(expected)
|
||||
|
||||
while len(result) < len_expected:
|
||||
result += self.client_socket.recv(1024)
|
||||
|
||||
len_result = len(result)
|
||||
|
||||
if result[0:len_expected] != expected:
|
||||
raise Exception("Unexpected handshake")
|
||||
|
||||
if len_result > len_expected:
|
||||
result = result[len_expected:]
|
||||
|
||||
len_expected = 6
|
||||
# Network configurations, which has the following struct:
|
||||
# header [2] - opcode[1], size[1]
|
||||
# type [1]
|
||||
# max_message_size [1]
|
||||
# cpointer_size [1]
|
||||
# little_endian [1]
|
||||
|
||||
while len(result) < len_expected:
|
||||
result += self.client_socket.recv(1024)
|
||||
|
||||
len_result = len(result)
|
||||
|
||||
if (ord(result[0]) != WEBSOCKET_BINARY_FRAME | WEBSOCKET_FIN_BIT or
|
||||
ord(result[1]) != 4 or
|
||||
ord(result[2]) != JERRY_DEBUGGER_CONFIGURATION):
|
||||
raise Exception("Unexpected configuration")
|
||||
|
||||
self.max_message_size = ord(result[3])
|
||||
self.cp_size = ord(result[4])
|
||||
self.little_endian = ord(result[5])
|
||||
|
||||
if self.little_endian:
|
||||
self.byte_order = "<"
|
||||
logging.debug("Little-endian machine")
|
||||
else:
|
||||
self.byte_order = ">"
|
||||
logging.debug("Big-endian machine")
|
||||
|
||||
if self.cp_size == 2:
|
||||
self.cp_format = "H"
|
||||
else:
|
||||
self.cp_format = "I"
|
||||
|
||||
self.idx_format = "I"
|
||||
|
||||
logging.debug("Compressed pointer size: %d" % (self.cp_size))
|
||||
|
||||
if len_result > len_expected:
|
||||
self.message_data = result[len_expected:]
|
||||
|
||||
def __del__(self):
|
||||
self.client_socket.close()
|
||||
|
||||
def send_message(self, message):
|
||||
size = len(message)
|
||||
while size > 0:
|
||||
bytes_send = self.client_socket.send(message)
|
||||
if bytes_send < size:
|
||||
message = message[bytes_send:]
|
||||
size -= bytes_send
|
||||
|
||||
def send_breakpoint(self, breakpoint):
|
||||
message = pack(self.byte_order + "BBIBB" + self.cp_format + self.idx_format,
|
||||
WEBSOCKET_BINARY_FRAME | WEBSOCKET_FIN_BIT,
|
||||
WEBSOCKET_FIN_BIT + 1 + 1 + self.cp_size + 4,
|
||||
0,
|
||||
JERRY_DEBUGGER_UPDATE_BREAKPOINT,
|
||||
int(breakpoint.active_index >= 0),
|
||||
breakpoint.function.byte_code_cp,
|
||||
breakpoint.offset)
|
||||
self.send_message(message)
|
||||
|
||||
def send_command(self, command):
|
||||
message = pack(self.byte_order + "BBIB",
|
||||
WEBSOCKET_BINARY_FRAME | WEBSOCKET_FIN_BIT,
|
||||
WEBSOCKET_FIN_BIT + 1,
|
||||
0,
|
||||
command)
|
||||
self.send_message(message)
|
||||
|
||||
def get_message(self):
|
||||
if self.message_data is None:
|
||||
return None
|
||||
|
||||
while True:
|
||||
if len(self.message_data) >= 2:
|
||||
if ord(self.message_data[0]) != WEBSOCKET_BINARY_FRAME | WEBSOCKET_FIN_BIT:
|
||||
raise Exception("Unexpected data frame")
|
||||
|
||||
size = ord(self.message_data[1])
|
||||
if size == 0 or size >= 126:
|
||||
raise Exception("Unexpected data frame")
|
||||
|
||||
if len(self.message_data) >= size + 2:
|
||||
result = self.message_data[0:size + 2]
|
||||
self.message_data = self.message_data[size + 2:]
|
||||
return result
|
||||
|
||||
data = self.client_socket.recv(MAX_BUFFER_SIZE)
|
||||
if not data:
|
||||
self.message_data = None
|
||||
return None
|
||||
|
||||
self.message_data += data
|
||||
|
||||
|
||||
def parse_source(debugger, data):
|
||||
source_name = ""
|
||||
function_name = ""
|
||||
stack = [{"lines": [], "offsets": [], "name": ""}]
|
||||
new_function_list = {}
|
||||
|
||||
while True:
|
||||
if data is None:
|
||||
return
|
||||
|
||||
buffer_type = ord(data[2])
|
||||
buffer_size = ord(data[1]) - 1
|
||||
|
||||
logging.debug("Parser buffer type: %d, message size: %d" % (buffer_type, buffer_size))
|
||||
|
||||
if buffer_type == JERRY_DEBUGGER_PARSE_ERROR:
|
||||
logging.error("Parser error!")
|
||||
return
|
||||
|
||||
if buffer_type in [JERRY_DEBUGGER_RESOURCE_NAME, JERRY_DEBUGGER_RESOURCE_NAME_END]:
|
||||
source_name += unpack("%ds" % (buffer_size), data[3:buffer_size+3])[0]
|
||||
|
||||
elif buffer_type in [JERRY_DEBUGGER_FUNCTION_NAME, JERRY_DEBUGGER_FUNCTION_NAME_END]:
|
||||
function_name += unpack("%ds" % (buffer_size), data[3:buffer_size+3])[0]
|
||||
|
||||
elif buffer_type == JERRY_DEBUGGER_PARSE_FUNCTION:
|
||||
logging.debug("Source name: %s, function name: %s" % (source_name, function_name))
|
||||
stack.append({"name": function_name, "source": source_name, "lines": [], "offsets": []})
|
||||
function_name = ""
|
||||
|
||||
elif buffer_type in [JERRY_DEBUGGER_BREAKPOINT_LIST, JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST]:
|
||||
name = "lines"
|
||||
if buffer_type == JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST:
|
||||
name = "offsets"
|
||||
|
||||
logging.debug("Breakpoint %s received" % (name))
|
||||
|
||||
buffer_pos = 3
|
||||
while buffer_size > 0:
|
||||
line = unpack(debugger.byte_order + debugger.idx_format,
|
||||
data[buffer_pos: buffer_pos + 4])
|
||||
stack[-1][name].append(line[0])
|
||||
buffer_pos += 4
|
||||
buffer_size -= 4
|
||||
|
||||
elif buffer_type == JERRY_DEBUGGER_BYTE_CODE_CP:
|
||||
byte_code_cp = unpack(debugger.byte_order + debugger.cp_format,
|
||||
data[3: 3 + debugger.cp_size])[0]
|
||||
|
||||
logging.debug("Byte code cptr received: {0x%x}" % (byte_code_cp))
|
||||
|
||||
func_desc = stack.pop()
|
||||
|
||||
# We know the last item in the list is the general byte code.
|
||||
if len(stack) == 0:
|
||||
func_desc["source"] = source_name
|
||||
|
||||
function = JerryFunction(byte_code_cp,
|
||||
func_desc["source"],
|
||||
func_desc["name"],
|
||||
func_desc["lines"],
|
||||
func_desc["offsets"])
|
||||
|
||||
new_function_list[byte_code_cp] = function
|
||||
|
||||
if len(stack) == 0:
|
||||
logging.debug("Parse completed.")
|
||||
break
|
||||
|
||||
else:
|
||||
logging.error("Parser error!")
|
||||
return
|
||||
|
||||
data = debugger.get_message()
|
||||
|
||||
# Copy the ready list to the global storage.
|
||||
debugger.function_list.update(new_function_list)
|
||||
|
||||
for function in new_function_list.values():
|
||||
for line, breakpoint in function.lines.items():
|
||||
debugger.line_list.insert(line, breakpoint)
|
||||
|
||||
|
||||
def release_function(debugger, data):
|
||||
byte_code_cp = unpack(debugger.byte_order + debugger.cp_format,
|
||||
data[3: 3 + debugger.cp_size])[0]
|
||||
|
||||
function = debugger.function_list[byte_code_cp]
|
||||
|
||||
for line, breakpoint in function.lines.items():
|
||||
debugger.line_list.delete(line, breakpoint)
|
||||
if breakpoint.active_index >= 0:
|
||||
del debugger.active_breakpoint_list[breakpoint.active_index]
|
||||
|
||||
del debugger.function_list[byte_code_cp]
|
||||
|
||||
message = pack(debugger.byte_order + "BBIB" + debugger.cp_format,
|
||||
WEBSOCKET_BINARY_FRAME | WEBSOCKET_FIN_BIT,
|
||||
WEBSOCKET_FIN_BIT + 1 + debugger.cp_size,
|
||||
0,
|
||||
JERRY_DEBUGGER_FREE_BYTE_CODE_CP,
|
||||
byte_code_cp)
|
||||
|
||||
debugger.send_message(message)
|
||||
|
||||
logging.debug("Function {0x%x} byte-code released" % byte_code_cp)
|
||||
|
||||
|
||||
def enable_breakpoint(debugger, breakpoint):
|
||||
if breakpoint.active_index < 0:
|
||||
debugger.next_breakpoint_index += 1
|
||||
|
||||
debugger.active_breakpoint_list[debugger.next_breakpoint_index] = breakpoint
|
||||
breakpoint.active_index = debugger.next_breakpoint_index
|
||||
debugger.send_breakpoint(breakpoint)
|
||||
|
||||
print ("Breakpoint %d at %s"
|
||||
% (breakpoint.active_index, breakpoint.to_string()))
|
||||
|
||||
|
||||
def set_breakpoint(debugger, string):
|
||||
line = re.match("(.*):(\\d+)$", string)
|
||||
found = False
|
||||
|
||||
if line:
|
||||
source = line.group(1)
|
||||
line = int(line.group(2))
|
||||
|
||||
for breakpoint in debugger.line_list.get(line):
|
||||
func_source = breakpoint.function.source
|
||||
if (source == func_source or
|
||||
func_source.endswith("/" + source) or
|
||||
func_source.endswith("\\" + source)):
|
||||
|
||||
enable_breakpoint(debugger, breakpoint)
|
||||
found = True
|
||||
|
||||
else:
|
||||
for function in debugger.function_list.values():
|
||||
if function.name == string:
|
||||
if function.first_line >= 0:
|
||||
enable_breakpoint(debugger, function.lines[function.first_line])
|
||||
else:
|
||||
print("Function %s has no breakpoints." % (string))
|
||||
found = True
|
||||
|
||||
if not found:
|
||||
print("Breakpoint not found")
|
||||
return
|
||||
|
||||
|
||||
def main():
|
||||
args = arguments_parse()
|
||||
|
||||
debugger = JerryDebugger(args.address)
|
||||
|
||||
logging.debug("Connected to JerryScript on %d port" % (debugger.port))
|
||||
|
||||
prompt = DebuggerPrompt(debugger)
|
||||
prompt.prompt = "(jerry-debugger) "
|
||||
|
||||
while True:
|
||||
|
||||
data = debugger.get_message()
|
||||
|
||||
if not data: # Break the while loop if there is no more data.
|
||||
break
|
||||
|
||||
buffer_type = ord(data[2])
|
||||
buffer_size = ord(data[1]) - 1
|
||||
|
||||
logging.debug("Main buffer type: %d, message size: %d" % (buffer_type, buffer_size))
|
||||
|
||||
if buffer_type in [JERRY_DEBUGGER_PARSE_ERROR,
|
||||
JERRY_DEBUGGER_BYTE_CODE_CP,
|
||||
JERRY_DEBUGGER_PARSE_FUNCTION,
|
||||
JERRY_DEBUGGER_BREAKPOINT_LIST,
|
||||
JERRY_DEBUGGER_RESOURCE_NAME,
|
||||
JERRY_DEBUGGER_RESOURCE_NAME_END,
|
||||
JERRY_DEBUGGER_FUNCTION_NAME,
|
||||
JERRY_DEBUGGER_FUNCTION_NAME_END]:
|
||||
parse_source(debugger, data)
|
||||
|
||||
elif buffer_type == JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP:
|
||||
release_function(debugger, data)
|
||||
|
||||
elif buffer_type == JERRY_DEBUGGER_BREAKPOINT_HIT:
|
||||
breakpoint_data = unpack(debugger.byte_order + debugger.cp_format + debugger.idx_format, data[3:])
|
||||
|
||||
function = debugger.function_list[breakpoint_data[0]]
|
||||
breakpoint = function.offsets[breakpoint_data[1]]
|
||||
|
||||
breakpoint_index = ""
|
||||
if breakpoint.active_index >= 0:
|
||||
breakpoint_index = " breakpoint:%d" % (breakpoint.active_index)
|
||||
|
||||
print("Stopped at%s %s" % (breakpoint_index, breakpoint.to_string()))
|
||||
|
||||
prompt.cmdloop()
|
||||
|
||||
elif buffer_type in [JERRY_DEBUGGER_BACKTRACE, JERRY_DEBUGGER_BACKTRACE_END]:
|
||||
frame_index = 0
|
||||
|
||||
while True:
|
||||
|
||||
buffer_pos = 3
|
||||
while buffer_size > 0:
|
||||
breakpoint_data = unpack(debugger.byte_order + debugger.cp_format + debugger.idx_format,
|
||||
data[buffer_pos: buffer_pos + debugger.cp_size + 4])
|
||||
|
||||
function = debugger.function_list[breakpoint_data[0]]
|
||||
best_offset = -1
|
||||
|
||||
for offset in function.offsets:
|
||||
if offset <= breakpoint_data[1] and offset > best_offset:
|
||||
best_offset = offset
|
||||
|
||||
if best_offset >= 0:
|
||||
breakpoint = function.offsets[best_offset]
|
||||
print("Frame %d: %s" % (frame_index, breakpoint.to_string()))
|
||||
elif function.name:
|
||||
print("Frame %d: %s()" % (frame_index, function.name))
|
||||
else:
|
||||
print("Frame %d: <unknown>()" % (frame_index))
|
||||
|
||||
frame_index += 1
|
||||
buffer_pos += 6
|
||||
buffer_size -= 6
|
||||
|
||||
if buffer_type == JERRY_DEBUGGER_BACKTRACE_END:
|
||||
break
|
||||
|
||||
data = debugger.get_message()
|
||||
buffer_type = ord(data[2])
|
||||
buffer_size = ord(data[1]) - 1
|
||||
|
||||
if buffer_type not in [JERRY_DEBUGGER_BACKTRACE,
|
||||
JERRY_DEBUGGER_BACKTRACE_END]:
|
||||
raise Exception("Backtrace data expected")
|
||||
|
||||
prompt.cmdloop()
|
||||
|
||||
else:
|
||||
raise Exception("Unknown message")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except socket.error as error_msg:
|
||||
try:
|
||||
errno = error_msg.errno
|
||||
msg = str(error_msg)
|
||||
except:
|
||||
errno = error_msg[0]
|
||||
msg = error_msg[1]
|
||||
|
||||
if errno == 111:
|
||||
sys.exit("Failed to connect to the JerryScript debugger.")
|
||||
elif errno == 32:
|
||||
sys.exit("Connection closed.")
|
||||
else:
|
||||
sys.exit("Failed to connect to the JerryScript debugger.\nError: %s" % (msg))
|
||||
Reference in New Issue
Block a user