Android debugger class enhancements

Support for thread info - name and status
Reference count global suspends to allow correct resuming when multiple events are triggered.
Added methods for suspend/resume individual threads
This commit is contained in:
adelphes
2017-02-01 10:10:22 +00:00
parent ab68b83900
commit d1ab9a8339
2 changed files with 135 additions and 13 deletions

View File

@@ -141,6 +141,7 @@ Debugger.prototype = {
cpfilters: [], cpfilters: [],
preparedclasses: [], preparedclasses: [],
stepids: {}, // hashmap<threadid,stepid> stepids: {}, // hashmap<threadid,stepid>
suspendcount: 0, // refcount of suspend-all-threads
} }
return this; return this;
}, },
@@ -415,6 +416,35 @@ Debugger.prototype = {
}); });
}, },
threadinfos: function(thread_ids, extra) {
if (!Array.isArray(thread_ids))
thread_ids = [thread_ids];
var o = {
dbgr: this, thread_ids, extra, threadinfos:[], idx:0,
next() {
var thread_id = this.thread_ids[this.idx];
if (typeof(thread_id) === 'undefined')
return $.Deferred().resolveWith(this.dbgr, [this.threadinfos, this.extra]);
var info = {
threadid: thread_id,
name:'',
status:null,
};
return this.dbgr.session.adbclient.jdwp_command({ ths:this.dbgr, extra:info, cmd:this.dbgr.JDWP.Commands.threadname(info.threadid) })
.then((name,info) => {
info.name = name;
return this.dbgr.session.adbclient.jdwp_command({ ths:this.dbgr, extra:info, cmd:this.dbgr.JDWP.Commands.threadstatus(info.threadid) })
})
.then((status, info) => {
info.status = status;
this.threadinfos.push(info);
})
.always(() => (this.idx++,this.next()))
}
};
return this.ensureconnected(o).then(o => o.next());
},
suspend: function (extra) { suspend: function (extra) {
return this.ensureconnected(extra) return this.ensureconnected(extra)
.then(function (extra) { .then(function (extra) {
@@ -426,37 +456,67 @@ Debugger.prototype = {
}); });
}) })
.then(function () { .then(function () {
this.session.suspendcount++;
this._trigger('suspended'); this._trigger('suspended');
}); });
}, },
resume: function (extra) { suspendthread: function (threadid, extra) {
return this.ensureconnected(extra) return this.ensureconnected(extra)
.then(function (extra) { .then(function (extra) {
this._trigger('resuming');
this.session.stoppedlocation = null;
return this.session.adbclient.jdwp_command({ return this.session.adbclient.jdwp_command({
ths: this, ths: this,
extra: extra, extra: extra,
cmd: this.JDWP.Commands.resume(), cmd: this.JDWP.Commands.suspendthread(threadid),
}); });
}) })
.then((res,extra) => extra);
},
_resume:function(triggers, extra) {
return this.ensureconnected(extra)
.then(function (extra) {
if (triggers) this._trigger('resuming');
const resume_cmd = (decoded,extra) => {
return this.session.adbclient.jdwp_command({
ths: this,
extra: extra,
cmd: this.JDWP.Commands.resume(),
});
}
// we must resume with the same number of suspends
var def = resume_cmd(null, extra);
for (var i=1; i < this.session.suspendcount; i++) {
def = def.then(resume_cmd);
}
this.session.stoppedlocation = null;
this.session.suspendcount = 0;
return def;
})
.then(function (decoded, extra) { .then(function (decoded, extra) {
this._trigger('resumed'); if (triggers) this._trigger('resumed');
return extra; return extra;
}); });
}, },
resume: function (extra) {
return this._resume(true, extra);
},
_resumesilent: function () { _resumesilent: function () {
return this.ensureconnected() return this._resume(false);
.then(function () { },
this.session.stoppedlocation = null;
resumethread: function (threadid, extra) {
return this.ensureconnected(extra)
.then(function (extra) {
return this.session.adbclient.jdwp_command({ return this.session.adbclient.jdwp_command({
ths: this, ths: this,
//extra: extra, extra: extra,
cmd: this.JDWP.Commands.resume(), cmd: this.JDWP.Commands.resumethread(threadid),
}); });
}); })
.then((res,extra) => extra);
}, },
step: function (steptype, threadid) { step: function (steptype, threadid) {
@@ -1311,6 +1371,8 @@ Debugger.prototype = {
}, },
fn: function (e) { fn: function (e) {
var x = e.data; var x = e.data;
// each class prepare contributes a global suspend
x.dbgr.session.suspendcount++;
x.onprepare.apply(x.dbgr, [e.event]); x.onprepare.apply(x.dbgr, [e.event]);
} }
}; };
@@ -1339,6 +1401,8 @@ Debugger.prototype = {
dbgr: this, dbgr: this,
}, },
fn: function (e) { fn: function (e) {
// each step hit contributes a global suspend
e.data.dbgr.session.suspendcount++;
e.data.dbgr._clearLastStepRequest(e.event.threadid, e) e.data.dbgr._clearLastStepRequest(e.event.threadid, e)
.then(function (e) { .then(function (e) {
var x = e.data; var x = e.data;
@@ -1392,6 +1456,8 @@ Debugger.prototype = {
bp: x.dbgr.breakpoints.enabled[cmlkey].bp, bp: x.dbgr.breakpoints.enabled[cmlkey].bp,
}; };
x.dbgr.session.stoppedlocation = stoppedloc; x.dbgr.session.stoppedlocation = stoppedloc;
// each breakpoint hit contributes a global suspend
x.dbgr.session.suspendcount++;
// if this was a conditional breakpoint, it will have been automatically cleared // if this was a conditional breakpoint, it will have been automatically cleared
// - set a new (unconditional) breakpoint in it's place // - set a new (unconditional) breakpoint in it's place
if (bp.conditions.hitcount) { if (bp.conditions.hitcount) {
@@ -1554,6 +1620,8 @@ Debugger.prototype = {
dbgr: this, dbgr: this,
}, },
fn: function (e) { fn: function (e) {
// each exception hit contributes a global suspend
x.dbgr.session.suspendcount++;
// if this exception break occurred during a step request, we must manually clear the event // if this exception break occurred during a step request, we must manually clear the event
// or the (device-side) debugger will crash on next step // or the (device-side) debugger will crash on next step
this._clearLastStepRequest(e.event.threadid, e).then(e => { this._clearLastStepRequest(e.event.threadid, e).then(e => {

View File

@@ -250,6 +250,12 @@ function _JDWP() {
decodeStatus : function(o) { decodeStatus : function(o) {
return this.mapflags(this.decodeInt(o), ['verified','prepared','initialized','error']); 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) { decodeTaggedObjectID : function(o) {
return this.decodeValue(o); return this.decodeValue(o);
}, },
@@ -721,6 +727,7 @@ function _JDWP() {
} }
); );
}, },
// nestedTypes is not implemented on android
nestedTypes:function(ci) { nestedTypes:function(ci) {
return new Command('NestedTypes:'+ci.name, 2, 8, return new Command('NestedTypes:'+ci.name, 2, 8,
function() { function() {
@@ -733,7 +740,7 @@ function _JDWP() {
var arrlen = DataCoder.decodeInt(o); var arrlen = DataCoder.decodeInt(o);
while (--arrlen>=0) { while (--arrlen>=0) {
var v = DataCoder.decodeList(o, [{reftype:'reftype'},{typeid:'tref'}]); var v = DataCoder.decodeList(o, [{reftype:'reftype'},{typeid:'tref'}]);
res.vars.push(v); res.push(v);
} }
return res; return res;
} }
@@ -1163,6 +1170,26 @@ function _JDWP() {
resume:function() { resume:function() {
return new Command('resume',1, 9, null, null); 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() { allthreads:function() {
return new Command('allthreads',1, 4, return new Command('allthreads',1, 4,
null, null,
@@ -1175,7 +1202,34 @@ function _JDWP() {
return res; 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),
}
}
);
},
}; };
} }