mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-23 18:08:29 +00:00
Support attaching to running app (#85)
* add support for timeout on adb socket reads * add debugger support for attaching to a process * add new launch configuration and support for picking an Android process ID * initial support for attaching to android process * display enhanced quick pick list with pids and names * add flag to prevent disconnect messages when not connected * Retrieve all loaded classes during startup. This allows us to identify breakpoints in anonymous classes that are already loaded. * correct name of process picker command * make PickAndroidProcess command private * selectAndroidProcessID always returns an object * make breakpoint setup a loop instead of recursive * tidy some labels and error messages * use a more consistent command for retrieving process names * show pid list sorted by pid instead of name * refactor some Android and ADB-specific functions Check ANDROID_SDK as replacement for ANDROID_HOME * tidy up logcat launch and refactor target device selection * fix logcat not displaying * filter duplicates and blanks from logcat output
This commit is contained in:
121
src/adbclient.js
121
src/adbclient.js
@@ -68,14 +68,63 @@ class ADBClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of debuggable pids from the device
|
||||
* Return a list of debuggable pids from the device.
|
||||
*
|
||||
* The `adb jdwp` command never terminates - it just posts each debuggable PID
|
||||
* as it comes online. Normally we just perform a single read of stdout
|
||||
* and terminate the connection, but if there are no pids available, the command
|
||||
* will wait forever.
|
||||
* @param {number} [timeout_ms] time to wait before we abort reading (and return an empty list).
|
||||
*/
|
||||
async jdwp_list() {
|
||||
async jdwp_list(timeout_ms) {
|
||||
await this.connect_to_adb();
|
||||
await this.adbsocket.cmd_and_status(`host:transport:${this.deviceid}`);
|
||||
const stdout = await this.adbsocket.cmd_and_read_stdout('jdwp');
|
||||
/** @type {string} */
|
||||
let stdout;
|
||||
try {
|
||||
stdout = await this.adbsocket.cmd_and_read_stdout('jdwp', timeout_ms);
|
||||
} catch {
|
||||
// timeout or socket closed
|
||||
stdout = '';
|
||||
}
|
||||
await this.disconnect_from_adb();
|
||||
return stdout.trim().split(/\r?\n|\r/);
|
||||
// do not sort the pid list - the debugger needs to pick the last one in the list.
|
||||
return stdout.trim().split(/\s+/).filter(x => x).map(s => parseInt(s, 10));
|
||||
}
|
||||
|
||||
async named_jdwp_list(timeout_ms) {
|
||||
const named_pids = (await this.jdwp_list(timeout_ms))
|
||||
.map(pid => ({
|
||||
pid,
|
||||
name: '',
|
||||
}))
|
||||
if (!named_pids.length)
|
||||
return [];
|
||||
|
||||
// retrieve the list of process names from the device
|
||||
const command = `for pid in ${named_pids.map(np => np.pid).join(' ')}; do cat /proc/$pid/cmdline;echo " $pid"; done`;
|
||||
const stdout = await this.shell_cmd({
|
||||
command,
|
||||
untilclosed: true,
|
||||
});
|
||||
// output should look something like...
|
||||
// com.example.somepkg 32721
|
||||
const lines = stdout.replace(/\0+/g,'').split(/\r?\n|\r/g);
|
||||
|
||||
// scan the list looking for pids to match names with...
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let entries = lines[i].match(/^\s*(.*)\s+(\d+)$/);
|
||||
if (!entries) {
|
||||
continue;
|
||||
}
|
||||
const pid = parseInt(entries[2], 10);
|
||||
const named_pid = named_pids.find(x => x.pid === pid);
|
||||
if (named_pid) {
|
||||
named_pid.name = entries[1];
|
||||
}
|
||||
}
|
||||
|
||||
return named_pids;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,12 +181,14 @@ class ADBClient {
|
||||
|
||||
/**
|
||||
* Run a shell command on the connected device
|
||||
* @param {{command:string}} o
|
||||
* @param {{command:string, untilclosed?:boolean}} o
|
||||
* @param {number} [timeout_ms]
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async shell_cmd(o) {
|
||||
async shell_cmd(o, timeout_ms) {
|
||||
await this.connect_to_adb();
|
||||
await this.adbsocket.cmd_and_status(`host:transport:${this.deviceid}`);
|
||||
const stdout = await this.adbsocket.cmd_and_read_stdout(`shell:${o.command}`);
|
||||
const stdout = await this.adbsocket.cmd_and_read_stdout(`shell:${o.command}`, timeout_ms, o.untilclosed);
|
||||
await this.disconnect_from_adb();
|
||||
return stdout;
|
||||
}
|
||||
@@ -145,7 +196,7 @@ class ADBClient {
|
||||
/**
|
||||
* Starts the Logcat monitor.
|
||||
* Logcat lines are passed back via onlog callback. If the device disconnects, onclose is called.
|
||||
* @param {{onlog:(e)=>void, onclose:()=>void}} o
|
||||
* @param {{onlog:(e)=>void, onclose:(err)=>void}} o
|
||||
*/
|
||||
async startLogcatMonitor(o) {
|
||||
// onlog:function(e)
|
||||
@@ -157,37 +208,41 @@ class ADBClient {
|
||||
if (!o.onlog) {
|
||||
const logcatbuffer = await this.adbsocket.read_stdout();
|
||||
await this.disconnect_from_adb();
|
||||
return logcatbuffer;
|
||||
return logcatbuffer.toString();
|
||||
}
|
||||
|
||||
// start the logcat monitor
|
||||
let logcatbuffer = Buffer.alloc(0);
|
||||
const next_logcat_lines = async () => {
|
||||
// read the next data from ADB
|
||||
let logcatbuffer = Buffer.alloc(0);
|
||||
let next_data;
|
||||
try{
|
||||
next_data = await this.adbsocket.read_stdout(null);
|
||||
} catch(e) {
|
||||
o.onclose();
|
||||
return;
|
||||
for (;;) {
|
||||
// read the next data from ADB
|
||||
try {
|
||||
next_data = await this.adbsocket.read_stdout();
|
||||
} catch(e) {
|
||||
o.onclose(e);
|
||||
return;
|
||||
}
|
||||
logcatbuffer = Buffer.concat([logcatbuffer, next_data]);
|
||||
const last_newline_index = logcatbuffer.lastIndexOf(10) + 1;
|
||||
if (last_newline_index === 0) {
|
||||
// wait for a whole line
|
||||
next_logcat_lines();
|
||||
return;
|
||||
}
|
||||
// split into lines, sort and remove duplicates and blanks
|
||||
const logs = logcatbuffer.slice(0, last_newline_index).toString()
|
||||
.split(/\r\n?|\n/)
|
||||
.sort()
|
||||
.filter((line,idx,arr) => line && line !== arr[idx-1]);
|
||||
|
||||
logcatbuffer = logcatbuffer.slice(last_newline_index);
|
||||
const e = {
|
||||
adbclient: this,
|
||||
logs,
|
||||
};
|
||||
o.onlog(e);
|
||||
}
|
||||
logcatbuffer = Buffer.concat([logcatbuffer, next_data]);
|
||||
const last_newline_index = logcatbuffer.lastIndexOf(10) + 1;
|
||||
if (last_newline_index === 0) {
|
||||
// wait for a whole line
|
||||
next_logcat_lines();
|
||||
return;
|
||||
}
|
||||
// split into lines
|
||||
const logs = logcatbuffer.slice(0, last_newline_index).toString().split(/\r\n?|\n/);
|
||||
logcatbuffer = logcatbuffer.slice(last_newline_index);
|
||||
|
||||
const e = {
|
||||
adbclient: this,
|
||||
logs,
|
||||
};
|
||||
o.onlog(e);
|
||||
next_logcat_lines();
|
||||
}
|
||||
next_logcat_lines();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user