jerryscript/jerry-debugger/jerry-client-ws.html
Zoltan Herczeg 679500b327 Improve breakpoint generator of the debugger. (#1652)
Now the debugger generates a breakpoint for each function before
its first executable statement. This allows inspecting the function
arguments. Position (line and column) info is also added which
simplifies finding of anonymus functions. Several tests were
update to check more corner cases.

JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com
2017-03-13 11:36:34 +01:00

1206 lines
26 KiB
HTML

<!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_SOURCE_CODE = 7;
var JERRY_DEBUGGER_SOURCE_CODE_END = 8;
var JERRY_DEBUGGER_SOURCE_CODE_NAME = 9;
var JERRY_DEBUGGER_SOURCE_CODE_NAME_END = 10;
var JERRY_DEBUGGER_FUNCTION_NAME = 11;
var JERRY_DEBUGGER_FUNCTION_NAME_END = 12;
var JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP = 13;
var JERRY_DEBUGGER_BREAKPOINT_HIT = 14;
var JERRY_DEBUGGER_BACKTRACE = 15;
var JERRY_DEBUGGER_BACKTRACE_END = 16;
var JERRY_DEBUGGER_EVAL_RESULT = 17;
var JERRY_DEBUGGER_EVAL_RESULT_END = 18;
var JERRY_DEBUGGER_EVAL_ERROR = 19;
var JERRY_DEBUGGER_EVAL_ERROR_END = 20;
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 JERRY_DEBUGGER_EVAL = 8;
var JERRY_DEBUGGER_EVAL_PART = 9;
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(address)
{
appendLog("ws://" + address + "/jerry-debugger");
var parseObj = null;
var maxMessageSize = 0;
var cpointerSize = 0;
var littleEndian = true;
var functions = { };
var lineList = new Multimap();
var lastBreakpointHit = null;
var activeBreakpoints = { };
var nextBreakpointIndex = 1;
var backtraceFrame = 0;
var evalResult = null;
function assert(expr)
{
if (!expr)
{
throw new Error("Assertion failed.");
}
}
function setUint32(array, offset, value)
{
if (littleEndian)
{
array[offset] = value & 0xff;
array[offset + 1] = (value >> 8) & 0xff;
array[offset + 2] = (value >> 16) & 0xff;
array[offset + 3] = (value >> 24) & 0xff;
}
else
{
array[offset] = (value >> 24) & 0xff;
array[offset + 1] = (value >> 16) & 0xff;
array[offset + 2] = (value >> 8) & 0xff;
array[offset + 3] = value & 0xff;
}
}
/* 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(baseLength + nextLength);
result.set(nextArray, baseLength - 1);
/* This set operation 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 stringToCesu8(string)
{
assert(string != "");
var length = string.length;
var byteLength = length;
for (var i = 0; i < length; i++)
{
var chr = string.charCodeAt(i);
if (chr >= 0x7ff)
{
byteLength ++;
}
if (chr >= 0x7f)
{
byteLength++;
}
}
var result = new Uint8Array(byteLength + 1 + 4);
result[0] = JERRY_DEBUGGER_EVAL;
setUint32(result, 1, byteLength);
var offset = 5;
for (var i = 0; i < length; i++)
{
var chr = string.charCodeAt(i);
if (chr >= 0x7ff)
{
result[offset] = 0xe0 | (chr >> 12);
result[offset + 1] = 0x80 | ((chr >> 6) & 0x3f);
result[offset + 2] = 0x80 | (chr & 0x3f);
offset += 3;
}
else if (chr >= 0x7f)
{
result[offset] = 0xc0 | (chr >> 6);
result[offset + 1] = 0x80 | (chr & 0x3f);
}
else
{
result[offset] = chr;
offset++;
}
}
return result;
}
function breakpointToString(breakpoint)
{
var name = breakpoint.func.name;
var result = breakpoint.func.sourceName;
if (!result)
{
result = "<unknown>";
}
result += ":" + breakpoint.line;
if (breakpoint.func.is_func)
{
result += " (in "
+ (breakpoint.func.name ? breakpoint.func.name : "function")
+ "() at line:"
+ breakpoint.func.line
+ ", col:"
+ breakpoint.func.column
+ ")";
}
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://" + address + "/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;
}
setUint32(message, offset, value);
offset += 4;
}
socket.send(message);
}
function releaseFunction(message)
{
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);
}
this.encodeMessage = encodeMessage;
function ParseSource()
{
var source = "";
var sourceData = null;
var sourceName = "";
var sourceNameData = null;
var functionName = null;
var stack = [{ is_func: false,
line: 1,
column: 1,
name: "",
source: "",
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_SOURCE_CODE:
case JERRY_DEBUGGER_SOURCE_CODE_END:
{
sourceData = concatUint8Arrays(sourceData, message);
if (message[0] == JERRY_DEBUGGER_SOURCE_CODE_END)
{
source = cesu8ToString(sourceData);
}
return;
}
case JERRY_DEBUGGER_SOURCE_CODE_NAME:
case JERRY_DEBUGGER_SOURCE_CODE_NAME_END:
{
sourceNameData = concatUint8Arrays(sourceNameData, message);
if (message[0] == JERRY_DEBUGGER_SOURCE_CODE_NAME_END)
{
sourceName = cesu8ToString(sourceNameData);
}
return;
}
case JERRY_DEBUGGER_FUNCTION_NAME:
case JERRY_DEBUGGER_FUNCTION_NAME_END:
{
functionName = concatUint8Arrays(functionName, message);
return;
}
case JERRY_DEBUGGER_PARSE_FUNCTION:
{
position = decodeMessage("II", message, 1);
stack.push({ is_func: true,
line: position[0],
column: position[1],
name: cesu8ToString(functionName),
source: source,
sourceName: sourceName,
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.firstBreakpointLine = func.lines[0];
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[func.byte_code_cp] = func;
if (stack.length > 0)
{
return;
}
func.source = source;
func.sourceName = sourceName;
break;
}
case JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP:
{
var byte_code_cp = decodeMessage("C", message, 1)[0];
if (byte_code_cp in newFunctions)
{
delete newFunctions[byte_code_cp];
}
else
{
releaseFunction(message);
}
return;
}
default:
{
abortConnection("unexpected message.");
return;
}
}
for (var i in newFunctions)
{
var func = newFunctions[i];
functions[i] = 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_SOURCE_CODE:
case JERRY_DEBUGGER_SOURCE_CODE_END:
case JERRY_DEBUGGER_SOURCE_CODE_NAME:
case JERRY_DEBUGGER_SOURCE_CODE_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:
{
releaseFunction(message);
return;
}
case JERRY_DEBUGGER_BREAKPOINT_HIT:
{
var breakpoint = decodeMessage("CI", message, 1);
breakpoint = functions[breakpoint[0]].offsets[breakpoint[1]];
lastBreakpointHit = breakpoint;
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;
}
case JERRY_DEBUGGER_EVAL_RESULT:
case JERRY_DEBUGGER_EVAL_RESULT_END:
case JERRY_DEBUGGER_EVAL_ERROR:
case JERRY_DEBUGGER_EVAL_ERROR_END:
{
evalResult = concatUint8Arrays(evalResult, message);
if (message[0] == JERRY_DEBUGGER_EVAL_RESULT_END)
{
appendLog(cesu8ToString(evalResult));
evalResult = null;
return;
}
if (message[0] == JERRY_DEBUGGER_EVAL_ERROR_END)
{
appendLog("Uncaught exception: " + cesu8ToString(evalResult));
evalResult = null;
return;
}
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 sourceName = func.sourceName;
if (sourceName == line[1]
|| sourceName.endsWith("/" + line[1])
|| sourceName.endsWith("\\" + line[1]))
{
insertBreakpoint(func.lines[line[2]]);
}
}
}
else
{
for (var i in functions)
{
var func = functions[i];
if (func.name == str)
{
insertBreakpoint(func.lines[func.firstBreakpointLine]);
}
}
}
}
this.deleteBreakpoint = function(index)
{
breakpoint = activeBreakpoints[index];
if (index == "all")
{
var found = false;
for (var i in activeBreakpoints)
{
delete activeBreakpoints[i];
found = true;
}
if (!found)
{
appendLog("No active breakpoints.")
}
}
else 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.sendResumeExec = function(command)
{
if (!lastBreakpointHit)
{
appendLog("This command is allowed only if JavaScript execution is stopped at a breakpoint.");
return;
}
encodeMessage("B", [ command ]);
lastBreakpointHit = null;
}
this.sendGetBacktrace = function(depth)
{
if (!lastBreakpointHit)
{
appendLog("This command is allowed only if JavaScript execution is stopped at a breakpoint.");
return;
}
encodeMessage("BI", [ JERRY_DEBUGGER_GET_BACKTRACE, max_depth ]);
appendLog("Backtrace:");
}
this.sendEval = function(str)
{
if (!lastBreakpointHit)
{
appendLog("This command is allowed only if JavaScript execution is stopped at a breakpoint.");
return;
}
if (str == "")
{
appendLog("Argument required");
return;
}
var array = stringToCesu8(str);
var byteLength = array.byteLength;
if (byteLength <= maxMessageSize)
{
socket.send(array);
return;
}
socket.send(array.slice(0, maxMessageSize));
var offset = maxMessageSize - 1;
while (offset < byteLength)
{
array[offset] = JERRY_DEBUGGER_EVAL_PART;
socket.send(array.slice(offset, offset + maxMessageSize));
offset += maxMessageSize - 1;
}
}
this.printSource = function()
{
if (lastBreakpointHit)
{
appendLog(lastBreakpointHit.func.source);
}
}
this.dump = function()
{
for (var i in functions)
{
var func = functions[i];
var sourceName = func.sourceName;
if (!sourceName)
{
sourceName = "<unknown>";
}
appendLog("Function 0x"
+ Number(i).toString(16)
+ " '"
+ func.name
+ "' at "
+ sourceName
+ ":"
+ func.line
+ ","
+ func.column);
for (var j in func.lines)
{
var active = "";
if (func.lines[j].active >= 0)
{
active = " (active: " + func.lines[j].active + ")";
}
appendLog(" Breakpoint 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:PORT> - connect to server (default is localhost:5001)\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" +
" eval|e - evaluate expression\n" +
" backtrace|bt <max-depth> - get backtrace\n" +
" src - print current source code\n" +
" dump - dump all breakpoint data");
commandBox.value = "";
return true;
}
if (args[1] == "connect")
{
if (debuggerObj)
{
appendLog("Debugger is connected");
return true;
}
var ipAddr = args[2];
var PORT = "5001";
if (ipAddr == "")
{
ipAddr = "localhost";
}
if (ipAddr.match(/.*:\d/))
{
var fields = ipAddr.split(":");
ipAddr = fields[0];
PORT = fields[1];
}
var address = ipAddr + ":" + PORT;
appendLog("Connect to: " + address);
debuggerObj = new DebuggerClient(address);
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 "st":
case "stop":
debuggerObj.encodeMessage("B", [ JERRY_DEBUGGER_STOP ]);
break;
case "c":
case "continue":
debuggerObj.sendResumeExec(JERRY_DEBUGGER_CONTINUE);
break;
case "s":
case "step":
debuggerObj.sendResumeExec(JERRY_DEBUGGER_STEP);
break;
case "n":
case "next":
debuggerObj.sendResumeExec(JERRY_DEBUGGER_NEXT);
break;
case "e":
case "eval":
debuggerObj.sendEval(args[2]);
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;
}
}
debuggerObj.sendGetBacktrace(max_depth);
break;
case "src":
debuggerObj.printSource();
break;
case "list":
debuggerObj.listBreakpoints();
break;
case "dump":
debuggerObj.dump();
break;
default:
appendLog("Unknown command: " + args[1]);
break;
}
commandBox.value = "";
return true;
}
</script>
</body>
</html>