|
@ -23,6 +23,7 @@ var stream = require('stream'); |
|
|
var path = require('path'); |
|
|
var path = require('path'); |
|
|
var fs = require('fs'); |
|
|
var fs = require('fs'); |
|
|
var net = require('net'); |
|
|
var net = require('net'); |
|
|
|
|
|
var byline = require('byline'); |
|
|
var util = require('util'); |
|
|
var util = require('util'); |
|
|
var readline = require('readline'); |
|
|
var readline = require('readline'); |
|
|
var sprintf = require('sprintf').sprintf; |
|
|
var sprintf = require('sprintf').sprintf; |
|
@ -34,6 +35,8 @@ var yaml = require('yamljs'); |
|
|
var optTargetHost = '127.0.0.1'; |
|
|
var optTargetHost = '127.0.0.1'; |
|
|
var optTargetPort = 9091; |
|
|
var optTargetPort = 9091; |
|
|
var optHttpPort = 9092; |
|
|
var optHttpPort = 9092; |
|
|
|
|
|
var optJsonProxyPort = 9093; |
|
|
|
|
|
var optJsonProxy = false; |
|
|
var optSourceSearchDirs = [ '../ecmascript-testcases' ]; |
|
|
var optSourceSearchDirs = [ '../ecmascript-testcases' ]; |
|
|
var optDumpDebugRead = null; |
|
|
var optDumpDebugRead = null; |
|
|
var optDumpDebugWrite = null; |
|
|
var optDumpDebugWrite = null; |
|
@ -79,38 +82,22 @@ var ERR_TOOMANY = 0x02; |
|
|
var ERR_NOTFOUND = 0x03; |
|
|
var ERR_NOTFOUND = 0x03; |
|
|
|
|
|
|
|
|
// Marker objects for special protocol values
|
|
|
// Marker objects for special protocol values
|
|
|
var DVAL_EOM = { EOM: true }; |
|
|
var DVAL_EOM = { type: 'eom' }; |
|
|
var DVAL_REQ = { REQ: true }; |
|
|
var DVAL_REQ = { type: 'req' }; |
|
|
var DVAL_REP = { REP: true }; |
|
|
var DVAL_REP = { type: 'rep' }; |
|
|
var DVAL_ERR = { ERR: true }; |
|
|
var DVAL_ERR = { type: 'err' }; |
|
|
var DVAL_NFY = { NFY: true }; |
|
|
var DVAL_NFY = { type: 'nfy' }; |
|
|
|
|
|
|
|
|
// String map for commands (debug dumping). A single map works (instead of
|
|
|
// String map for commands (debug dumping). A single map works (instead of
|
|
|
// separate maps for each direction) because command numbers don't currently
|
|
|
// separate maps for each direction) because command numbers don't currently
|
|
|
// overlap.
|
|
|
// overlap.
|
|
|
var debugCommandNames = {}; |
|
|
var debugCommandNames = yaml.load('duk_debugcommands.yaml'); |
|
|
debugCommandNames[0x01] = 'Status'; |
|
|
|
|
|
debugCommandNames[0x02] = 'Print'; |
|
|
// Map debug command names to numbers.
|
|
|
debugCommandNames[0x03] = 'Alert'; |
|
|
var debugCommandNumbers = {}; |
|
|
debugCommandNames[0x04] = 'Log'; |
|
|
debugCommandNames.forEach(function (k, i) { |
|
|
debugCommandNames[0x05] = 'Gc'; |
|
|
debugCommandNumbers[k] = i; |
|
|
debugCommandNames[0x10] = 'BasicInfo'; |
|
|
}); |
|
|
debugCommandNames[0x11] = 'TriggerStatus'; |
|
|
|
|
|
debugCommandNames[0x12] = 'Pause'; |
|
|
|
|
|
debugCommandNames[0x13] = 'Resume'; |
|
|
|
|
|
debugCommandNames[0x14] = 'StepInto'; |
|
|
|
|
|
debugCommandNames[0x15] = 'StepOver'; |
|
|
|
|
|
debugCommandNames[0x16] = 'StepOut'; |
|
|
|
|
|
debugCommandNames[0x17] = 'ListBreak'; |
|
|
|
|
|
debugCommandNames[0x18] = 'AddBreak'; |
|
|
|
|
|
debugCommandNames[0x19] = 'DelBreak'; |
|
|
|
|
|
debugCommandNames[0x1a] = 'GetVar'; |
|
|
|
|
|
debugCommandNames[0x1b] = 'PutVar'; |
|
|
|
|
|
debugCommandNames[0x1c] = 'GetCallStack'; |
|
|
|
|
|
debugCommandNames[0x1d] = 'GetLocals'; |
|
|
|
|
|
debugCommandNames[0x1e] = 'Eval'; |
|
|
|
|
|
debugCommandNames[0x1f] = 'Detach'; |
|
|
|
|
|
debugCommandNames[0x20] = 'DumpHeap'; |
|
|
|
|
|
|
|
|
|
|
|
// Duktape heaphdr type constants, must match C headers
|
|
|
// Duktape heaphdr type constants, must match C headers
|
|
|
var DUK_HTYPE_STRING = 1; |
|
|
var DUK_HTYPE_STRING = 1; |
|
@ -118,27 +105,7 @@ var DUK_HTYPE_OBJECT = 2; |
|
|
var DUK_HTYPE_BUFFER = 3; |
|
|
var DUK_HTYPE_BUFFER = 3; |
|
|
|
|
|
|
|
|
// Duktape internal class numbers, must match C headers
|
|
|
// Duktape internal class numbers, must match C headers
|
|
|
var dukClassNames = [ |
|
|
var dukClassNames = yaml.load('duk_classnames.yaml'); |
|
|
'unused', |
|
|
|
|
|
'Arguments', |
|
|
|
|
|
'Array', |
|
|
|
|
|
'Boolean', |
|
|
|
|
|
'Date', |
|
|
|
|
|
'Error', |
|
|
|
|
|
'Function', |
|
|
|
|
|
'JSON', |
|
|
|
|
|
'Math', |
|
|
|
|
|
'Number', |
|
|
|
|
|
'Object', |
|
|
|
|
|
'RegExp', |
|
|
|
|
|
'String', |
|
|
|
|
|
'global', |
|
|
|
|
|
'ObjEnv', |
|
|
|
|
|
'DecEnv', |
|
|
|
|
|
'Buffer', |
|
|
|
|
|
'Pointer', |
|
|
|
|
|
'Thread' |
|
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
|
|
// Bytecode opcode/extraop metadata
|
|
|
// Bytecode opcode/extraop metadata
|
|
|
var dukOpcodes = yaml.load('duk_opcodes.yaml') |
|
|
var dukOpcodes = yaml.load('duk_opcodes.yaml') |
|
@ -206,11 +173,19 @@ function stringToDebugString(str) { |
|
|
|
|
|
|
|
|
/* Pretty print a dvalue. Useful for dumping etc. */ |
|
|
/* Pretty print a dvalue. Useful for dumping etc. */ |
|
|
function prettyDebugValue(x) { |
|
|
function prettyDebugValue(x) { |
|
|
if (x === DVAL_EOM) { return 'EOM'; } |
|
|
if (typeof x === 'object' && x !== null) { |
|
|
if (x === DVAL_REQ) { return 'REQ'; } |
|
|
if (x.type === 'eom') { |
|
|
if (x === DVAL_REP) { return 'REP'; } |
|
|
return 'EOM'; |
|
|
if (x === DVAL_ERR) { return 'ERR'; } |
|
|
} else if (x.type === 'req') { |
|
|
if (x === DVAL_NFY) { return 'NFY'; } |
|
|
return 'REQ'; |
|
|
|
|
|
} else if (x.type === 'rep') { |
|
|
|
|
|
return 'REP'; |
|
|
|
|
|
} else if (x.type === 'err') { |
|
|
|
|
|
return 'ERR'; |
|
|
|
|
|
} else if (x.type === 'nfy') { |
|
|
|
|
|
return 'NFY'; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
return JSON.stringify(x); |
|
|
return JSON.stringify(x); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -300,50 +275,49 @@ function prettyUiStringUnquoted(x, cliplen) { |
|
|
* so that styling etc. could take typing into account. |
|
|
* so that styling etc. could take typing into account. |
|
|
*/ |
|
|
*/ |
|
|
function prettyUiDebugValue(x, cliplen) { |
|
|
function prettyUiDebugValue(x, cliplen) { |
|
|
var t; |
|
|
|
|
|
|
|
|
|
|
|
if (x === DVAL_EOM) { return 'EOM'; } |
|
|
|
|
|
if (x === DVAL_REQ) { return 'REQ'; } |
|
|
|
|
|
if (x === DVAL_REP) { return 'REP'; } |
|
|
|
|
|
if (x === DVAL_ERR) { return 'ERR'; } |
|
|
|
|
|
if (x === DVAL_NFY) { return 'NFY'; } |
|
|
|
|
|
if (x === undefined) { return 'undefined'; } |
|
|
|
|
|
if (x === null) { return 'null'; } |
|
|
|
|
|
if (typeof x === 'boolean') { |
|
|
|
|
|
return x ? 'true' : 'false'; |
|
|
|
|
|
} |
|
|
|
|
|
if (typeof x === 'string') { |
|
|
|
|
|
return prettyUiString(x, cliplen); |
|
|
|
|
|
} |
|
|
|
|
|
if (typeof x === 'number') { |
|
|
|
|
|
// Debug protocol integer
|
|
|
|
|
|
return prettyUiNumber(x); |
|
|
|
|
|
} |
|
|
|
|
|
if (typeof x === 'object' && x !== null) { |
|
|
if (typeof x === 'object' && x !== null) { |
|
|
// Note: typeof null === 'object', so null special case explicitly
|
|
|
// Note: typeof null === 'object', so null special case explicitly
|
|
|
if (typeof x.UNUSED !== 'undefined') { |
|
|
if (x.type === 'eom') { |
|
|
|
|
|
return 'EOM'; |
|
|
|
|
|
} else if (x.type === 'req') { |
|
|
|
|
|
return 'REQ'; |
|
|
|
|
|
} else if (x.type === 'rep') { |
|
|
|
|
|
return 'REP'; |
|
|
|
|
|
} else if (x.type === 'err') { |
|
|
|
|
|
return 'ERR'; |
|
|
|
|
|
} else if (x.type === 'nfy') { |
|
|
|
|
|
return 'NFY'; |
|
|
|
|
|
} else if (x.type === 'unused') { |
|
|
return 'unused'; |
|
|
return 'unused'; |
|
|
} |
|
|
} else if (x.type === 'undefined') { |
|
|
if (typeof x.BUF !== 'undefined') { |
|
|
return 'undefined'; |
|
|
return '|' + x.BUF + '|'; |
|
|
} else if (x.type === 'buffer') { |
|
|
} |
|
|
return '|' + x.data + '|'; |
|
|
if (typeof x.OBJ !== 'undefined') { |
|
|
} else if (x.type === 'object') { |
|
|
return '[object ' + (dukClassNames[x.class] || ('class ' + x.class)) + ']'; |
|
|
return '[object ' + (dukClassNames[x.class] || ('class ' + x.class)) + ' ' + x.pointer + ']'; |
|
|
} |
|
|
} else if (x.type === 'pointer') { |
|
|
if (typeof x.PTR !== 'undefined') { |
|
|
return '<pointer ' + x.pointer + '>'; |
|
|
return '<pointer ' + x.PTR + '>'; |
|
|
} else if (x.type === 'lightfunc') { |
|
|
} |
|
|
return '<lightfunc 0x' + x.flags.toString(16) + ' ' + x.pointer + '>'; |
|
|
if (typeof x.LFUNC !== 'undefined') { |
|
|
} else if (x.type === 'number') { |
|
|
return '<lightfunc 0x' + x.flags.toString(16) + ' ' + x.LFUNC + '>'; |
|
|
|
|
|
} |
|
|
|
|
|
if (typeof x.NUM !== 'undefined') { |
|
|
|
|
|
// duk_tval number, any IEEE double
|
|
|
// duk_tval number, any IEEE double
|
|
|
return prettyUiNumber(x.value); |
|
|
var tmp = new Buffer(x.data, 'hex'); // decode into hex
|
|
|
|
|
|
var val = tmp.readDoubleBE(0); // big endian ieee double
|
|
|
|
|
|
return prettyUiNumber(val); |
|
|
} |
|
|
} |
|
|
|
|
|
} else if (x === null) { |
|
|
|
|
|
return 'null'; |
|
|
|
|
|
} else if (typeof x === 'boolean') { |
|
|
|
|
|
return x ? 'true' : 'false'; |
|
|
|
|
|
} else if (typeof x === 'string') { |
|
|
|
|
|
return prettyUiString(x, cliplen); |
|
|
|
|
|
} else if (typeof x === 'number') { |
|
|
|
|
|
// Debug protocol integer
|
|
|
|
|
|
return prettyUiNumber(x); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// We shouldn't come here, but if we do, JSON is a reasonable default.
|
|
|
// We shouldn't come here, but if we do, JSON is a reasonable default.
|
|
|
return JSON.stringify(t); |
|
|
return JSON.stringify(x); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/* Pretty print a debugger message given as an array of parsed dvalues. |
|
|
/* Pretty print a debugger message given as an array of parsed dvalues. |
|
@ -551,6 +525,7 @@ function DebugProtocolParser(inputStream, |
|
|
this.prevBytes = 0; |
|
|
this.prevBytes = 0; |
|
|
this.bytesPerSec = 0; |
|
|
this.bytesPerSec = 0; |
|
|
this.statsTimer = null; |
|
|
this.statsTimer = null; |
|
|
|
|
|
this.readableNumberValue = true; |
|
|
|
|
|
|
|
|
events.EventEmitter.call(this); |
|
|
events.EventEmitter.call(this); |
|
|
|
|
|
|
|
@ -693,7 +668,7 @@ function DebugProtocolParser(inputStream, |
|
|
if (buf.length >= 5 + len) { |
|
|
if (buf.length >= 5 + len) { |
|
|
v = new Buffer(len); |
|
|
v = new Buffer(len); |
|
|
buf.copy(v, 0, 5, 5 + len); |
|
|
buf.copy(v, 0, 5, 5 + len); |
|
|
v = { BUF: v.toString('hex') }; |
|
|
v = { type: 'buffer', data: v.toString('hex') }; |
|
|
consume(5 + len); |
|
|
consume(5 + len); |
|
|
// Value could be a Node.js buffer directly, but
|
|
|
// Value could be a Node.js buffer directly, but
|
|
|
// we prefer all dvalues to be JSON compatible
|
|
|
// we prefer all dvalues to be JSON compatible
|
|
@ -706,7 +681,7 @@ function DebugProtocolParser(inputStream, |
|
|
if (buf.length >= 3 + len) { |
|
|
if (buf.length >= 3 + len) { |
|
|
v = new Buffer(len); |
|
|
v = new Buffer(len); |
|
|
buf.copy(v, 0, 3, 3 + len); |
|
|
buf.copy(v, 0, 3, 3 + len); |
|
|
v = { BUF: v.toString('hex') }; |
|
|
v = { type: 'buffer', data: v.toString('hex') }; |
|
|
consume(3 + len); |
|
|
consume(3 + len); |
|
|
// Value could be a Node.js buffer directly, but
|
|
|
// Value could be a Node.js buffer directly, but
|
|
|
// we prefer all dvalues to be JSON compatible
|
|
|
// we prefer all dvalues to be JSON compatible
|
|
@ -714,11 +689,11 @@ function DebugProtocolParser(inputStream, |
|
|
} |
|
|
} |
|
|
break; |
|
|
break; |
|
|
case 0x15: // unused/none
|
|
|
case 0x15: // unused/none
|
|
|
v = { UNUSED: true }; |
|
|
v = { type: 'unused' }; |
|
|
consume(1); |
|
|
consume(1); |
|
|
break; |
|
|
break; |
|
|
case 0x16: // undefined
|
|
|
case 0x16: // undefined
|
|
|
v = undefined; |
|
|
v = { type: 'undefined' }; |
|
|
gotValue = true; // indicate 'v' is actually set
|
|
|
gotValue = true; // indicate 'v' is actually set
|
|
|
consume(1); |
|
|
consume(1); |
|
|
break; |
|
|
break; |
|
@ -739,7 +714,13 @@ function DebugProtocolParser(inputStream, |
|
|
if (buf.length >= 9) { |
|
|
if (buf.length >= 9) { |
|
|
v = new Buffer(8); |
|
|
v = new Buffer(8); |
|
|
buf.copy(v, 0, 1, 9); |
|
|
buf.copy(v, 0, 1, 9); |
|
|
v = { NUM: v.toString('hex'), value: v.readDoubleBE(0) }; |
|
|
v = { type: 'number', data: v.toString('hex') } |
|
|
|
|
|
|
|
|
|
|
|
if (_this.readableNumberValue) { |
|
|
|
|
|
// The _value key should not be used programmatically,
|
|
|
|
|
|
// it is just there to make the dumps more readable.
|
|
|
|
|
|
v._value = buf.readDoubleBE(1); |
|
|
|
|
|
} |
|
|
consume(9); |
|
|
consume(9); |
|
|
} |
|
|
} |
|
|
break; |
|
|
break; |
|
@ -749,7 +730,7 @@ function DebugProtocolParser(inputStream, |
|
|
if (buf.length >= 3 + len) { |
|
|
if (buf.length >= 3 + len) { |
|
|
v = new Buffer(len); |
|
|
v = new Buffer(len); |
|
|
buf.copy(v, 0, 3, 3 + len); |
|
|
buf.copy(v, 0, 3, 3 + len); |
|
|
v = { OBJ: v.toString('hex'), class: buf[1] }; |
|
|
v = { type: 'object', 'class': buf[1], pointer: v.toString('hex') }; |
|
|
consume(3 + len); |
|
|
consume(3 + len); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
@ -760,7 +741,7 @@ function DebugProtocolParser(inputStream, |
|
|
if (buf.length >= 2 + len) { |
|
|
if (buf.length >= 2 + len) { |
|
|
v = new Buffer(len); |
|
|
v = new Buffer(len); |
|
|
buf.copy(v, 0, 2, 2 + len); |
|
|
buf.copy(v, 0, 2, 2 + len); |
|
|
v = { PTR: v.toString('hex') }; |
|
|
v = { type: 'pointer', pointer: v.toString('hex') }; |
|
|
consume(2 + len); |
|
|
consume(2 + len); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
@ -771,7 +752,7 @@ function DebugProtocolParser(inputStream, |
|
|
if (buf.length >= 4 + len) { |
|
|
if (buf.length >= 4 + len) { |
|
|
v = new Buffer(len); |
|
|
v = new Buffer(len); |
|
|
buf.copy(v, 0, 4, 4 + len); |
|
|
buf.copy(v, 0, 4, 4 + len); |
|
|
v = { LFUNC: v.toString('hex'), flags: buf.readUInt16BE(1) }; |
|
|
v = { type: 'lightfunc', flags: buf.readUInt16BE(1), pointer: v.toString('hex') }; |
|
|
consume(4 + len); |
|
|
consume(4 + len); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
@ -782,7 +763,7 @@ function DebugProtocolParser(inputStream, |
|
|
if (buf.length >= 2 + len) { |
|
|
if (buf.length >= 2 + len) { |
|
|
v = new Buffer(len); |
|
|
v = new Buffer(len); |
|
|
buf.copy(v, 0, 2, 2 + len); |
|
|
buf.copy(v, 0, 2, 2 + len); |
|
|
v = { HEAPPTR: v.toString('hex') }; |
|
|
v = { type: 'heapptr', pointer: v.toString('hex') }; |
|
|
consume(2 + len); |
|
|
consume(2 + len); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
@ -858,20 +839,94 @@ DebugProtocolParser.prototype.close = function () { |
|
|
*/ |
|
|
*/ |
|
|
|
|
|
|
|
|
function formatDebugValue(v) { |
|
|
function formatDebugValue(v) { |
|
|
var buf; |
|
|
var buf, dec, len; |
|
|
|
|
|
|
|
|
// See doc/debugger.rst for format description.
|
|
|
// See doc/debugger.rst for format description.
|
|
|
|
|
|
|
|
|
if (v === DVAL_EOM) { |
|
|
if (typeof v === 'object' && v !== null) { |
|
|
return new Buffer([ 0x00 ]); |
|
|
// Note: typeof null === 'object', so null special case explicitly
|
|
|
} else if (v === DVAL_REQ) { |
|
|
if (v.type === 'eom') { |
|
|
return new Buffer([ 0x01 ]); |
|
|
return new Buffer([ 0x00 ]); |
|
|
} else if (v === DVAL_REP) { |
|
|
} else if (v.type === 'req') { |
|
|
return new Buffer([ 0x02 ]); |
|
|
return new Buffer([ 0x01 ]); |
|
|
} else if (v === DVAL_ERR) { |
|
|
} else if (v.type === 'rep') { |
|
|
return new Buffer([ 0x03 ]); |
|
|
return new Buffer([ 0x02 ]); |
|
|
} else if (v === DVAL_NFY) { |
|
|
} else if (v.type === 'err') { |
|
|
return new Buffer([ 0x04 ]); |
|
|
return new Buffer([ 0x03 ]); |
|
|
|
|
|
} else if (v.type === 'nfy') { |
|
|
|
|
|
return new Buffer([ 0x04 ]); |
|
|
|
|
|
} else if (v.type === 'unused') { |
|
|
|
|
|
return new Buffer([ 0x15 ]); |
|
|
|
|
|
} else if (v.type === 'undefined') { |
|
|
|
|
|
return new Buffer([ 0x16 ]); |
|
|
|
|
|
} else if (v.type === 'number') { |
|
|
|
|
|
dec = new Buffer(v.data, 'hex'); |
|
|
|
|
|
len = dec.length; |
|
|
|
|
|
if (len !== 8) { |
|
|
|
|
|
throw new TypeError('value cannot be converted to dvalue: ' + JSON.stringify(v)); |
|
|
|
|
|
} |
|
|
|
|
|
buf = new Buffer(1 + len); |
|
|
|
|
|
buf[0] = 0x1a; |
|
|
|
|
|
dec.copy(buf, 1); |
|
|
|
|
|
return buf; |
|
|
|
|
|
} else if (v.type === 'buffer') { |
|
|
|
|
|
dec = new Buffer(v.data, 'hex'); |
|
|
|
|
|
len = dec.length; |
|
|
|
|
|
if (len <= 0xffff) { |
|
|
|
|
|
buf = new Buffer(3 + len); |
|
|
|
|
|
buf[0] = 0x14; |
|
|
|
|
|
buf[1] = (len >> 8) & 0xff; |
|
|
|
|
|
buf[2] = (len >> 0) & 0xff; |
|
|
|
|
|
dec.copy(buf, 3); |
|
|
|
|
|
return buf; |
|
|
|
|
|
} else { |
|
|
|
|
|
buf = new Buffer(5 + len); |
|
|
|
|
|
buf[0] = 0x13; |
|
|
|
|
|
buf[1] = (len >> 24) & 0xff; |
|
|
|
|
|
buf[2] = (len >> 16) & 0xff; |
|
|
|
|
|
buf[3] = (len >> 8) & 0xff; |
|
|
|
|
|
buf[4] = (len >> 0) & 0xff; |
|
|
|
|
|
dec.copy(buf, 5); |
|
|
|
|
|
return buf; |
|
|
|
|
|
} |
|
|
|
|
|
} else if (v.type === 'object') { |
|
|
|
|
|
dec = new Buffer(v.pointer, 'hex'); |
|
|
|
|
|
len = dec.length; |
|
|
|
|
|
buf = new Buffer(3 + len); |
|
|
|
|
|
buf[0] = 0x1b; |
|
|
|
|
|
buf[1] = v.class; |
|
|
|
|
|
buf[2] = len; |
|
|
|
|
|
dec.copy(buf, 3); |
|
|
|
|
|
return buf; |
|
|
|
|
|
} else if (v.type === 'pointer') { |
|
|
|
|
|
dec = new Buffer(v.pointer, 'hex'); |
|
|
|
|
|
len = dec.length; |
|
|
|
|
|
buf = new Buffer(2 + len); |
|
|
|
|
|
buf[0] = 0x1c; |
|
|
|
|
|
buf[1] = len; |
|
|
|
|
|
dec.copy(buf, 2); |
|
|
|
|
|
return buf; |
|
|
|
|
|
} else if (v.type === 'lightfunc') { |
|
|
|
|
|
dec = new Buffer(v.pointer, 'hex'); |
|
|
|
|
|
len = dec.length; |
|
|
|
|
|
buf = new Buffer(4 + len); |
|
|
|
|
|
buf[0] = 0x1d; |
|
|
|
|
|
buf[1] = (v.flags >> 8) & 0xff; |
|
|
|
|
|
buf[2] = v.flags & 0xff; |
|
|
|
|
|
buf[3] = len; |
|
|
|
|
|
dec.copy(buf, 4); |
|
|
|
|
|
return buf; |
|
|
|
|
|
} else if (v.type === 'heapptr') { |
|
|
|
|
|
dec = new Buffer(v.pointer, 'hex'); |
|
|
|
|
|
len = dec.length; |
|
|
|
|
|
buf = new Buffer(2 + len); |
|
|
|
|
|
buf[0] = 0x1e; |
|
|
|
|
|
buf[1] = len; |
|
|
|
|
|
dec.copy(buf, 2); |
|
|
|
|
|
return buf; |
|
|
|
|
|
} |
|
|
|
|
|
} else if (v === null) { |
|
|
|
|
|
return new Buffer([ 0x17 ]); |
|
|
} else if (typeof v === 'boolean') { |
|
|
} else if (typeof v === 'boolean') { |
|
|
return new Buffer([ v ? 0x18 : 0x19 ]); |
|
|
return new Buffer([ v ? 0x18 : 0x19 ]); |
|
|
} else if (typeof v === 'number') { |
|
|
} else if (typeof v === 'number') { |
|
@ -930,10 +985,8 @@ function formatDebugValue(v) { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// XXX: missing support for various types (pointer, object, lightfunc, buffer)
|
|
|
// Shouldn't come here.
|
|
|
// as they're not needed right now.
|
|
|
throw new TypeError('value cannot be converted to dvalue: ' + JSON.stringify(v)); |
|
|
|
|
|
|
|
|
throw new TypeError('value cannot be converted to dvalue: ' + v); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/* |
|
|
/* |
|
@ -1386,10 +1439,10 @@ Debugger.prototype.sendGetBytecodeRequest = function () { |
|
|
preformatted = preformatted.join('\n') + '\n'; |
|
|
preformatted = preformatted.join('\n') + '\n'; |
|
|
|
|
|
|
|
|
ret = { |
|
|
ret = { |
|
|
"constants": consts, |
|
|
constants: consts, |
|
|
"functions": funcs, |
|
|
functions: funcs, |
|
|
"bytecode": bcode, |
|
|
bytecode: bcode, |
|
|
"preformatted": preformatted |
|
|
preformatted: preformatted |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
return ret; |
|
|
return ret; |
|
@ -2073,6 +2126,251 @@ DebugWebServer.prototype.emitLocals = function () { |
|
|
this.socket.emit('locals', newMsg); |
|
|
this.socket.emit('locals', newMsg); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
* JSON debug proxy |
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
function DebugProxy(serverPort) { |
|
|
|
|
|
this.serverPort = serverPort; |
|
|
|
|
|
this.server = null; |
|
|
|
|
|
this.socket = null; |
|
|
|
|
|
this.targetStream = null; |
|
|
|
|
|
this.inputParser = null; |
|
|
|
|
|
|
|
|
|
|
|
// preformatted dvalues
|
|
|
|
|
|
this.dval_eom = formatDebugValue(DVAL_EOM); |
|
|
|
|
|
this.dval_req = formatDebugValue(DVAL_REQ); |
|
|
|
|
|
this.dval_rep = formatDebugValue(DVAL_REP); |
|
|
|
|
|
this.dval_nfy = formatDebugValue(DVAL_NFY); |
|
|
|
|
|
this.dval_err = formatDebugValue(DVAL_ERR); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DebugProxy.prototype.determineCommandNumber = function (cmdString, cmdNumber) { |
|
|
|
|
|
var ret; |
|
|
|
|
|
if (typeof cmdString === 'string') { |
|
|
|
|
|
ret = debugCommandNumbers[cmdString]; |
|
|
|
|
|
} |
|
|
|
|
|
ret = ret || cmdNumber; |
|
|
|
|
|
if (typeof ret !== 'number') { |
|
|
|
|
|
throw Error('cannot figure out command number for "' + cmdString + '" (' + cmdNumber + ')'); |
|
|
|
|
|
} |
|
|
|
|
|
return ret; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
DebugProxy.prototype.commandNumberToString = function (id) { |
|
|
|
|
|
return debugCommandNames[id] || String(id); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
DebugProxy.prototype.formatDvalues = function (args) { |
|
|
|
|
|
if (!args) { |
|
|
|
|
|
return []; |
|
|
|
|
|
} |
|
|
|
|
|
return args.map(function (v) { |
|
|
|
|
|
return formatDebugValue(v); |
|
|
|
|
|
}); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
DebugProxy.prototype.writeJson = function (val) { |
|
|
|
|
|
this.socket.write(JSON.stringify(val) + '\n'); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
DebugProxy.prototype.writeJsonSafe = function (val) { |
|
|
|
|
|
try { |
|
|
|
|
|
this.writeJson(val); |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
console.log('Failed to write JSON in writeJsonSafe, ignoring: ' + e); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
DebugProxy.prototype.disconnectJsonClient = function () { |
|
|
|
|
|
if (this.socket) { |
|
|
|
|
|
this.socket.destroy(); |
|
|
|
|
|
this.socket = null; |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
DebugProxy.prototype.disconnectTarget = function () { |
|
|
|
|
|
if (this.inputParser) { |
|
|
|
|
|
this.inputParser.close(); |
|
|
|
|
|
this.inputParser = null; |
|
|
|
|
|
} |
|
|
|
|
|
if (this.targetStream) { |
|
|
|
|
|
this.targetStream.destroy(); |
|
|
|
|
|
this.targetStream = null; |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
DebugProxy.prototype.run = function () { |
|
|
|
|
|
var _this = this; |
|
|
|
|
|
|
|
|
|
|
|
console.log('Waiting for client connections on port ' + this.serverPort); |
|
|
|
|
|
this.server = net.createServer(function (socket) { |
|
|
|
|
|
console.log('JSON proxy client connected'); |
|
|
|
|
|
|
|
|
|
|
|
_this.disconnectJsonClient(); |
|
|
|
|
|
_this.disconnectTarget(); |
|
|
|
|
|
|
|
|
|
|
|
// A byline-parser is simple and good enough for now (assume
|
|
|
|
|
|
// compact JSON with no newlines).
|
|
|
|
|
|
var socketByline = byline(socket); |
|
|
|
|
|
_this.socket = socket; |
|
|
|
|
|
|
|
|
|
|
|
socketByline.on('data', function (line) { |
|
|
|
|
|
try { |
|
|
|
|
|
// console.log('Received json proxy input line: ' + line.toString('utf8'));
|
|
|
|
|
|
var msg = JSON.parse(line.toString('utf8')); |
|
|
|
|
|
var first_dval; |
|
|
|
|
|
var args_dvalues = _this.formatDvalues(msg.args); |
|
|
|
|
|
var last_dval = _this.dval_eom; |
|
|
|
|
|
var cmd; |
|
|
|
|
|
|
|
|
|
|
|
if (msg.request) { |
|
|
|
|
|
// "request" can be a string or "true"
|
|
|
|
|
|
first_dval = _this.dval_req; |
|
|
|
|
|
cmd = _this.determineCommandNumber(msg.request, msg.command); |
|
|
|
|
|
} else if (msg.reply) { |
|
|
|
|
|
first_dval = _this.dval_rep; |
|
|
|
|
|
} else if (msg.notify) { |
|
|
|
|
|
// "notify" can be a string or "true"
|
|
|
|
|
|
first_dval = _this.dval_nfy; |
|
|
|
|
|
cmd = _this.determineCommandNumber(msg.notify, msg.command); |
|
|
|
|
|
} else if (msg.error) { |
|
|
|
|
|
first_dval = _this.dval_err; |
|
|
|
|
|
} else { |
|
|
|
|
|
throw new Error('Invalid input JSON message: ' + JSON.stringify(msg)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
_this.targetStream.write(first_dval); |
|
|
|
|
|
if (cmd) { |
|
|
|
|
|
_this.targetStream.write(formatDebugValue(cmd)); |
|
|
|
|
|
} |
|
|
|
|
|
args_dvalues.forEach(function (v) { |
|
|
|
|
|
_this.targetStream.write(v); |
|
|
|
|
|
}); |
|
|
|
|
|
_this.targetStream.write(last_dval); |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
console.log(e); |
|
|
|
|
|
|
|
|
|
|
|
_this.writeJsonSafe({ |
|
|
|
|
|
notify: '_Error', |
|
|
|
|
|
args: [ 'Failed to handle input json message: ' + e ] |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
_this.disconnectJsonClient(); |
|
|
|
|
|
_this.disconnectTarget(); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
_this.connectToTarget(); |
|
|
|
|
|
}).listen(this.serverPort); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
DebugProxy.prototype.connectToTarget = function () { |
|
|
|
|
|
var _this = this; |
|
|
|
|
|
|
|
|
|
|
|
console.log('Connecting to ' + optTargetHost + ':' + optTargetPort + '...'); |
|
|
|
|
|
this.targetStream = new net.Socket(); |
|
|
|
|
|
this.targetStream.connect(optTargetPort, optTargetHost, function () { |
|
|
|
|
|
console.log('Debug transport connected'); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
this.inputParser = new DebugProtocolParser( |
|
|
|
|
|
this.targetStream, |
|
|
|
|
|
null, |
|
|
|
|
|
optDumpDebugRead, |
|
|
|
|
|
optDumpDebugPretty, |
|
|
|
|
|
optDumpDebugPretty ? 'Recv: ' : null, |
|
|
|
|
|
null, |
|
|
|
|
|
null // console logging is done at a higher level to match request/response
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// Don't add a '_value' key to numbers.
|
|
|
|
|
|
this.inputParser.readableNumberValue = false; |
|
|
|
|
|
|
|
|
|
|
|
this.inputParser.on('transport-close', function () { |
|
|
|
|
|
console.log('Debug transport closed'); |
|
|
|
|
|
|
|
|
|
|
|
_this.writeJsonSafe({ |
|
|
|
|
|
notify: '_Disconnecting' |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
_this.disconnectJsonClient(); |
|
|
|
|
|
_this.disconnectTarget(); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
this.inputParser.on('transport-error', function (err) { |
|
|
|
|
|
console.log('Debug transport error', err); |
|
|
|
|
|
|
|
|
|
|
|
_this.writeJsonSafe({ |
|
|
|
|
|
notify: '_Error', |
|
|
|
|
|
args: [ String(err) ] |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
this.inputParser.on('protocol-version', function (msg) { |
|
|
|
|
|
var ver = msg.protocolVersion; |
|
|
|
|
|
console.log('Debug version identification:', msg.versionIdentification); |
|
|
|
|
|
|
|
|
|
|
|
_this.writeJson({ |
|
|
|
|
|
notify: '_Connected', |
|
|
|
|
|
args: [ msg.versionIdentification ] // raw identification string
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if (ver !== 1) { |
|
|
|
|
|
console.log('Protocol version ' + ver + ' unsupported, dropping connection'); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
this.inputParser.on('debug-message', function (msg) { |
|
|
|
|
|
var t; |
|
|
|
|
|
|
|
|
|
|
|
//console.log(msg);
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof msg[0] !== 'object' || msg[0] === null) { |
|
|
|
|
|
throw new Error('unexpected initial dvalue: ' + msg[0]); |
|
|
|
|
|
} else if (msg.type === 'eom') { |
|
|
|
|
|
throw new Error('unexpected initial dvalue: ' + msg[0]); |
|
|
|
|
|
} else if (msg.type === 'req') { |
|
|
|
|
|
if (typeof msg[1] !== 'number') { |
|
|
|
|
|
throw new Error('unexpected request command number: ' + msg[1]); |
|
|
|
|
|
} |
|
|
|
|
|
t = { |
|
|
|
|
|
request: _this.commandNumberToString(msg[1]), |
|
|
|
|
|
command: msg[1], |
|
|
|
|
|
args: msg.slice(2, msg.length - 1) |
|
|
|
|
|
} |
|
|
|
|
|
_this.writeJson(t); |
|
|
|
|
|
} else if (msg[0].type === 'rep') { |
|
|
|
|
|
t = { |
|
|
|
|
|
reply: true, |
|
|
|
|
|
args: msg.slice(1, msg.length - 1) |
|
|
|
|
|
} |
|
|
|
|
|
_this.writeJson(t); |
|
|
|
|
|
} else if (msg[0].type === 'err') { |
|
|
|
|
|
t = { |
|
|
|
|
|
error: true, |
|
|
|
|
|
args: msg.slice(1, msg.length - 1) |
|
|
|
|
|
} |
|
|
|
|
|
_this.writeJson(t); |
|
|
|
|
|
} else if (msg[0].type === 'nfy') { |
|
|
|
|
|
if (typeof msg[1] !== 'number') { |
|
|
|
|
|
throw new Error('unexpected notify command number: ' + msg[1]); |
|
|
|
|
|
} |
|
|
|
|
|
t = { |
|
|
|
|
|
notify: _this.commandNumberToString(msg[1]), |
|
|
|
|
|
command: msg[1], |
|
|
|
|
|
args: msg.slice(2, msg.length - 1) |
|
|
|
|
|
} |
|
|
|
|
|
_this.writeJson(t); |
|
|
|
|
|
} else { |
|
|
|
|
|
throw new Error('unexpected initial dvalue: ' + msg[0]); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
this.inputParser.on('stats-update', function () { |
|
|
|
|
|
}); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
/* |
|
|
/* |
|
|
* Command line parsing and initialization |
|
|
* Command line parsing and initialization |
|
|
*/ |
|
|
*/ |
|
@ -2093,6 +2391,12 @@ function main() { |
|
|
if (argv['http-port']) { |
|
|
if (argv['http-port']) { |
|
|
optHttpPort = argv['http-port']; |
|
|
optHttpPort = argv['http-port']; |
|
|
} |
|
|
} |
|
|
|
|
|
if (argv['json-proxy-port']) { |
|
|
|
|
|
optJsonProxyPort = argv['json-proxy-port']; |
|
|
|
|
|
} |
|
|
|
|
|
if (argv['json-proxy']) { |
|
|
|
|
|
optJsonProxy = argv['json-proxy']; |
|
|
|
|
|
} |
|
|
if (argv['source-dirs']) { |
|
|
if (argv['source-dirs']) { |
|
|
optSourceSearchDirs = argv['source-dirs'].split(path.delimiter); |
|
|
optSourceSearchDirs = argv['source-dirs'].split(path.delimiter); |
|
|
} |
|
|
} |
|
@ -2116,6 +2420,8 @@ function main() { |
|
|
console.log(' --target-host: ' + optTargetHost); |
|
|
console.log(' --target-host: ' + optTargetHost); |
|
|
console.log(' --target-port: ' + optTargetPort); |
|
|
console.log(' --target-port: ' + optTargetPort); |
|
|
console.log(' --http-port: ' + optHttpPort); |
|
|
console.log(' --http-port: ' + optHttpPort); |
|
|
|
|
|
console.log(' --json-proxy-port: ' + optJsonProxyPort); |
|
|
|
|
|
console.log(' --json-proxy: ' + optJsonProxy); |
|
|
console.log(' --source-dirs: ' + optSourceSearchDirs.join(' ')); |
|
|
console.log(' --source-dirs: ' + optSourceSearchDirs.join(' ')); |
|
|
console.log(' --dump-debug-read: ' + optDumpDebugRead); |
|
|
console.log(' --dump-debug-read: ' + optDumpDebugRead); |
|
|
console.log(' --dump-debug-write: ' + optDumpDebugWrite); |
|
|
console.log(' --dump-debug-write: ' + optDumpDebugWrite); |
|
@ -2126,12 +2432,19 @@ function main() { |
|
|
// Create debugger and web UI singletons, tie them together and
|
|
|
// Create debugger and web UI singletons, tie them together and
|
|
|
// start them.
|
|
|
// start them.
|
|
|
|
|
|
|
|
|
var dbg = new Debugger(); |
|
|
if (optJsonProxy) { |
|
|
var web = new DebugWebServer(); |
|
|
console.log('Starting in JSON proxy mode, JSON port: ' + optJsonProxyPort); |
|
|
dbg.web = web; |
|
|
|
|
|
web.dbg = dbg; |
|
|
var prx = new DebugProxy(optJsonProxyPort); |
|
|
dbg.run(); |
|
|
prx.run(); |
|
|
web.run(); |
|
|
} else { |
|
|
|
|
|
var dbg = new Debugger(); |
|
|
|
|
|
var web = new DebugWebServer(); |
|
|
|
|
|
dbg.web = web; |
|
|
|
|
|
web.dbg = dbg; |
|
|
|
|
|
dbg.run(); |
|
|
|
|
|
web.run(); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
main(); |
|
|
main(); |
|
|