mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-24 02:19:15 +00:00
Separate out thread-specific parts Only pause event thread for step, bp and thread events Continue now resumes the specified thread instead of all threads Prioritise stepping thread to prevent context switching during step Monitor thread starts/ends
1263 lines
36 KiB
JavaScript
1263 lines
36 KiB
JavaScript
const $ = require('./jq-promise');
|
|
const { atob,btoa,D,getutf8bytes,fromutf8bytes,intToHex } = require('./util');
|
|
/*
|
|
JDWP - The Java Debug Wire Protocol
|
|
*/
|
|
function _JDWP() {
|
|
var gCommandId = 0;
|
|
var gCommandList = [];
|
|
var gEventCallbacks = {};
|
|
|
|
function Command(name, cs, cmd, outdatafn, replydecodefn) {
|
|
this.length = 11;
|
|
this.id = ++gCommandId;
|
|
this.flags = 0;
|
|
this.commandset = cs;
|
|
this.command = cmd;
|
|
this.rawdata = outdatafn?outdatafn():[];
|
|
|
|
this.length = 11 + this.rawdata.length;
|
|
gCommandList[this.id] = this;
|
|
|
|
this.name = name;
|
|
this.replydecodefn = replydecodefn;
|
|
this.deferred = $.Deferred();
|
|
}
|
|
|
|
Command.prototype = {
|
|
promise : function() {
|
|
return this.deferred.promise();
|
|
},
|
|
toRawString : function() {
|
|
var s = '';
|
|
s += String.fromCharCode((this.length >> 24)&255);
|
|
s += String.fromCharCode((this.length >> 16)&255);
|
|
s += String.fromCharCode((this.length >> 8)&255);
|
|
s += String.fromCharCode((this.length)&255);
|
|
s += String.fromCharCode((this.id >> 24)&255);
|
|
s += String.fromCharCode((this.id >> 16)&255);
|
|
s += String.fromCharCode((this.id >> 8)&255);
|
|
s += String.fromCharCode((this.id)&255);
|
|
s += String.fromCharCode(this.flags);
|
|
s += String.fromCharCode(this.commandset);
|
|
s += String.fromCharCode(this.command);
|
|
var i=this.rawdata.length, j=0;
|
|
while (--i>=0) {
|
|
s += String.fromCharCode(this.rawdata[j++]);
|
|
}
|
|
return s;
|
|
},
|
|
tob64 : function() {
|
|
return btoa(this.toRawString());
|
|
}
|
|
};
|
|
|
|
function Reply(s) {
|
|
this.length = s.charCodeAt(0) << 24;
|
|
this.length += s.charCodeAt(1) << 16;
|
|
this.length += s.charCodeAt(2) << 8;
|
|
this.length += s.charCodeAt(3);
|
|
this.id = s.charCodeAt(4) << 24;
|
|
this.id += s.charCodeAt(5) << 16;
|
|
this.id += s.charCodeAt(6) << 8;
|
|
this.id += s.charCodeAt(7);
|
|
this.flags = s.charCodeAt(8)|0;
|
|
this.errorcode = s.charCodeAt(9) << 8;
|
|
this.errorcode += s.charCodeAt(10);
|
|
this.rawdata = new Array(s.length-11);
|
|
var i=0, j=this.rawdata.length;
|
|
while (--j>=0) {
|
|
this.rawdata[i]=s.charCodeAt(i+11);
|
|
i++;
|
|
}
|
|
this.command = gCommandList[this.id];
|
|
|
|
if (this.errorcode===16484) {
|
|
// errorcode===16484 (0x4064) means a composite event command (set 64,cmd 100) sent from the VM
|
|
this.errorcode=0;
|
|
this.isevent=!0;
|
|
this.decoded=DataCoder.decodeCompositeEvent({
|
|
idx:0,
|
|
data:this.rawdata.slice()
|
|
});
|
|
// call any registered event callbacks
|
|
for (var i in this.decoded.events) {
|
|
var event = this.decoded.events[i];
|
|
var cbinfo = event.reqid && gEventCallbacks[event.reqid];
|
|
if (cbinfo) {
|
|
var e = {
|
|
data:cbinfo.callback.data,
|
|
event:event,
|
|
reply:this,
|
|
};
|
|
cbinfo.callback.fn.call(cbinfo.callback.ths, e);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (this.errorcode != 0) {
|
|
console.error("Command failed: error " + this.errorcode, this);
|
|
}
|
|
|
|
if (!this.errorcode && this.command && this.command.replydecodefn) {
|
|
// try and decode the values
|
|
this.decoded = this.command.replydecodefn({
|
|
idx:0,
|
|
data:this.rawdata.slice()
|
|
});
|
|
return;
|
|
}
|
|
|
|
this.decoded = {empty:true};
|
|
}
|
|
|
|
this.decodereply = function(ths,s) {
|
|
var reply = new Reply(s);
|
|
if (reply.command) {
|
|
reply.command.deferred.resolveWith(ths, [reply.decoded, reply.command, reply]);
|
|
}
|
|
return reply;
|
|
};
|
|
|
|
this.signaturetotype = function(s) {
|
|
return DataCoder.signaturetotype(s);
|
|
}
|
|
|
|
this.setIDSizes = function(idsizes) {
|
|
DataCoder._idsizes = idsizes;
|
|
}
|
|
|
|
var DataCoder = {
|
|
_idsizes:null,
|
|
|
|
nullRefValue: function() {
|
|
if (!this._idsizes._nullreftypeid) {
|
|
var x = '00', len = this._idsizes.reftypeidsize * 2; // each byte needs 2 chars
|
|
while (x.length < len) x += x;
|
|
this._idsizes._nullreftypeid = x.slice(0, len); // should be power of 2, but just in case...
|
|
}
|
|
return this._idsizes._nullreftypeid;
|
|
},
|
|
|
|
decodeString: function(o) {
|
|
var rd = o.data;
|
|
var utf8len=(rd[o.idx++]<<24)+(rd[o.idx++]<<16)+(rd[o.idx++]<<8)+(rd[o.idx++]);
|
|
if (utf8len > 10000)
|
|
utf8len = 10000; // just to prevent hangs if the decoding is wrong
|
|
var res=fromutf8bytes(o.data.slice(o.idx, o.idx+utf8len));
|
|
o.idx+= utf8len;
|
|
return res;
|
|
},
|
|
decodeLong: function(o, hexstring) {
|
|
var rd = o.data;
|
|
var res1=(rd[o.idx++]<<24)+(rd[o.idx++]<<16)+(rd[o.idx++]<<8)+(rd[o.idx++]);
|
|
var res2=(rd[o.idx++]<<24)+(rd[o.idx++]<<16)+(rd[o.idx++]<<8)+(rd[o.idx++]);
|
|
return intToHex(res1>>>0,8)+intToHex(res2>>>0,8); // >>> 0 ensures +ve value
|
|
},
|
|
decodeInt: function(o) {
|
|
var rd = o.data;
|
|
var res=(rd[o.idx++]<<24)+(rd[o.idx++]<<16)+(rd[o.idx++]<<8)+(rd[o.idx++]);
|
|
return res;
|
|
},
|
|
decodeByte: function(o) {
|
|
var i = o.data[o.idx++];
|
|
return i<128?i:i-256;
|
|
},
|
|
decodeShort: function(o) {
|
|
var i = (o.data[o.idx++]<<8)+o.data[o.idx++];
|
|
return i<32768?i:i-65536;
|
|
},
|
|
decodeChar: function(o) {
|
|
return (o.data[o.idx++]<<8)+o.data[o.idx++]; // uint16
|
|
},
|
|
decodeBoolean: function(o) {
|
|
return o.data[o.idx++] != 0;
|
|
},
|
|
decodeDecimal: function(bytes, signBits, exponentBits, fractionBits, eMin, eMax, littleEndian) {
|
|
var totalBits = (signBits + exponentBits + fractionBits);
|
|
|
|
var binary = "";
|
|
for (var i = 0, l = bytes.length; i < l; i++) {
|
|
var bits = bytes[i].toString(2);
|
|
while (bits.length < 8)
|
|
bits = "0" + bits;
|
|
|
|
if (littleEndian)
|
|
binary = bits + binary;
|
|
else
|
|
binary += bits;
|
|
}
|
|
|
|
var sign = (binary.charAt(0) == '1')?-1:1;
|
|
var exponent = parseInt(binary.substr(signBits, exponentBits), 2) - eMax;
|
|
var significandBase = binary.substr(signBits + exponentBits, fractionBits);
|
|
var significandBin = '1'+significandBase;
|
|
var i = 0;
|
|
var val = 1;
|
|
var significand = 0;
|
|
|
|
if (exponent+eMax===((eMax*2)+1)) {
|
|
if (significandBase.indexOf('1')<0)
|
|
return sign>0?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY;
|
|
return Number.NaN;
|
|
}
|
|
if (exponent == -eMax) {
|
|
if (significandBase.indexOf('1') == -1)
|
|
return 0;
|
|
else {
|
|
exponent = eMin;
|
|
significandBin = '0'+significandBase;
|
|
}
|
|
}
|
|
|
|
while (i < significandBin.length) {
|
|
significand += val * parseInt(significandBin.charAt(i));
|
|
val = val / 2;
|
|
i++;
|
|
}
|
|
|
|
return sign * significand * Math.pow(2, exponent);
|
|
},
|
|
decodeFloat: function(o) {
|
|
var bytes = o.data.slice(o.idx, o.idx+=4);
|
|
return this.decodeDecimal(bytes, 1, 8, 23, -126, 127, false);
|
|
},
|
|
decodeDouble: function(o) {
|
|
var bytes = o.data.slice(o.idx, o.idx+=8);
|
|
return this.decodeDecimal(bytes, 1, 11, 52, -1022, 1023, false);
|
|
},
|
|
decodeRef: function(o, bytes) {
|
|
var rd = o.data;
|
|
var res = '';
|
|
while (--bytes>=0) {
|
|
res += ('0'+rd[o.idx++].toString(16)).slice(-2);
|
|
}
|
|
return res;
|
|
},
|
|
decodeTRef: function(o) {
|
|
return this.decodeRef(o,this._idsizes.reftypeidsize);
|
|
},
|
|
decodeORef: function(o) {
|
|
return this.decodeRef(o,this._idsizes.objectidsize);
|
|
},
|
|
decodeMRef: function(o) {
|
|
return this.decodeRef(o,this._idsizes.methodidsize);
|
|
},
|
|
decodeRefType : function(o) {
|
|
return this.mapvalue(this.decodeByte(o), [null,'class','interface','array']);
|
|
},
|
|
decodeStatus : function(o) {
|
|
return this.mapflags(this.decodeInt(o), ['verified','prepared','initialized','error']);
|
|
},
|
|
decodeThreadStatus : function(o) {
|
|
return ['zombie','running','sleeping','monitor','wait'][this.decodeInt(o)] || '';
|
|
},
|
|
decodeSuspendStatus : function(o) {
|
|
return this.decodeInt(o) ? 'suspended': '';
|
|
},
|
|
decodeTaggedObjectID : function(o) {
|
|
return this.decodeValue(o);
|
|
},
|
|
decodeValue : function(o) {
|
|
var rd = o.data;
|
|
return this.tagtodecoder(rd[o.idx++]).call(this, o);
|
|
},
|
|
tagtodecoder: function(tag) {
|
|
switch (tag) {
|
|
case 91:
|
|
case 76:
|
|
case 115:
|
|
case 116:
|
|
case 103:
|
|
case 108:
|
|
case 99:
|
|
return this.decodeORef;
|
|
case 66:
|
|
return this.decodeByte;
|
|
case 90:
|
|
return this.decodeBoolean;
|
|
case 67:
|
|
return this.decodeChar;
|
|
case 83:
|
|
return this.decodeShort;
|
|
case 70:
|
|
return this.decodeFloat;
|
|
case 73:
|
|
return this.decodeInt;
|
|
case 68:
|
|
return this.decodeDouble;
|
|
case 74:
|
|
return this.decodeLong;
|
|
case 86:
|
|
return function() { return 'void'; };
|
|
}
|
|
},
|
|
mapvalue : function(value,values) {
|
|
return {value: value, string:values[value] };
|
|
},
|
|
mapflags : function(value,values) {
|
|
var res = {value: value, string:'[]'};
|
|
var flgs=[];
|
|
for (var i=value,j=0;i;i>>=1) {
|
|
if ((i&1)&&(values[j]))
|
|
flgs.push(values[j]);
|
|
j++;
|
|
}
|
|
res.string = '['+flgs.join('|')+']';
|
|
return res;
|
|
},
|
|
decodeList: function(o, list) {
|
|
var res = {};
|
|
while (list.length) {
|
|
var next = list.shift();
|
|
for ( var key in next) {
|
|
switch(next[key]) {
|
|
case 'string': res[key]=this.decodeString(o); break;
|
|
case 'int': res[key]=this.decodeInt(o); break;
|
|
case 'long': res[key]=this.decodeLong(o); break;
|
|
case 'byte': res[key]=this.decodeByte(o); break;
|
|
case 'fref': res[key]=this.decodeRef(o,this._idsizes.fieldidsize); break;
|
|
case 'mref': res[key]=this.decodeRef(o,this._idsizes.methodidsize); break;
|
|
case 'oref': res[key]=this.decodeRef(o,this._idsizes.objectidsize); break;
|
|
case 'tref': res[key]=this.decodeRef(o,this._idsizes.reftypeidsize); break;
|
|
case 'frameid': res[key]=this.decodeRef(o,this._idsizes.frameidsize); break;
|
|
case 'reftype': res[key]=this.decodeRefType(o); break;
|
|
case 'status': res[key]=this.decodeStatus(o); break;
|
|
case 'location': res[key]=this.decodeLocation(o); break;
|
|
case 'signature': res[key]=this.decodeTypeFromSignature(o); break;
|
|
case 'codeindex': res[key]=this.decodeLong(o, true); break;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
},
|
|
decodeLocation : function(o) {
|
|
return {
|
|
type: o.data[o.idx++],
|
|
cid: this.decodeTRef(o),
|
|
mid: this.decodeMRef(o),
|
|
idx: this.decodeLong(o, true),
|
|
};
|
|
},
|
|
decodeTypeFromSignature : function(o) {
|
|
var sig = this.decodeString(o);
|
|
return this.signaturetotype(sig);
|
|
},
|
|
decodeCompositeEvent: function (o) {
|
|
var rd = o.data;
|
|
var res = {};
|
|
res.suspend = rd[o.idx++];
|
|
res.events = [];
|
|
var arrlen = this.decodeInt(o);
|
|
while (--arrlen>=0) {
|
|
// all event types return kind+requestid as their first entries
|
|
var event = {
|
|
kind:{name:'', value:rd[o.idx++]},
|
|
};
|
|
var eventkinds = ['','step','breakpoint','framepop','exception','userdefined','threadstart','threadend','classprepare','classunload','classload'];
|
|
event.kind.name = eventkinds[event.kind.value];
|
|
switch(event.kind.value) {
|
|
case 1: // step
|
|
case 2: // breakpoint
|
|
event.reqid = this.decodeInt(o);
|
|
event.threadid = this.decodeORef(o);
|
|
event.location = this.decodeLocation(o);
|
|
break;
|
|
case 4: // exception
|
|
event.reqid = this.decodeInt(o);
|
|
event.threadid = this.decodeORef(o);
|
|
event.throwlocation = this.decodeLocation(o);
|
|
event.exception = this.decodeTaggedObjectID(o);
|
|
event.catchlocation = this.decodeLocation(o); // 0 = uncaught
|
|
break;
|
|
case 6: // thread start
|
|
case 7: // thread end
|
|
event.reqid = this.decodeInt(o);
|
|
event.threadid = this.decodeORef(o);
|
|
event.state = event.kind.value === 6 ? 'start' : 'end';
|
|
break;
|
|
case 8: // classprepare
|
|
event.reqid = this.decodeInt(o);
|
|
event.threadid = this.decodeORef(o);
|
|
event.reftype = this.decodeByte(o);
|
|
event.typeid = this.decodeTRef(o);
|
|
event.type = this.decodeTypeFromSignature(o);
|
|
event.status = this.decodeStatus(o);
|
|
break;
|
|
}
|
|
res.events.push(event);
|
|
}
|
|
return res;
|
|
},
|
|
|
|
encodeByte : function(res, i) {
|
|
res.push(i&255);
|
|
},
|
|
encodeBoolean : function(res, b) {
|
|
res.push(b?1:0);
|
|
},
|
|
encodeShort : function(res, i) {
|
|
res.push((i>>8)&255);
|
|
res.push((i)&255);
|
|
},
|
|
encodeInt : function(res, i) {
|
|
res.push((i>>24)&255);
|
|
res.push((i>>16)&255);
|
|
res.push((i>>8)&255);
|
|
res.push((i)&255);
|
|
},
|
|
encodeChar: function(res, c) {
|
|
// c can either be a 1 char string or an integer
|
|
this.encodeShort(res, typeof c === 'string' ? c.charCodeAt(0) : c);
|
|
},
|
|
encodeString : function(res, s) {
|
|
var utf8bytes = getutf8bytes(s);
|
|
this.encodeInt(res, utf8bytes.length);
|
|
for (var i=0; i < utf8bytes.length; i++)
|
|
res.push(utf8bytes[i]);
|
|
},
|
|
encodeRef: function(res, ref) {
|
|
if (ref === null) ref = this.nullRefValue();
|
|
for(var i=0; i < ref.length; i+=2) {
|
|
res.push(parseInt(ref.substring(i,i+2), 16));
|
|
}
|
|
},
|
|
encodeLong: function(res, l) {
|
|
for(var i=0; i < l.length; i+=2) {
|
|
res.push(parseInt(l.substring(i,i+2), 16));
|
|
}
|
|
},
|
|
encodeDouble: function(res, value) {
|
|
var hiWord = 0, loWord = 0;
|
|
switch (value) {
|
|
case Number.POSITIVE_INFINITY: hiWord = 0x7FF00000; break;
|
|
case Number.NEGATIVE_INFINITY: hiWord = 0xFFF00000; break;
|
|
case +0.0: hiWord = 0x00000000; break;//0x40000000; break;
|
|
case -0.0: hiWord = 0x80000000; break;//0xC0000000; break;
|
|
default:
|
|
if (Number.isNaN(value)) { hiWord = 0x7FF80000; break; }
|
|
|
|
if (value <= -0.0) {
|
|
hiWord = 0x80000000;
|
|
value = -value;
|
|
}
|
|
|
|
var exponent = Math.floor(Math.log(value) / Math.log(2));
|
|
var significand = Math.floor((value / Math.pow(2, exponent)) * Math.pow(2, 52));
|
|
|
|
loWord = significand & 0xFFFFFFFF;
|
|
significand /= Math.pow(2, 32);
|
|
|
|
exponent += 1023;
|
|
if (exponent >= 0x7FF) {
|
|
exponent = 0x7FF;
|
|
significand = 0;
|
|
} else if (exponent < 0) exponent = 0;
|
|
|
|
hiWord = hiWord | (exponent << 20);
|
|
hiWord = hiWord | (significand & ~(-1 << 20));
|
|
break;
|
|
}
|
|
this.encodeInt(res, hiWord);
|
|
this.encodeInt(res, loWord);
|
|
},
|
|
encodeFloat: function(res, value) {
|
|
var bytes = 0;
|
|
switch (value) {
|
|
case Number.POSITIVE_INFINITY: bytes = 0x7F800000; break;
|
|
case Number.NEGATIVE_INFINITY: bytes = 0xFF800000; break;
|
|
case +0.0: bytes = 0x00000000; break;//0x40000000; break;
|
|
case -0.0: bytes = 0x80000000; break;//0xC0000000l
|
|
default:
|
|
if (Number.isNaN(value)) { bytes = 0x7FC00000; break; }
|
|
|
|
if (value <= -0.0) {
|
|
bytes = 0x80000000;
|
|
value = -value;
|
|
}
|
|
|
|
var exponent = Math.floor(Math.log(value) / Math.log(2));
|
|
var significand = ((value / Math.pow(2, exponent)) * 0x00800000) | 0;
|
|
|
|
exponent += 127;
|
|
if (exponent >= 0xFF) {
|
|
exponent = 0xFF;
|
|
significand = 0;
|
|
} else if (exponent < 0) exponent = 0;
|
|
|
|
bytes = bytes | (exponent << 23);
|
|
bytes = bytes | (significand & ~(-1 << 23));
|
|
break;
|
|
}
|
|
|
|
this.encodeInt(res, bytes);
|
|
},
|
|
encodeValue: function(res, key, data) {
|
|
switch(key) {
|
|
case 'byte': this.encodeByte(res, data); break;
|
|
case 'short': this.encodeShort(res, data); break;
|
|
case 'int': this.encodeInt(res, data); break;
|
|
case 'long': this.encodeLong(res, data); break;
|
|
case 'boolean': this.encodeBoolean(res, data); break;
|
|
case 'char': this.encodeChar(res, data); break;
|
|
case 'float': this.encodeFloat(res, data); break;
|
|
case 'double': this.encodeDouble(res, data); break;
|
|
// note that strings are encoded as object references...
|
|
case 'oref': this.encodeRef(res,data); break;
|
|
}
|
|
},
|
|
|
|
encodeTaggedValue: function(res, key, data) {
|
|
switch(key) {
|
|
case 'byte': res.push(66); break;
|
|
case 'short': res.push(83); break;
|
|
case 'int': res.push(73); break;
|
|
case 'long': res.push(74); break;
|
|
case 'boolean': res.push(90); break;
|
|
case 'char': res.push(67); break;
|
|
case 'float': res.push(70); break;
|
|
case 'double': res.push(68); break;
|
|
case 'void': res.push(86); break;
|
|
// note that strings are encoded as object references...
|
|
case 'oref': res.push(76); break;
|
|
}
|
|
this.encodeValue(res, key, data);
|
|
},
|
|
|
|
signaturetotype:function(signature) {
|
|
var m = signature.match(/^L([^$]+)\/([^$\/]+)(\$.+)?;$/);
|
|
if (m) {
|
|
return {
|
|
signature: signature,
|
|
package: m[1].replace(/\//g,'.'),
|
|
typename: (m[2]+(m[3]||'')).replace(/\$(?=[^\d])/g,'.'),
|
|
anonymous: /\$\d/.test(m[3]),
|
|
}
|
|
}
|
|
m = signature.match(/^(\[+)(.+)$/);
|
|
if (m) {
|
|
var elementtype = this.signaturetotype(m[2]);
|
|
return {
|
|
signature:signature,
|
|
arraydims:m[1].length,
|
|
elementtype: elementtype,
|
|
typename:elementtype.typename+m[1].replace(/\[/g,'[]'),
|
|
}
|
|
}
|
|
var primitivetypes = {
|
|
B: { signature:'B', typename:'byte', primitive:true, },
|
|
C: { signature:'C', typename:'char', primitive:true, },
|
|
F: { signature:'F', typename:'float', primitive:true, },
|
|
D: { signature:'D', typename:'double', primitive:true, },
|
|
I: { signature:'I', typename:'int', primitive:true, },
|
|
J: { signature:'J', typename:'long', primitive:true, },
|
|
S: { signature:'S', typename:'short', primitive:true, },
|
|
V: { signature:'V', typename:'void', primitive:true, },
|
|
Z: { signature:'Z', typename:'boolean', primitive:true, },
|
|
}
|
|
var res = (signature.length===1)?primitivetypes[signature[0]]:null;
|
|
if (res) return res;
|
|
return {
|
|
signature:signature,
|
|
typename:signature,
|
|
invalid:true,
|
|
}
|
|
},
|
|
};
|
|
|
|
//var Commands = {
|
|
this.Commands = {
|
|
version:function() {
|
|
return new Command('version',1, 1,
|
|
null,
|
|
function (o) {
|
|
return DataCoder.decodeList(o, [{description:'string'},{major:'int'},{minor:'int'},{version:'string'},{name:'string'}]);
|
|
}
|
|
);
|
|
},
|
|
idsizes:function() {
|
|
return new Command('IDSizes', 1, 7,
|
|
function() {
|
|
return [];
|
|
},
|
|
function(o) {
|
|
return DataCoder.decodeList(o, [{fieldidsize:'int'},{methodidsize:'int'},{objectidsize:'int'},{reftypeidsize:'int'},{frameidsize:'int'}]);
|
|
}
|
|
);
|
|
},
|
|
classinfo:function(ci) {
|
|
return new Command('ClassesBySignature:'+ci.name, 1, 2,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeString(res, ci.type.signature);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
var res = [];
|
|
while (--arrlen>=0) {
|
|
res.push(DataCoder.decodeList(o, [{reftype:'reftype'},{typeid:'tref'},{status:'status'}]));
|
|
}
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
fields:function(ci) {
|
|
// not supported by Dalvik
|
|
return new Command('Fields:'+ci.name, 2, 4,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, ci.info.typeid);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
var res = [];
|
|
while (--arrlen>=0) {
|
|
res.push(DataCoder.decodeList(o, [{fieldid:'fref'},{name:'string'},{sig:'string'},{modbits:'int'}]));
|
|
}
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
methods:function(ci) {
|
|
// not supported by Dalvik - use methodsWithGeneric
|
|
return new Command('Methods:'+ci.name, 2, 5,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, ci.info.typeid);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
var res = [];
|
|
while (--arrlen>=0) {
|
|
res.push(DataCoder.decodeList(o, [{methodid:'mref'},{name:'string'},{sig:'string'},{modbits:'int'}]));
|
|
}
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
GetStaticFieldValues:function(typeid, fields) {
|
|
return new Command('GetStaticFieldValues:'+typeid, 2, 6,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, typeid);
|
|
DataCoder.encodeInt(res, fields.length);
|
|
for (var i in fields) {
|
|
DataCoder.encodeRef(res, fields[i].fieldid);
|
|
}
|
|
return res;
|
|
},
|
|
function(o) {
|
|
var res = [];
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
while (--arrlen>=0) {
|
|
var v = DataCoder.decodeValue(o);
|
|
res.push(v);
|
|
}
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
sourcefile:function(ci) {
|
|
return new Command('SourceFile:'+ci.name, 2, 7,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, ci.info.typeid);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
return [{'sourcefile':DataCoder.decodeString(o)}];
|
|
}
|
|
);
|
|
},
|
|
fieldsWithGeneric:function(ci) {
|
|
return new Command('FieldsWithGeneric:'+ci.name, 2, 14,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, ci.info.typeid);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
var res = [];
|
|
while (--arrlen>=0) {
|
|
var field = DataCoder.decodeList(o, [{fieldid:'fref'},{name:'string'},{type:'signature'},{genericsig:'string'},{modbits:'int'}]);
|
|
field.typeid = ci.info.typeid;
|
|
res.push(field);
|
|
}
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
methodsWithGeneric:function(ci) {
|
|
return new Command('MethodsWithGeneric:'+ci.name, 2, 15,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, ci.info.typeid);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
var res = [];
|
|
while (--arrlen>=0) {
|
|
res.push(DataCoder.decodeList(o, [{methodid:'mref'},{name:'string'},{sig:'string'},{genericsig:'string'},{modbits:'int'}]));
|
|
}
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
superclass:function(ci) {
|
|
return new Command('Superclass:'+ci.name, 3, 1,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, ci.info.typeid);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
return DataCoder.decodeTRef(o);
|
|
}
|
|
);
|
|
},
|
|
signature:function(typeid) {
|
|
return new Command('Signature:'+typeid, 2, 1,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, typeid);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
return DataCoder.decodeTypeFromSignature(o);
|
|
}
|
|
);
|
|
},
|
|
// nestedTypes is not implemented on android
|
|
nestedTypes:function(ci) {
|
|
return new Command('NestedTypes:'+ci.name, 2, 8,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, ci.info.typeid);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
var res=[];
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
while (--arrlen>=0) {
|
|
var v = DataCoder.decodeList(o, [{reftype:'reftype'},{typeid:'tref'}]);
|
|
res.push(v);
|
|
}
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
lineTable:function(ci, mi) {
|
|
return new Command('Linetable:'+ci.name+","+mi.name, 6, 1,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, ci.info.typeid);
|
|
DataCoder.encodeRef(res, mi.methodid);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
var res = {};
|
|
res.start = DataCoder.decodeLong(o, true);
|
|
res.end = DataCoder.decodeLong(o, true);
|
|
res.lines = [];
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
while (--arrlen>=0) {
|
|
var line = DataCoder.decodeList(o, [{linecodeidx:'codeindex'},{linenum:'int'}]);
|
|
res.lines.push(line);
|
|
}
|
|
// sort the lines by...um..line number
|
|
res.lines.sort(function(a,b) {
|
|
return a.linenum-b.linenum
|
|
|| a.linecodeidx-b.linecodeidx;
|
|
})
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
VariableTableWithGeneric:function(ci, mi) {
|
|
// VariableTable is not supported by Dalvik
|
|
return new Command('VariableTableWithGeneric:'+ci.name+","+mi.name, 6, 5,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, ci.info.typeid);
|
|
DataCoder.encodeRef(res, mi.methodid);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
var res = {};
|
|
res.argCnt = DataCoder.decodeInt(o);
|
|
res.vars = [];
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
while (--arrlen>=0) {
|
|
var v = DataCoder.decodeList(o, [{codeidx:'codeindex'},{name:'string'},{type:'signature'},{genericsig:'string'},{length:'int'},{slot:'int'}]);
|
|
res.vars.push(v);
|
|
}
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
Frames:function(threadid, start, count) {
|
|
return new Command('Frames:'+threadid, 11, 6,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, threadid);
|
|
DataCoder.encodeInt(res, start||0);
|
|
DataCoder.encodeInt(res, count||-1);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
var res = [];
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
while (--arrlen>=0) {
|
|
var v = DataCoder.decodeList(o, [{frameid:'frameid'},{location:'location'}]);
|
|
res.push(v);
|
|
}
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
GetStackValues:function(threadid, frameid, slots) {
|
|
return new Command('GetStackValues:'+threadid, 16, 1,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, threadid);
|
|
DataCoder.encodeRef(res, frameid);
|
|
DataCoder.encodeInt(res, slots.length);
|
|
for (var i in slots) {
|
|
DataCoder.encodeInt(res, slots[i].slot);
|
|
DataCoder.encodeByte(res, slots[i].tag);
|
|
}
|
|
return res;
|
|
},
|
|
function(o) {
|
|
var res = [];
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
while (--arrlen>=0) {
|
|
var v = DataCoder.decodeValue(o);
|
|
res.push(v);
|
|
}
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
SetStackValue:function(threadid, frameid, slot, data) {
|
|
return new Command('SetStackValue:'+threadid, 16, 2,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, threadid);
|
|
DataCoder.encodeRef(res, frameid);
|
|
DataCoder.encodeInt(res, 1);
|
|
DataCoder.encodeInt(res, slot);
|
|
DataCoder.encodeTaggedValue(res, data.valuetype, data.value);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
// there's no return data - if we reach here, the update was successfull
|
|
return true;
|
|
}
|
|
);
|
|
},
|
|
GetObjectType:function(objectid) {
|
|
return new Command('GetObjectType:'+objectid, 9, 1,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, objectid);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
DataCoder.decodeRefType(o);
|
|
return DataCoder.decodeTRef(o);
|
|
}
|
|
);
|
|
},
|
|
GetFieldValues:function(objectid, fields) {
|
|
return new Command('GetFieldValues:'+objectid, 9, 2,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, objectid);
|
|
DataCoder.encodeInt(res, fields.length);
|
|
for (var i in fields) {
|
|
DataCoder.encodeRef(res, fields[i].fieldid);
|
|
}
|
|
return res;
|
|
},
|
|
function(o) {
|
|
var res = [];
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
while (--arrlen>=0) {
|
|
var v = DataCoder.decodeValue(o);
|
|
res.push(v);
|
|
}
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
SetFieldValue:function(objectid, field, data) {
|
|
return new Command('SetFieldValue:'+objectid, 9, 3,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, objectid);
|
|
DataCoder.encodeInt(res, 1);
|
|
DataCoder.encodeRef(res, field.fieldid);
|
|
DataCoder.encodeValue(res, data.valuetype, data.value);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
// there's no return data - if we reach here, the update was successfull
|
|
return true;
|
|
}
|
|
);
|
|
},
|
|
InvokeMethod:function(objectid, threadid, classid, methodid, args) {
|
|
return new Command('InvokeMethod:'+[objectid, threadid, classid, methodid, args].join(','), 9, 6,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, objectid);
|
|
DataCoder.encodeRef(res, threadid);
|
|
DataCoder.encodeRef(res, classid);
|
|
DataCoder.encodeRef(res, methodid);
|
|
DataCoder.encodeInt(res, args.length);
|
|
args.forEach(arg => DataCoder.encodeValue(res, arg.type, arg.value));
|
|
DataCoder.encodeInt(res, 1); // INVOKE_SINGLE_THREADED
|
|
return res;
|
|
},
|
|
function(o) {
|
|
return {
|
|
return_value: DataCoder.decodeValue(o),
|
|
exception: DataCoder.decodeTaggedObjectID(o),
|
|
}
|
|
}
|
|
);
|
|
},
|
|
GetArrayLength:function(arrobjid) {
|
|
return new Command('GetArrayLength:'+arrobjid, 13, 1,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, arrobjid);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
return DataCoder.decodeInt(o);
|
|
}
|
|
);
|
|
},
|
|
GetArrayValues:function(arrobjid, idx, count) {
|
|
return new Command('GetArrayValues:'+arrobjid, 13, 2,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, arrobjid);
|
|
DataCoder.encodeInt(res, idx);
|
|
DataCoder.encodeInt(res, count);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
var res = [];
|
|
var tag = DataCoder.decodeByte(o);
|
|
var decodefn = DataCoder.tagtodecoder(tag);
|
|
// objects are decoded as values
|
|
if (decodefn===DataCoder.decodeORef)
|
|
decodefn = DataCoder.decodeValue;
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
while (--arrlen>=0) {
|
|
var v = decodefn.call(DataCoder, o);
|
|
res.push(v);
|
|
}
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
SetArrayElements:function(arrobjid, idx, count, data) {
|
|
return new Command('SetArrayElements:'+arrobjid, 13, 3,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, arrobjid);
|
|
DataCoder.encodeInt(res, idx);
|
|
DataCoder.encodeInt(res, count);
|
|
for (var i=0; i < count; i++)
|
|
DataCoder.encodeValue(res, data.valuetype, data.value);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
// there's no return data - if we reach here, the update was successfull
|
|
return true;
|
|
}
|
|
);
|
|
},
|
|
GetStringValue:function(strobjid) {
|
|
return new Command('GetStringValue:'+strobjid, 10, 1,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, strobjid);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
return DataCoder.decodeString(o);
|
|
}
|
|
);
|
|
},
|
|
CreateStringObject:function(text) {
|
|
return new Command('CreateStringObject:'+text.substring(0,20), 1, 11,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeString(res, text);
|
|
return res;
|
|
},
|
|
function(o) {
|
|
return DataCoder.decodeORef(o);
|
|
}
|
|
);
|
|
},
|
|
SetEventRequest:function(kindname, kind, suspend, modifiers, modifiercb, onevent) {
|
|
return new Command('SetEventRequest:'+kindname, 15, 1,
|
|
function() {
|
|
var res=[kind,suspend];
|
|
DataCoder.encodeInt(res, modifiers.length);
|
|
for (var i=0;i<modifiers.length; i++) {
|
|
modifiercb(modifiers[i], i, res);
|
|
}
|
|
return res;
|
|
},
|
|
function(o) {
|
|
var res = {
|
|
id:DataCoder.decodeInt(o),
|
|
callback: onevent,
|
|
};
|
|
gEventCallbacks[res.id] = res;
|
|
D('Accepted event request: '+kindname+', id:'+res.id);
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
ClearEvent:function(kindname, kind, requestid) {
|
|
return new Command('ClearEvent:'+kindname, 15, 2,
|
|
function() {
|
|
var res=[kind];
|
|
DataCoder.encodeInt(res, requestid);
|
|
D('Clearing event request: '+kindname+', id:'+requestid);
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
SetSingleStep:function(steptype, threadid, onevent) {
|
|
// a wrapper around SetEventRequest
|
|
var stepdepths = {into:0,over:1,out:2};
|
|
var mods =[{
|
|
modkind:10, // step
|
|
threadid: threadid,
|
|
size:1,// =Line
|
|
depth:stepdepths[steptype],
|
|
}];
|
|
// kind(1=singlestep)
|
|
// suspendpolicy(0=none,1=event-thread,2=all)
|
|
return this.SetEventRequest("step",1,1,mods,
|
|
function(m1, i, res) {
|
|
res.push(m1.modkind);
|
|
DataCoder.encodeRef(res, m1.threadid);
|
|
DataCoder.encodeInt(res, m1.size);
|
|
DataCoder.encodeInt(res, m1.depth);
|
|
},
|
|
onevent
|
|
);
|
|
},
|
|
SetBreakpoint:function(ci, mi, idx, hitcount, onevent) {
|
|
// a wrapper around SetEventRequest
|
|
var mods = [{
|
|
modkind:7, // location
|
|
loc:{ type:ci.info.reftype.value, cid:ci.info.typeid, mid:mi.methodid, idx:idx },
|
|
encode(res) {
|
|
res.push(this.modkind);
|
|
res.push(this.loc.type);
|
|
DataCoder.encodeRef(res, this.loc.cid);
|
|
DataCoder.encodeRef(res, this.loc.mid);
|
|
DataCoder.encodeLong(res, this.loc.idx);
|
|
}
|
|
}];
|
|
if (hitcount > 0) {
|
|
// remember when setting a hitcount, the event is automatically cancelled after being fired
|
|
mods.unshift({
|
|
modkind:1,
|
|
count: hitcount,
|
|
encode(res) {
|
|
res.push(this.modkind);
|
|
DataCoder.encodeInt(res, this.count);
|
|
}
|
|
})
|
|
}
|
|
// kind(2=breakpoint)
|
|
// suspendpolicy(0=none,1=event-thread,2=all)
|
|
return this.SetEventRequest("breakpoint",2,1,mods,
|
|
function(m, i, res) {
|
|
m.encode(res,i);
|
|
},
|
|
onevent
|
|
);
|
|
},
|
|
ClearStep:function(requestid) {
|
|
// kind(1=step)
|
|
return this.ClearEvent("step",1,requestid);
|
|
},
|
|
ClearBreakpoint:function(requestid) {
|
|
// kind(2=breakpoint)
|
|
return this.ClearEvent("breakpoint",2,requestid);
|
|
},
|
|
ThreadStartNotify:function(onevent) {
|
|
// a wrapper around SetEventRequest
|
|
var mods = [];
|
|
// kind(6=threadstart)
|
|
// suspendpolicy(0=none,1=event-thread,2=all)
|
|
return this.SetEventRequest("threadstart",6,1,mods,
|
|
function() {},
|
|
onevent
|
|
);
|
|
},
|
|
ThreadEndNotify:function(onevent) {
|
|
// a wrapper around SetEventRequest
|
|
var mods = [];
|
|
// kind(7=threadend)
|
|
// suspendpolicy(0=none,1=event-thread,2=all)
|
|
return this.SetEventRequest("threadend",7,1,mods,
|
|
function() {},
|
|
onevent
|
|
);
|
|
},
|
|
OnClassPrepare:function(pattern, onevent) {
|
|
// a wrapper around SetEventRequest
|
|
var mods = [{
|
|
modkind:5, // classmatch
|
|
pattern: pattern,
|
|
}];
|
|
// kind(8=classprepare)
|
|
// suspendpolicy(0=none,1=event-thread,2=all)
|
|
return this.SetEventRequest("classprepare",8,2,mods,
|
|
function(m1, i, res) {
|
|
res.push(m1.modkind);
|
|
DataCoder.encodeString(res, m1.pattern);
|
|
},
|
|
onevent
|
|
);
|
|
},
|
|
ClearExceptionBreak:function(requestid) {
|
|
// kind(4=exception)
|
|
return this.ClearEvent("exception",4,requestid);
|
|
},
|
|
SetExceptionBreak:function(pattern, caught, uncaught, onevent) {
|
|
// a wrapper around SetEventRequest
|
|
var mods = [{
|
|
modkind:8, // exceptiononly
|
|
reftypeid: DataCoder.nullRefValue(), // exception class
|
|
caught: caught,
|
|
uncaught: uncaught,
|
|
}];
|
|
pattern && mods.unshift({
|
|
modkind:5, // classmatch
|
|
pattern: pattern,
|
|
});
|
|
// kind(4=exception)
|
|
// suspendpolicy(0=none,1=event-thread,2=all)
|
|
return this.SetEventRequest("exception",4,1,mods,
|
|
function(m, i, res) {
|
|
res.push(m.modkind);
|
|
switch(m.modkind) {
|
|
case 5: DataCoder.encodeString(res, m.pattern); break;
|
|
case 8:
|
|
DataCoder.encodeRef(res, m.reftypeid);
|
|
DataCoder.encodeBoolean(res, m.caught);
|
|
DataCoder.encodeBoolean(res, m.uncaught);
|
|
break;
|
|
}
|
|
},
|
|
onevent
|
|
);
|
|
},
|
|
allclasses:function() {
|
|
// not supported by android
|
|
},
|
|
AllClassesWithGeneric:function() {
|
|
return new Command('allclasses',1,20,
|
|
null,
|
|
function(o) {
|
|
var res = [];
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
while (--arrlen>=0) {
|
|
res.push(DataCoder.decodeList(o, [{reftype:'reftype'},{typeid:'tref'},{type:'signature'},{genericSignature:'string'},{status:'status'}]));
|
|
}
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
suspend:function() {
|
|
return new Command('suspend',1, 8, null, null);
|
|
},
|
|
resume:function() {
|
|
return new Command('resume',1, 9, null, null);
|
|
},
|
|
suspendthread:function(threadid) {
|
|
return new Command('suspendthread:'+threadid,11, 2,
|
|
function() {
|
|
var res = [];
|
|
DataCoder.encodeRef(res, this);
|
|
return res;
|
|
}.bind(threadid),
|
|
null
|
|
);
|
|
},
|
|
resumethread:function(threadid) {
|
|
return new Command('resumethread:'+threadid,11, 3,
|
|
function() {
|
|
var res = [];
|
|
DataCoder.encodeRef(res, this);
|
|
return res;
|
|
}.bind(threadid),
|
|
null
|
|
);
|
|
},
|
|
allthreads:function() {
|
|
return new Command('allthreads',1, 4,
|
|
null,
|
|
function(o) {
|
|
var res = [];
|
|
var arrlen = DataCoder.decodeInt(o);
|
|
while (--arrlen>=0) {
|
|
res.push(DataCoder.decodeTRef(o));
|
|
}
|
|
return res;
|
|
}
|
|
);
|
|
},
|
|
threadname:function(threadid) {
|
|
return new Command('threadname',11,1,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, this);
|
|
return res;
|
|
}.bind(threadid),
|
|
function(o) {
|
|
return DataCoder.decodeString(o);
|
|
}
|
|
);
|
|
},
|
|
threadstatus:function(threadid) {
|
|
return new Command('threadstatus',11,4,
|
|
function() {
|
|
var res=[];
|
|
DataCoder.encodeRef(res, this);
|
|
return res;
|
|
}.bind(threadid),
|
|
function(o) {
|
|
return {
|
|
thread: DataCoder.decodeThreadStatus(o),
|
|
suspend: DataCoder.decodeSuspendStatus(o),
|
|
}
|
|
}
|
|
);
|
|
},
|
|
};
|
|
}
|
|
|
|
exports._JDWP = _JDWP;
|