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:
Dave Holoway
2020-04-23 13:28:03 +01:00
committed by GitHub
parent 9aeca6b96b
commit 44d887dd6c
15 changed files with 781 additions and 278 deletions

View File

@@ -73,10 +73,13 @@ class ADBSocket extends AndroidSocket {
/**
* Sends an ADB command, checks the returned status and then reads raw data from the socket
* @param {string} command
* @param {number} timeout_ms
* @param {boolean} [until_closed]
*/
async cmd_and_read_stdout(command) {
async cmd_and_read_stdout(command, timeout_ms, until_closed) {
await this.cmd_and_status(command);
return this.read_stdout();
const buf = await this.read_stdout(timeout_ms, until_closed);
return buf.toString('latin1');
}
/**

View File

@@ -64,8 +64,9 @@ class AndroidSocket extends EventEmitter {
*
* @param {number|'length+data'|undefined} length
* @param {string} [format]
* @param {number} [timeout_ms]
*/
async read_bytes(length, format) {
async read_bytes(length, format, timeout_ms) {
//D(`reading ${length} bytes`);
let actual_length = length;
if (typeof actual_length === 'undefined') {
@@ -95,25 +96,40 @@ class AndroidSocket extends EventEmitter {
return Promise.resolve(data);
}
// wait for the socket to update and then retry the read
await this.wait_for_socket_data();
await this.wait_for_socket_data(timeout_ms);
return this.read_bytes(length, format);
}
wait_for_socket_data() {
/**
*
* @param {number} [timeout_ms]
*/
wait_for_socket_data(timeout_ms) {
return new Promise((resolve, reject) => {
let done = 0;
let done = 0, timer = null;
let onDataChanged = () => {
if ((done += 1) !== 1) return;
this.off('socket-ended', onSocketEnded);
clearTimeout(timer);
resolve();
}
let onSocketEnded = () => {
if ((done += 1) !== 1) return;
this.off('data-changed', onDataChanged);
clearTimeout(timer);
reject(new Error(`${this.which} socket read failed. Socket closed.`));
}
let onTimerExpired = () => {
if ((done += 1) !== 1) return;
this.off('socket-ended', onSocketEnded);
this.off('data-changed', onDataChanged);
reject(new Error(`${this.which} socket read failed. Read timeout.`));
}
this.once('data-changed', onDataChanged);
this.once('socket-ended', onSocketEnded);
if (typeof timeout_ms === 'number' && timeout_ms >= 0) {
timer = setTimeout(onTimerExpired, timeout_ms);
}
});
}
@@ -122,8 +138,26 @@ class AndroidSocket extends EventEmitter {
return this.read_bytes(len.readUInt32LE(0), format);
}
read_stdout(format = 'latin1') {
return this.read_bytes(undefined, format);
/**
*
* @param {number} [timeout_ms]
* @param {boolean} [until_closed]
* @returns {Promise<Buffer>}
*/
async read_stdout(timeout_ms, until_closed) {
let buf = await this.read_bytes(undefined, null, timeout_ms);
if (!until_closed) {
return buf;
}
const parts = [buf];
try {
for (;;) {
buf = await this.read_bytes(undefined, null);
parts.push(buf);
}
} catch {
}
return Buffer.concat(parts);
}
/**
@@ -134,6 +168,7 @@ class AndroidSocket extends EventEmitter {
return new Promise((resolve, reject) => {
this.check_socket_active('write');
try {
// @ts-ignore
const flushed = this.socket.write(bytes, () => {
flushed ? resolve() : this.socket.once('drain', resolve);
});