From 8fe1ee11e49ec4cd2223ef359b94bb2e82e703ca Mon Sep 17 00:00:00 2001 From: Jan S Date: Sun, 20 Mar 2022 18:01:01 +0100 Subject: [PATCH] fix(debugger): resolve IO read problems, proper socket closing (PR #1414) * fix(debugger): several IO read problems fixed * merged latest changes * fixed read loop * Update jadx-gui/src/main/java/jadx/gui/device/protocol/ADB.java Co-authored-by: skylot <118523+skylot@users.noreply.github.com> --- .../gui/device/debugger/DebugController.java | 5 +- .../device/debugger/EventListenerAdapter.java | 76 ++++ .../jadx/gui/device/debugger/RuntimeType.java | 84 ++++ .../gui/device/debugger/SmaliDebugger.java | 298 +------------- .../debugger/SmaliDebuggerException.java | 30 ++ .../jadx/gui/device/debugger/SuspendInfo.java | 97 +++++ .../java/jadx/gui/device/protocol/ADB.java | 386 +++--------------- .../jadx/gui/device/protocol/ADBDevice.java | 252 ++++++++++++ .../gui/device/protocol/ADBDeviceInfo.java | 42 ++ .../java/jadx/gui/ui/dialog/ADBDialog.java | 42 +- .../src/main/java/jadx/gui/utils/IOUtils.java | 47 +++ 11 files changed, 719 insertions(+), 640 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/device/debugger/EventListenerAdapter.java create mode 100644 jadx-gui/src/main/java/jadx/gui/device/debugger/RuntimeType.java create mode 100644 jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebuggerException.java create mode 100644 jadx-gui/src/main/java/jadx/gui/device/debugger/SuspendInfo.java create mode 100644 jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDevice.java create mode 100644 jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDeviceInfo.java create mode 100644 jadx-gui/src/main/java/jadx/gui/utils/IOUtils.java diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java index b4c1ddda..756cf917 100644 --- a/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java +++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java @@ -33,7 +33,6 @@ import jadx.gui.device.debugger.SmaliDebugger.RuntimeField; import jadx.gui.device.debugger.SmaliDebugger.RuntimeRegister; import jadx.gui.device.debugger.SmaliDebugger.RuntimeValue; import jadx.gui.device.debugger.SmaliDebugger.RuntimeVarInfo; -import jadx.gui.device.debugger.SmaliDebugger.SmaliDebuggerException; import jadx.gui.device.debugger.smali.Smali; import jadx.gui.device.debugger.smali.SmaliRegister; import jadx.gui.treemodel.JClass; @@ -42,8 +41,6 @@ import jadx.gui.ui.panel.JDebuggerPanel; import jadx.gui.ui.panel.JDebuggerPanel.IListElement; import jadx.gui.ui.panel.JDebuggerPanel.ValueTreeNode; -import static jadx.gui.device.debugger.SmaliDebugger.RuntimeType; - public final class DebugController implements SmaliDebugger.SuspendListener, IDebugController { private static final Logger LOG = LoggerFactory.getLogger(DebugController.class); @@ -359,7 +356,7 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe } @Override - public void onSuspendEvent(SmaliDebugger.SuspendInfo info) { + public void onSuspendEvent(SuspendInfo info) { if (!isDebugging()) { return; } diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/EventListenerAdapter.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/EventListenerAdapter.java new file mode 100644 index 00000000..aad2ebee --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/EventListenerAdapter.java @@ -0,0 +1,76 @@ +package jadx.gui.device.debugger; + +import io.github.hqktech.JDWP.Event.Composite.BreakpointEvent; +import io.github.hqktech.JDWP.Event.Composite.ClassPrepareEvent; +import io.github.hqktech.JDWP.Event.Composite.ClassUnloadEvent; +import io.github.hqktech.JDWP.Event.Composite.ExceptionEvent; +import io.github.hqktech.JDWP.Event.Composite.FieldAccessEvent; +import io.github.hqktech.JDWP.Event.Composite.FieldModificationEvent; +import io.github.hqktech.JDWP.Event.Composite.MethodEntryEvent; +import io.github.hqktech.JDWP.Event.Composite.MethodExitEvent; +import io.github.hqktech.JDWP.Event.Composite.MethodExitWithReturnValueEvent; +import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnterEvent; +import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnteredEvent; +import io.github.hqktech.JDWP.Event.Composite.MonitorWaitEvent; +import io.github.hqktech.JDWP.Event.Composite.MonitorWaitedEvent; +import io.github.hqktech.JDWP.Event.Composite.SingleStepEvent; +import io.github.hqktech.JDWP.Event.Composite.ThreadDeathEvent; +import io.github.hqktech.JDWP.Event.Composite.ThreadStartEvent; +import io.github.hqktech.JDWP.Event.Composite.VMDeathEvent; +import io.github.hqktech.JDWP.Event.Composite.VMStartEvent; + +abstract class EventListenerAdapter { + void onVMStart(VMStartEvent event) { + } + + void onVMDeath(VMDeathEvent event) { + } + + void onSingleStep(SingleStepEvent event) { + } + + void onBreakpoint(BreakpointEvent event) { + } + + void onMethodEntry(MethodEntryEvent event) { + } + + void onMethodExit(MethodExitEvent event) { + } + + void onMethodExitWithReturnValue(MethodExitWithReturnValueEvent event) { + } + + void onMonitorContendedEnter(MonitorContendedEnterEvent event) { + } + + void onMonitorContendedEntered(MonitorContendedEnteredEvent event) { + } + + void onMonitorWait(MonitorWaitEvent event) { + } + + void onMonitorWaited(MonitorWaitedEvent event) { + } + + void onException(ExceptionEvent event) { + } + + void onThreadStart(ThreadStartEvent event) { + } + + void onThreadDeath(ThreadDeathEvent event) { + } + + void onClassPrepare(ClassPrepareEvent event) { + } + + void onClassUnload(ClassUnloadEvent event) { + } + + void onFieldAccess(FieldAccessEvent event) { + } + + void onFieldModification(FieldModificationEvent event) { + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/RuntimeType.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/RuntimeType.java new file mode 100644 index 00000000..6833085b --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/RuntimeType.java @@ -0,0 +1,84 @@ +package jadx.gui.device.debugger; + +import io.github.hqktech.JDWP; + +public enum RuntimeType { + ARRAY(91, "[]"), + BYTE(66, "byte"), + CHAR(67, "char"), + OBJECT(76, "object"), + FLOAT(70, "float"), + DOUBLE(68, "double"), + INT(73, "int"), + LONG(74, "long"), + SHORT(83, "short"), + VOID(86, "void"), + BOOLEAN(90, "boolean"), + STRING(115, "string"), + THREAD(116, "thread"), + THREAD_GROUP(103, "thread_group"), + CLASS_LOADER(108, "class_loader"), + CLASS_OBJECT(99, "class_object"); + + private final int jdwpTag; + private final String desc; + + RuntimeType(int tag, String desc) { + this.jdwpTag = tag; + this.desc = desc; + } + + public int getTag() { + return jdwpTag; + } + + public String getDesc() { + return this.desc; + } + + /** + * Converts a JDWP.Tag to a {@link RuntimeType} + * + * @param tag + * @return + * @throws SmaliDebuggerException + */ + public static RuntimeType fromJdwpTag(int tag) throws SmaliDebuggerException { + switch (tag) { + case JDWP.Tag.ARRAY: + return RuntimeType.ARRAY; + case JDWP.Tag.BYTE: + return RuntimeType.BYTE; + case JDWP.Tag.CHAR: + return RuntimeType.CHAR; + case JDWP.Tag.OBJECT: + return RuntimeType.OBJECT; + case JDWP.Tag.FLOAT: + return RuntimeType.FLOAT; + case JDWP.Tag.DOUBLE: + return RuntimeType.DOUBLE; + case JDWP.Tag.INT: + return RuntimeType.INT; + case JDWP.Tag.LONG: + return RuntimeType.LONG; + case JDWP.Tag.SHORT: + return RuntimeType.SHORT; + case JDWP.Tag.VOID: + return RuntimeType.VOID; + case JDWP.Tag.BOOLEAN: + return RuntimeType.BOOLEAN; + case JDWP.Tag.STRING: + return RuntimeType.STRING; + case JDWP.Tag.THREAD: + return RuntimeType.THREAD; + case JDWP.Tag.THREAD_GROUP: + return RuntimeType.THREAD_GROUP; + case JDWP.Tag.CLASS_LOADER: + return RuntimeType.CLASS_LOADER; + case JDWP.Tag.CLASS_OBJECT: + return RuntimeType.CLASS_OBJECT; + default: + throw new SmaliDebuggerException("Unexpected value: " + tag); + } + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebugger.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebugger.java index 1674860d..263d1faa 100644 --- a/jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebugger.java +++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebugger.java @@ -75,6 +75,7 @@ import io.reactivex.annotations.NonNull; import jadx.api.plugins.input.data.AccessFlags; import jadx.gui.device.debugger.smali.RegisterInfo; +import jadx.gui.utils.IOUtils; import jadx.gui.utils.ObjectPool; // TODO: Finish error notification, inner errors should be logged let user notice. @@ -83,9 +84,9 @@ public class SmaliDebugger { private static final Logger LOG = LoggerFactory.getLogger(SmaliDebugger.class); private final JDWP jdwp; - private int localTcpPort; - private InputStream inputStream; - private OutputStream outputStream; + private final int localTcpPort; + private final InputStream inputStream; + private final OutputStream outputStream; // All event callbacks will be called in this queue, e.g. class prepare/unload private static final Executor EVENT_LISTENER_QUEUE = Executors.newSingleThreadExecutor(); @@ -118,10 +119,13 @@ public class SmaliDebugger { private static final ICommandResult SKIP_RESULT = res -> { }; - private SmaliDebugger(SuspendListener suspendListener, int localTcpPort, JDWP jdwp) { + private SmaliDebugger(SuspendListener suspendListener, int localTcpPort, JDWP jdwp, InputStream inputStream, + OutputStream outputStream) { this.jdwp = jdwp; this.localTcpPort = localTcpPort; this.suspendListener = suspendListener; + this.inputStream = inputStream; + this.outputStream = outputStream; oneOffEventReq = jdwp.eventRequest().cmdSet().newCountRequest(); oneOffEventReq.count = 1; @@ -135,6 +139,7 @@ public class SmaliDebugger { try { byte[] bytes = JDWP.IDSizes.encode().getBytes(); JDWP.setPacketID(bytes, 1); + LOG.debug("Connecting to ADB {}:{}", host, port); Socket socket = new Socket(host, port); InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); @@ -143,9 +148,7 @@ public class SmaliDebugger { JDWP jdwp = initJDWP(outputStream, inputStream); socket.setSoTimeout(0); // set back to 0 so the decodingLoop won't break for timeout. - SmaliDebugger debugger = new SmaliDebugger(suspendListener, port, jdwp); - debugger.inputStream = inputStream; - debugger.outputStream = outputStream; + SmaliDebugger debugger = new SmaliDebugger(suspendListener, port, jdwp, inputStream, outputStream); debugger.decodingLoop(); debugger.listenClassUnloadEvent(); @@ -262,7 +265,7 @@ public class SmaliDebugger { for (int i = 0; i < values.size(); i++) { ObjectReference.GetValues.GetValuesReplyDataValues value = values.get(i); flds.get(i).setValue(value.value.idOrValue) - .setType(getType(value.value.tag)); + .setType(RuntimeType.fromJdwpTag(value.value.tag)); } } @@ -502,7 +505,7 @@ public class SmaliDebugger { ObjectReference.GetValues.GetValuesReplyData data = jdwp.objectReference().cmdGetValues().decode(res.getBuf(), JDWP.PACKET_HEADER_SIZE); fld.setValue(data.values.get(0).value.idOrValue) - .setType(getType(data.values.get(0).value.tag)); + .setType(RuntimeType.fromJdwpTag(data.values.get(0).value.tag)); } private long createString(String localStr) throws SmaliDebuggerException { @@ -636,7 +639,7 @@ public class SmaliDebugger { byte[] buf; try { outputStream.write(JDWP.encodeHandShakePacket()); - buf = readBytes(inputStream, 14); + buf = IOUtils.readNBytes(inputStream, 14); } catch (Exception e) { throw new SmaliDebuggerException("jdwp handshake failed", e); } @@ -1128,7 +1131,7 @@ public class SmaliDebugger { @Nullable private static Packet readPacket(InputStream inputStream) throws SmaliDebuggerException { try { - byte[] header = readBytes(inputStream, JDWP.PACKET_HEADER_SIZE); + byte[] header = IOUtils.readNBytes(inputStream, JDWP.PACKET_HEADER_SIZE); if (header == null) { // stream ended return null; @@ -1137,7 +1140,7 @@ public class SmaliDebugger { if (bodyLength <= 0) { return Packet.make(header); } - byte[] body = readBytes(inputStream, bodyLength); + byte[] body = IOUtils.readNBytes(inputStream, bodyLength); if (body == null) { throw new SmaliDebuggerException("Stream truncated"); } @@ -1147,21 +1150,6 @@ public class SmaliDebugger { } } - private static byte[] readBytes(InputStream inputStream, int len) throws IOException { - byte[] payload = new byte[len]; - int readSize = 0; - while (true) { - int read = inputStream.read(payload, readSize, len - readSize); - if (read == -1) { - return null; - } - readSize += read; - if (readSize == len) { - return payload; - } - } - } - private static byte[] concatBytes(byte[] buf1, byte[] buf2) { byte[] tempBuf = new byte[buf1.length + buf2.length]; System.arraycopy(buf1, 0, tempBuf, 0, buf1.length); @@ -1183,62 +1171,6 @@ public class SmaliDebugger { void onCommandReply(Packet res) throws SmaliDebuggerException; } - private abstract class EventListenerAdapter { - void onVMStart(VMStartEvent event) { - } - - void onVMDeath(VMDeathEvent event) { - } - - void onSingleStep(SingleStepEvent event) { - } - - void onBreakpoint(BreakpointEvent event) { - } - - void onMethodEntry(MethodEntryEvent event) { - } - - void onMethodExit(MethodExitEvent event) { - } - - void onMethodExitWithReturnValue(MethodExitWithReturnValueEvent event) { - } - - void onMonitorContendedEnter(MonitorContendedEnterEvent event) { - } - - void onMonitorContendedEntered(MonitorContendedEnteredEvent event) { - } - - void onMonitorWait(MonitorWaitEvent event) { - } - - void onMonitorWaited(MonitorWaitedEvent event) { - } - - void onException(ExceptionEvent event) { - } - - void onThreadStart(ThreadStartEvent event) { - } - - void onThreadDeath(ThreadDeathEvent event) { - } - - void onClassPrepare(ClassPrepareEvent event) { - } - - void onClassUnload(ClassUnloadEvent event) { - } - - void onFieldAccess(FieldAccessEvent event) { - } - - void onFieldModification(FieldModificationEvent event) { - } - } - public static class RuntimeField extends RuntimeValue { private final String name; private final String fldType; @@ -1296,46 +1228,7 @@ public class SmaliDebugger { } private RuntimeRegister buildRegister(int num, int tag, ByteBuffer buf) throws SmaliDebuggerException { - return new RuntimeRegister(num, getType(tag), buf); - } - - private RuntimeType getType(int tag) throws SmaliDebuggerException { - switch (tag) { - case JDWP.Tag.ARRAY: - return RuntimeType.ARRAY; - case JDWP.Tag.BYTE: - return RuntimeType.BYTE; - case JDWP.Tag.CHAR: - return RuntimeType.CHAR; - case JDWP.Tag.OBJECT: - return RuntimeType.OBJECT; - case JDWP.Tag.FLOAT: - return RuntimeType.FLOAT; - case JDWP.Tag.DOUBLE: - return RuntimeType.DOUBLE; - case JDWP.Tag.INT: - return RuntimeType.INT; - case JDWP.Tag.LONG: - return RuntimeType.LONG; - case JDWP.Tag.SHORT: - return RuntimeType.SHORT; - case JDWP.Tag.VOID: - return RuntimeType.VOID; - case JDWP.Tag.BOOLEAN: - return RuntimeType.BOOLEAN; - case JDWP.Tag.STRING: - return RuntimeType.STRING; - case JDWP.Tag.THREAD: - return RuntimeType.THREAD; - case JDWP.Tag.THREAD_GROUP: - return RuntimeType.THREAD_GROUP; - case JDWP.Tag.CLASS_LOADER: - return RuntimeType.CLASS_LOADER; - case JDWP.Tag.CLASS_OBJECT: - return RuntimeType.CLASS_OBJECT; - default: - throw new SmaliDebuggerException("Unexpected value: " + tag); - } + return new RuntimeRegister(num, RuntimeType.fromJdwpTag(tag), buf); } public static class RuntimeValue { @@ -1428,41 +1321,6 @@ public class SmaliDebugger { } } - public enum RuntimeType { - ARRAY(91, "[]"), - BYTE(66, "byte"), - CHAR(67, "char"), - OBJECT(76, "object"), - FLOAT(70, "float"), - DOUBLE(68, "double"), - INT(73, "int"), - LONG(74, "long"), - SHORT(83, "short"), - VOID(86, "void"), - BOOLEAN(90, "boolean"), - STRING(115, "string"), - THREAD(116, "thread"), - THREAD_GROUP(103, "thread_group"), - CLASS_LOADER(108, "class_loader"), - CLASS_OBJECT(99, "class_object"); - - private final int jdwpTag; - private final String desc; - - RuntimeType(int tag, String desc) { - this.jdwpTag = tag; - this.desc = desc; - } - - private int getTag() { - return jdwpTag; - } - - public String getDesc() { - return this.desc; - } - } - public static class Frame { private final long id; private final long clsID; @@ -1503,35 +1361,6 @@ public class SmaliDebugger { void onUnloaded(String cls); } - public static class SmaliDebuggerException extends Exception { - private final int errCode; - private static final long serialVersionUID = -1111111202102191403L; - - public SmaliDebuggerException(Exception e) { - super(e); - errCode = -1; - } - - public SmaliDebuggerException(String msg) { - super(msg); - this.errCode = -1; - } - - public SmaliDebuggerException(String message, Throwable cause) { - super(message, cause); - this.errCode = -1; - } - - public SmaliDebuggerException(String msg, int errCode) { - super(msg); - this.errCode = errCode; - } - - public int getErrCode() { - return errCode; - } - } - /** * Listener for breakpoint, watch, step, etc. */ @@ -1543,99 +1372,4 @@ public class SmaliDebugger { void onSuspendEvent(SuspendInfo current); } - public static class SuspendInfo { - private boolean terminated; - private boolean newRound; - private final InfoSetter updater = new InfoSetter(); - - public long getThreadID() { - return updater.thread; - } - - public long getClassID() { - return updater.clazz; - } - - public long getMethodID() { - return updater.method; - } - - public long getOffset() { - return updater.offset; - } - - private InfoSetter update() { - updater.changed = false; - updater.nextRound(newRound); - this.newRound = false; - return updater; - } - - // called by decodingLoop, to tell the updater even though the values are the same, - // they are decoded from another packet, they should be treated as new. - private void nextRound() { - newRound = true; - } - - // according to JDWP document it's legal to fire two or more events on a same location, - // e.g. one for single step and the other for breakpoint, so when this happened we only - // want one of them. - private boolean isAnythingChanged() { - return updater.changed; - } - - public boolean isTerminated() { - return terminated; - } - - private void setTerminated() { - terminated = true; - } - - private static class InfoSetter { - private long thread; - private long clazz; - private long method; - private long offset; // code offset; - private boolean changed; - - private void nextRound(boolean newRound) { - if (!changed) { - changed = newRound; - } - } - - private InfoSetter updateThread(long thread) { - if (!changed) { - changed = this.thread != thread; - } - this.thread = thread; - return this; - } - - private InfoSetter updateClass(long clazz) { - if (!changed) { - changed = this.clazz != clazz; - } - this.clazz = clazz; - return this; - } - - private InfoSetter updateMethod(long method) { - if (!changed) { - changed = this.method != method; - } - this.method = method; - return this; - } - - private InfoSetter updateOffset(long offset) { - if (!changed) { - changed = this.offset != offset; - } - this.offset = offset; - return this; - } - } - } } diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebuggerException.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebuggerException.java new file mode 100644 index 00000000..87b0594d --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebuggerException.java @@ -0,0 +1,30 @@ +package jadx.gui.device.debugger; + +public class SmaliDebuggerException extends Exception { + private final int errCode; + private static final long serialVersionUID = -1111111202102191403L; + + public SmaliDebuggerException(Exception e) { + super(e); + errCode = -1; + } + + public SmaliDebuggerException(String msg) { + super(msg); + this.errCode = -1; + } + + public SmaliDebuggerException(String msg, Exception e) { + super(msg, e); + errCode = -1; + } + + public SmaliDebuggerException(String msg, int errCode) { + super(msg); + this.errCode = errCode; + } + + public int getErrCode() { + return errCode; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/SuspendInfo.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/SuspendInfo.java new file mode 100644 index 00000000..58e79ebe --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/SuspendInfo.java @@ -0,0 +1,97 @@ +package jadx.gui.device.debugger; + +public class SuspendInfo { + private boolean terminated; + private boolean newRound; + private final InfoSetter updater = new InfoSetter(); + + public long getThreadID() { + return updater.thread; + } + + public long getClassID() { + return updater.clazz; + } + + public long getMethodID() { + return updater.method; + } + + public long getOffset() { + return updater.offset; + } + + InfoSetter update() { + updater.changed = false; + updater.nextRound(newRound); + this.newRound = false; + return updater; + } + + // called by decodingLoop, to tell the updater even though the values are the same, + // they are decoded from another packet, they should be treated as new. + void nextRound() { + newRound = true; + } + + // according to JDWP document it's legal to fire two or more events on a same location, + // e.g. one for single step and the other for breakpoint, so when this happened we only + // want one of them. + boolean isAnythingChanged() { + return updater.changed; + } + + public boolean isTerminated() { + return terminated; + } + + void setTerminated() { + terminated = true; + } + + static class InfoSetter { + private long thread; + private long clazz; + private long method; + private long offset; // code offset; + private boolean changed; + + void nextRound(boolean newRound) { + if (!changed) { + changed = newRound; + } + } + + InfoSetter updateThread(long thread) { + if (!changed) { + changed = this.thread != thread; + } + this.thread = thread; + return this; + } + + InfoSetter updateClass(long clazz) { + if (!changed) { + changed = this.clazz != clazz; + } + this.clazz = clazz; + return this; + } + + InfoSetter updateMethod(long method) { + if (!changed) { + changed = this.method != method; + } + this.method = method; + return this; + } + + InfoSetter updateOffset(long offset) { + if (!changed) { + changed = this.offset != offset; + } + this.offset = offset; + return this; + } + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/device/protocol/ADB.java b/jadx-gui/src/main/java/jadx/gui/device/protocol/ADB.java index d08d3c81..1c20428c 100644 --- a/jadx-gui/src/main/java/jadx/gui/device/protocol/ADB.java +++ b/jadx-gui/src/main/java/jadx/gui/device/protocol/ADB.java @@ -1,5 +1,6 @@ package jadx.gui.device.protocol; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -7,19 +8,17 @@ import java.net.Socket; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.reactivex.annotations.NonNull; - -import jadx.core.utils.StringUtils; +import jadx.gui.utils.IOUtils; public class ADB { private static final Logger LOG = LoggerFactory.getLogger(ADB.class); @@ -28,13 +27,11 @@ public class ADB { private static final String DEFAULT_ADDR = "localhost"; private static final String CMD_FEATURES = "000dhost:features"; - private static final String CMD_TRACK_JDWP = "000atrack-jdwp"; private static final String CMD_TRACK_DEVICES = "0014host:track-devices-l"; private static final byte[] OKAY = "OKAY".getBytes(); - private static boolean isOkay(InputStream stream) throws IOException { - byte[] buf = new byte[4]; - stream.read(buf, 0, 4); + static boolean isOkay(InputStream stream) throws IOException { + byte[] buf = IOUtils.readNBytes(stream, 4); return Arrays.equals(buf, OKAY); } @@ -44,9 +41,9 @@ public class ADB { public static byte[] exec(String cmd) throws IOException { byte[] res; - Socket socket = connect(); - res = exec(cmd, socket.getOutputStream(), socket.getInputStream()); - socket.close(); + try (Socket socket = connect()) { + res = exec(cmd, socket.getOutputStream(), socket.getInputStream()); + } return res; } @@ -58,7 +55,7 @@ public class ADB { return new Socket(host, port); } - private static boolean execCommandAsync(OutputStream outputStream, + static boolean execCommandAsync(OutputStream outputStream, InputStream inputStream, String cmd) throws IOException { outputStream.write(cmd.getBytes()); return isOkay(inputStream); @@ -73,29 +70,21 @@ public class ADB { return null; } - private static byte[] readServiceProtocol(InputStream stream) { - byte[] bytes = null; - byte[] buf = new byte[4]; + static byte[] readServiceProtocol(InputStream stream) { try { - int len = stream.read(buf, 0, 4); - if (len == 4) { - len = unhex(buf); - if (len == 0) { - return new byte[0]; - } - if (len != -1) { - buf = new byte[len]; - if (stream.read(buf, 0, len) == len) { - bytes = buf; - } - } + byte[] buf = IOUtils.readNBytes(stream, 4); + int len = unhex(buf); + if (len == 0) { + return new byte[0]; } - } catch (IOException ignore) { + return IOUtils.readNBytes(stream, len); + } catch (IOException e) { + LOG.error("Failed to read readServiceProtocol: {}", e.toString()); + return null; } - return bytes; } - private static boolean setSerial(String serial, OutputStream outputStream, InputStream inputStream) throws IOException { + static boolean setSerial(String serial, OutputStream outputStream, InputStream inputStream) throws IOException { String setSerialCmd = String.format("host:tport:serial:%s", serial); setSerialCmd = String.format("%04x%s", setSerialCmd.length(), setSerialCmd); outputStream.write(setSerialCmd.getBytes()); @@ -107,9 +96,7 @@ public class ADB { return ok; } - private static byte[] execShellCommandRaw(String cmd, - OutputStream outputStream, InputStream inputStream) throws IOException { - + private static byte[] execShellCommandRaw(String cmd, OutputStream outputStream, InputStream inputStream) throws IOException { cmd = String.format("shell,v2,TERM=xterm-256color,raw:%s", cmd); cmd = String.format("%04x%s", cmd.length(), cmd); outputStream.write(cmd.getBytes()); @@ -119,7 +106,7 @@ public class ADB { return null; } - private static byte[] execShellCommandRaw(String serial, String cmd, + static byte[] execShellCommandRaw(String serial, String cmd, OutputStream outputStream, InputStream inputStream) throws IOException { if (setSerial(serial, outputStream, inputStream)) { return execShellCommandRaw(cmd, outputStream, inputStream); @@ -148,17 +135,19 @@ public class ADB { proc.destroyForcibly(); return false; } - InputStream is = proc.getInputStream(); - int size = is.available(); - byte[] bytes = new byte[size]; - is.read(bytes, 0, size); - return new String(bytes).contains(tcpPort); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (InputStream in = proc.getInputStream()) { + int read; + byte[] buf = new byte[1024]; + while ((read = in.read(buf)) >= 0) { + out.write(buf, 0, read); + } + } + return new String(out.toByteArray()).contains(tcpPort); } public static boolean isServerRunning(String host, int port) { - try { - Socket sock = new Socket(host, port); - sock.close(); + try (Socket sock = new Socket(host, port)) { return true; } catch (Exception e) { return false; @@ -184,10 +173,10 @@ public class ADB { if (listener != null) { String payload = new String(res); String[] deviceLines = payload.split("\n"); - List deviceInfoList = new ArrayList<>(deviceLines.length); + List deviceInfoList = new ArrayList<>(deviceLines.length); for (String deviceLine : deviceLines) { if (!deviceLine.trim().isEmpty()) { - deviceInfoList.add(DeviceInfo.make(deviceLine, host, port)); + deviceInfoList.add(ADBDeviceInfo.make(deviceLine, host, port)); } } listener.onDeviceStatusChange(deviceInfoList); @@ -213,10 +202,7 @@ public class ADB { byte[] bytes = readServiceProtocol(inputStream); if (bytes != null) { String[] forwards = new String(bytes).split("\n"); - List forwardList = new ArrayList<>(forwards.length); - for (String forward : forwards) { - forwardList.add(forward.trim()); - } + List forwardList = Arrays.stream(forwards).map(s -> s.trim()).collect(Collectors.toList()); socket.close(); return forwardList; } @@ -300,287 +286,17 @@ public class ADB { } public interface JDWPProcessListener { - void jdwpProcessOccurred(Device device, Set id); + void jdwpProcessOccurred(ADBDevice device, Set id); - void jdwpListenerClosed(Device device); + void jdwpListenerClosed(ADBDevice device); } public interface DeviceStateListener { - void onDeviceStatusChange(List deviceInfoList); + void onDeviceStatusChange(List deviceInfoList); void adbDisconnected(); } - public static class Device { - DeviceInfo info; - String androidReleaseVer; - volatile Socket jdwpListenerSock; - - public Device(DeviceInfo info) { - this.info = info; - } - - public DeviceInfo getDeviceInfo() { - return info; - } - - public boolean updateDeviceInfo(DeviceInfo info) { - boolean matched = this.info.serial.equals(info.serial); - if (matched) { - this.info = info; - } - return matched; - } - - public String getSerial() { - return info.serial; - } - - public boolean removeForward(String localPort) throws IOException { - return ADB.removeForward(info.adbHost, info.adbPort, info.serial, localPort); - } - - public ForwardResult forwardJDWP(String localPort, String jdwpPid) throws IOException { - Socket socket = connect(info.adbHost, info.adbPort); - String cmd = String.format("host:forward:tcp:%s;jdwp:%s", localPort, jdwpPid); - cmd = String.format("%04x%s", cmd.length(), cmd); - InputStream inputStream = socket.getInputStream(); - OutputStream outputStream = socket.getOutputStream(); - ForwardResult rst; - if (setSerial(info.serial, outputStream, inputStream)) { - outputStream.write(cmd.getBytes()); - if (!isOkay(inputStream)) { - rst = new ForwardResult(1, readServiceProtocol(inputStream)); - } else if (!isOkay(inputStream)) { - rst = new ForwardResult(2, readServiceProtocol(inputStream)); - } else { - rst = new ForwardResult(0, null); - } - } else { - rst = new ForwardResult(1, "Unknown error.".getBytes()); - } - socket.close(); - return rst; - } - - public static class ForwardResult { - /** - * 0 for success, 1 for failed at binding to local tcp, 2 for failed at remote. - */ - public int state; - public String desc; - - public ForwardResult(int state, byte[] desc) { - if (desc != null) { - this.desc = new String(desc); - } else { - this.desc = ""; - } - this.state = state; - } - } - - /** - * @return pid otherwise -1 - */ - public int launchApp(String fullAppName) throws IOException, InterruptedException { - Socket socket = connect(info.adbHost, info.adbPort); - String cmd = "am start -D -n " + fullAppName; - byte[] res = execShellCommandRaw(info.serial, cmd, socket.getOutputStream(), socket.getInputStream()); - socket.close(); - String rst = new String(res).trim(); - if (rst.startsWith("Starting: Intent {") && rst.endsWith(fullAppName + " }")) { - Thread.sleep(40); - String pkg = fullAppName.split("/")[0]; - for (Process process : getProcessByPkg(pkg)) { - return Integer.parseInt(process.pid); - } - } - return -1; - } - - public String getAndroidReleaseVersion() { - if (!StringUtils.isEmpty(androidReleaseVer)) { - return androidReleaseVer; - } - try { - List list = getProp("ro.build.version.release"); - if (list.size() != 0) { - androidReleaseVer = list.get(0); - } - } catch (Exception e) { - LOG.error("Failed to get android release version", e); - androidReleaseVer = ""; - } - return androidReleaseVer; - } - - public List getProp(String entry) throws IOException { - Socket socket = connect(info.adbHost, info.adbPort); - List props = Collections.emptyList(); - String cmd = "getprop"; - if (!StringUtils.isEmpty(entry)) { - cmd += " " + entry; - } - byte[] payload = execShellCommandRaw(info.serial, cmd, - socket.getOutputStream(), socket.getInputStream()); - if (payload != null) { - props = new ArrayList<>(); - String[] lines = new String(payload).split("\n"); - for (String line : lines) { - line = line.trim(); - if (!line.isEmpty()) { - props.add(line.trim()); - } - } - } - socket.close(); - return props; - } - - public List getProcessByPkg(String pkg) throws IOException { - return getProcessList("ps | grep " + pkg, 0); - } - - @NonNull - public List getProcessList() throws IOException { - return getProcessList("ps", 1); - } - - private List getProcessList(String cmd, int index) throws IOException { - Socket socket = connect(info.adbHost, info.adbPort); - List procs = Collections.emptyList(); - byte[] payload = execShellCommandRaw(info.serial, cmd, - socket.getOutputStream(), socket.getInputStream()); - if (payload != null) { - String ps = new String(payload); - String[] psLines = ps.split("\n"); - for (int i = index; i < psLines.length; i++) { - Process proc = Process.make(psLines[i]); - if (proc != null) { - if (procs.isEmpty()) { - procs = new ArrayList<>(); - } - procs.add(proc); - } - } - } - socket.close(); - return procs; - } - - public boolean listenForJDWP(JDWPProcessListener listener) throws IOException { - if (this.jdwpListenerSock != null) { - return false; - } - jdwpListenerSock = connect(this.info.adbHost, this.info.adbPort); - InputStream inputStream = jdwpListenerSock.getInputStream(); - OutputStream outputStream = jdwpListenerSock.getOutputStream(); - if (setSerial(info.serial, outputStream, inputStream) - && execCommandAsync(outputStream, inputStream, CMD_TRACK_JDWP)) { - Executors.newFixedThreadPool(1).execute(() -> { - for (;;) { - byte[] res = readServiceProtocol(inputStream); - if (res != null) { - if (listener != null) { - String payload = new String(res); - String[] ids = payload.split("\n"); - Set idList = new HashSet<>(ids.length); - for (String id : ids) { - if (!id.trim().isEmpty()) { - idList.add(id); - } - } - listener.jdwpProcessOccurred(this, idList); - } - } else { // socket disconnected - break; - } - } - if (listener != null) { - this.jdwpListenerSock = null; - listener.jdwpListenerClosed(this); - } - }); - } else { - jdwpListenerSock.close(); - jdwpListenerSock = null; - return false; - } - return true; - } - - public void stopListenForJDWP() { - if (jdwpListenerSock != null) { - try { - jdwpListenerSock.close(); - } catch (Exception e) { - LOG.error("JDWP socket close failed", e); - } - } - this.jdwpListenerSock = null; - } - - @Override - public int hashCode() { - return info.serial.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Device) { - return ((Device) obj).getDeviceInfo().serial.equals(info.serial); - } - return false; - } - - @Override - public String toString() { - return info.allInfo; - } - } - - public static class DeviceInfo { - public String adbHost; - public int adbPort; - public String serial; - public String state; - public String model; - public String allInfo; - - public boolean isOnline() { - return state.equals("device"); - } - - @Override - public String toString() { - return allInfo; - } - - static DeviceInfo make(String info, String host, int port) { - DeviceInfo deviceInfo = new DeviceInfo(); - String[] infoFields = info.trim().split("\\s+"); - deviceInfo.allInfo = String.join(" ", infoFields); - if (infoFields.length > 2) { - deviceInfo.serial = infoFields[0]; - deviceInfo.state = infoFields[1]; - } - int pos = info.indexOf("model:"); - if (pos != -1) { - int spacePos = info.indexOf(" ", pos); - if (spacePos != -1) { - deviceInfo.model = info.substring(pos + "model:".length(), spacePos); - } - } - if (deviceInfo.model == null || deviceInfo.model.equals("")) { - deviceInfo.model = deviceInfo.serial; - } - deviceInfo.adbHost = host; - deviceInfo.adbPort = port; - return deviceInfo; - } - } - public static class Process { public String user; public String pid; @@ -619,23 +335,23 @@ public class ADB { public static byte[] readStdout(InputStream inputStream) throws IOException { byte[] header = new byte[5]; - byte[] payload = new byte[0]; - byte[] tempBuf = new byte[0]; + ByteArrayOutputStream payload = new ByteArrayOutputStream(); + byte[] tempBuf = new byte[1024]; for (boolean exit = false; !exit;) { - if (inputStream.read(header, 0, 5) == 5) { - exit = header[0] == ID_EXIT; - int payloadSize = readInt(header, 1); - if (tempBuf.length < payloadSize) { - tempBuf = new byte[payloadSize]; - } - int readSize = inputStream.read(tempBuf, 0, payloadSize); - if (readSize != payloadSize) { - return null; // we don't want corrupted data. - } - payload = appendBytes(payload, tempBuf, readSize); + IOUtils.read(inputStream, header); + exit = header[0] == ID_EXIT; + int payloadSize = readInt(header, 1); + if (tempBuf.length < payloadSize) { + tempBuf = new byte[payloadSize]; + } + int readSize = IOUtils.read(inputStream, tempBuf, 0, payloadSize); + if (readSize != payloadSize) { + LOG.error("Failed to read ShellProtocol data"); + return null; // we don't want corrupted data. } + payload.write(tempBuf, 0, readSize); } - return payload; + return payload.toByteArray(); } } } diff --git a/jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDevice.java b/jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDevice.java new file mode 100644 index 00000000..2c06a575 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDevice.java @@ -0,0 +1,252 @@ +package jadx.gui.device.protocol; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Executors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.reactivex.annotations.NonNull; + +import jadx.core.utils.StringUtils; +import jadx.gui.device.protocol.ADB.JDWPProcessListener; +import jadx.gui.device.protocol.ADB.Process; + +public class ADBDevice { + private static final Logger LOG = LoggerFactory.getLogger(ADBDevice.class); + + private static final String CMD_TRACK_JDWP = "000atrack-jdwp"; + + ADBDeviceInfo info; + String androidReleaseVer; + volatile Socket jdwpListenerSock; + + public ADBDevice(ADBDeviceInfo info) { + this.info = info; + } + + public ADBDeviceInfo getDeviceInfo() { + return info; + } + + public boolean updateDeviceInfo(ADBDeviceInfo info) { + boolean matched = this.info.serial.equals(info.serial); + if (matched) { + this.info = info; + } + return matched; + } + + public String getSerial() { + return info.serial; + } + + public boolean removeForward(String localPort) throws IOException { + return ADB.removeForward(info.adbHost, info.adbPort, info.serial, localPort); + } + + public ForwardResult forwardJDWP(String localPort, String jdwpPid) throws IOException { + try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) { + String cmd = String.format("host:forward:tcp:%s;jdwp:%s", localPort, jdwpPid); + cmd = String.format("%04x%s", cmd.length(), cmd); + InputStream inputStream = socket.getInputStream(); + OutputStream outputStream = socket.getOutputStream(); + ForwardResult rst; + if (ADB.setSerial(info.serial, outputStream, inputStream)) { + outputStream.write(cmd.getBytes()); + if (!ADB.isOkay(inputStream)) { + rst = new ForwardResult(1, ADB.readServiceProtocol(inputStream)); + } else if (!ADB.isOkay(inputStream)) { + rst = new ForwardResult(2, ADB.readServiceProtocol(inputStream)); + } else { + rst = new ForwardResult(0, null); + } + } else { + rst = new ForwardResult(1, "Unknown error.".getBytes()); + } + return rst; + } + } + + public static class ForwardResult { + /** + * 0 for success, 1 for failed at binding to local tcp, 2 for failed at remote. + */ + public int state; + public String desc; + + public ForwardResult(int state, byte[] desc) { + if (desc != null) { + this.desc = new String(desc); + } else { + this.desc = ""; + } + this.state = state; + } + } + + /** + * @return pid otherwise -1 + */ + public int launchApp(String fullAppName) throws IOException, InterruptedException { + byte[] res; + try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) { + String cmd = "am start -D -n " + fullAppName; + res = ADB.execShellCommandRaw(info.serial, cmd, socket.getOutputStream(), socket.getInputStream()); + } + String rst = new String(res).trim(); + if (rst.startsWith("Starting: Intent {") && rst.endsWith(fullAppName + " }")) { + Thread.sleep(40); + String pkg = fullAppName.split("/")[0]; + for (Process process : getProcessByPkg(pkg)) { + return Integer.parseInt(process.pid); + } + } + return -1; + } + + public String getAndroidReleaseVersion() { + if (!StringUtils.isEmpty(androidReleaseVer)) { + return androidReleaseVer; + } + try { + List list = getProp("ro.build.version.release"); + if (list.size() != 0) { + androidReleaseVer = list.get(0); + } + } catch (Exception e) { + LOG.error("Failed to get android release version", e); + androidReleaseVer = ""; + } + return androidReleaseVer; + } + + public List getProp(String entry) throws IOException { + try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) { + List props = Collections.emptyList(); + String cmd = "getprop"; + if (!StringUtils.isEmpty(entry)) { + cmd += " " + entry; + } + byte[] payload = ADB.execShellCommandRaw(info.serial, cmd, + socket.getOutputStream(), socket.getInputStream()); + if (payload != null) { + props = new ArrayList<>(); + String[] lines = new String(payload).split("\n"); + for (String line : lines) { + line = line.trim(); + if (!line.isEmpty()) { + props.add(line.trim()); + } + } + } + return props; + } + } + + public List getProcessByPkg(String pkg) throws IOException { + return getProcessList("ps | grep " + pkg, 0); + } + + @NonNull + public List getProcessList() throws IOException { + return getProcessList("ps", 1); + } + + private List getProcessList(String cmd, int index) throws IOException { + try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) { + List procs = new ArrayList<>(); + byte[] payload = ADB.execShellCommandRaw(info.serial, cmd, + socket.getOutputStream(), socket.getInputStream()); + if (payload != null) { + String ps = new String(payload); + String[] psLines = ps.split("\n"); + for (int i = index; i < psLines.length; i++) { + Process proc = Process.make(psLines[i]); + if (proc != null) { + procs.add(proc); + } + } + } + return procs; + } + } + + public boolean listenForJDWP(JDWPProcessListener listener) throws IOException { + if (this.jdwpListenerSock != null) { + return false; + } + jdwpListenerSock = ADB.connect(this.info.adbHost, this.info.adbPort); + InputStream inputStream = jdwpListenerSock.getInputStream(); + OutputStream outputStream = jdwpListenerSock.getOutputStream(); + if (ADB.setSerial(info.serial, outputStream, inputStream) + && ADB.execCommandAsync(outputStream, inputStream, CMD_TRACK_JDWP)) { + Executors.newFixedThreadPool(1).execute(() -> { + for (;;) { + byte[] res = ADB.readServiceProtocol(inputStream); + if (res != null) { + if (listener != null) { + String payload = new String(res); + String[] ids = payload.split("\n"); + Set idList = new HashSet<>(ids.length); + for (String id : ids) { + if (!id.trim().isEmpty()) { + idList.add(id); + } + } + listener.jdwpProcessOccurred(this, idList); + } + } else { // socket disconnected + break; + } + } + if (listener != null) { + this.jdwpListenerSock = null; + listener.jdwpListenerClosed(this); + } + }); + } else { + jdwpListenerSock.close(); + jdwpListenerSock = null; + return false; + } + return true; + } + + public void stopListenForJDWP() { + if (jdwpListenerSock != null) { + try { + jdwpListenerSock.close(); + } catch (Exception e) { + LOG.error("JDWP socket close failed", e); + } + } + this.jdwpListenerSock = null; + } + + @Override + public int hashCode() { + return info.serial.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ADBDevice) { + return ((ADBDevice) obj).getDeviceInfo().serial.equals(info.serial); + } + return false; + } + + @Override + public String toString() { + return info.allInfo; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDeviceInfo.java b/jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDeviceInfo.java new file mode 100644 index 00000000..b61ae143 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDeviceInfo.java @@ -0,0 +1,42 @@ +package jadx.gui.device.protocol; + +public class ADBDeviceInfo { + public String adbHost; + public int adbPort; + public String serial; + public String state; + public String model; + public String allInfo; + + public boolean isOnline() { + return state.equals("device"); + } + + @Override + public String toString() { + return allInfo; + } + + static ADBDeviceInfo make(String info, String host, int port) { + ADBDeviceInfo deviceInfo = new ADBDeviceInfo(); + String[] infoFields = info.trim().split("\\s+"); + deviceInfo.allInfo = String.join(" ", infoFields); + if (infoFields.length > 2) { + deviceInfo.serial = infoFields[0]; + deviceInfo.state = infoFields[1]; + } + int pos = info.indexOf("model:"); + if (pos != -1) { + int spacePos = info.indexOf(" ", pos); + if (spacePos != -1) { + deviceInfo.model = info.substring(pos + "model:".length(), spacePos); + } + } + if (deviceInfo.model == null || deviceInfo.model.equals("")) { + deviceInfo.model = deviceInfo.serial; + } + deviceInfo.adbHost = host; + deviceInfo.adbPort = port; + return deviceInfo; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java index 2afd6f5a..ee38e449 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java @@ -42,6 +42,8 @@ import jadx.core.utils.StringUtils; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.gui.device.debugger.DbgUtils; import jadx.gui.device.protocol.ADB; +import jadx.gui.device.protocol.ADBDevice; +import jadx.gui.device.protocol.ADBDeviceInfo; import jadx.gui.treemodel.JClass; import jadx.gui.ui.MainWindow; import jadx.gui.ui.panel.IDebugController; @@ -49,7 +51,7 @@ import jadx.gui.utils.NLS; import jadx.gui.utils.SystemInfo; import jadx.gui.utils.UiUtils; -import static jadx.gui.device.protocol.ADB.Device.ForwardResult; +import static jadx.gui.device.protocol.ADBDevice.ForwardResult; public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.JDWPProcessListener { private static final Logger LOG = LoggerFactory.getLogger(ADBDialog.class); @@ -273,9 +275,9 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J } @Override - public void onDeviceStatusChange(List deviceInfoList) { + public void onDeviceStatusChange(List deviceInfoList) { List nodes = new ArrayList<>(deviceInfoList.size()); - info_loop: for (ADB.DeviceInfo info : deviceInfoList) { + info_loop: for (ADBDeviceInfo info : deviceInfoList) { for (DeviceNode deviceNode : deviceNodes) { if (deviceNode.device.updateDeviceInfo(info)) { deviceNode.refresh(); @@ -283,7 +285,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J continue info_loop; } } - ADB.Device device = new ADB.Device(info); + ADBDevice device = new ADBDevice(info); device.getAndroidReleaseVersion(); nodes.add(new DeviceNode(device)); listenJDWP(device); @@ -402,7 +404,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J return null; } - private DeviceNode getDeviceNode(ADB.Device device) { + private DeviceNode getDeviceNode(ADBDevice device) { for (DeviceNode deviceNode : deviceNodes) { if (deviceNode.device.equals(device)) { return deviceNode; @@ -411,7 +413,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J throw new JadxRuntimeException("Unexpected device: " + device); } - private void listenJDWP(ADB.Device device) { + private void listenJDWP(ADBDevice device) { try { device.listenForJDWP(this); } catch (Exception e) { @@ -441,7 +443,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J } @Override - public void jdwpProcessOccurred(ADB.Device device, Set id) { + public void jdwpProcessOccurred(ADBDevice device, Set id) { List procs; try { Thread.sleep(40); /* @@ -514,7 +516,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J return; } String fullName = pkg + "/" + cls.getCls().getClassNode().getClassInfo().getFullName(); - ADB.Device device = deviceNodes.get(0).device; // TODO: if multiple devices presented should let user select the one they desire. + ADBDevice device = deviceNodes.get(0).device; // TODO: if multiple devices presented should let user select the one they desire. if (device != null) { try { device.launchApp(fullName); @@ -547,7 +549,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J } @Override - public void jdwpListenerClosed(ADB.Device device) { + public void jdwpListenerClosed(ADBDevice device) { } @@ -556,27 +558,29 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J } private static class DeviceNode { - ADB.Device device; + ADBDevice device; DeviceTreeNode tNode; - DeviceNode(ADB.Device adbDevice) { + DeviceNode(ADBDevice adbDevice) { this.device = adbDevice; tNode = new DeviceTreeNode(); refresh(); } void refresh() { - ADB.DeviceInfo info = device.getDeviceInfo(); + ADBDeviceInfo info = device.getDeviceInfo(); String text = info.model; - if (!text.equals(info.serial)) { - text += String.format(" [serial: %s]", info.serial); + if (text != null) { + if (!text.equals(info.serial)) { + text += String.format(" [serial: %s]", info.serial); + } + text += String.format(" [state: %s]", info.isOnline() ? "online" : "offline"); + tNode.setUserObject(text); } - text += String.format(" [state: %s]", info.isOnline() ? "online" : "offline"); - tNode.setUserObject(text); } } - private boolean setupArgs(ADB.Device device, String pid, String name) { + private boolean setupArgs(ADBDevice device, String pid, String name) { String ver = device.getAndroidReleaseVersion(); if (StringUtils.isEmpty(ver)) { if (JOptionPane.showConfirmDialog(mainWindow, @@ -605,12 +609,12 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J private String ver; private String pid; private String name; - private ADB.Device device; + private ADBDevice device; private int forwardTcpPort = FORWARD_TCP_PORT; private String expectPkg = ""; private boolean autoAttachPkg = false; - private void set(ADB.Device device, String ver, String pid, String name) { + private void set(ADBDevice device, String ver, String pid, String name) { this.ver = ver; this.pid = pid; this.name = name; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/IOUtils.java b/jadx-gui/src/main/java/jadx/gui/utils/IOUtils.java new file mode 100644 index 00000000..cfe783ef --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/IOUtils.java @@ -0,0 +1,47 @@ +package jadx.gui.utils; + +import java.io.IOException; +import java.io.InputStream; + +public class IOUtils { + + /** + * This method can be deleted once Jadx is Java11+ + * + * @param inputStream + * @param len + * @return + * @throws IOException + */ + public static byte[] readNBytes(InputStream inputStream, int len) throws IOException { + byte[] payload = new byte[len]; + int readSize = 0; + while (true) { + int read = inputStream.read(payload, readSize, len - readSize); + if (read == -1) { + return null; + } + readSize += read; + if (readSize == len) { + return payload; + } + } + } + + public static int read(InputStream inputStream, byte[] buf) throws IOException { + return read(inputStream, buf, 0, buf.length); + } + + public static int read(InputStream inputStream, byte[] buf, int off, int len) throws IOException { + int remainingBytes = len; + while (remainingBytes > 0) { + int start = len - remainingBytes; + int bytesRead = inputStream.read(buf, off + start, remainingBytes); + if (bytesRead == -1) { + break; + } + remainingBytes -= bytesRead; + } + return len - remainingBytes; + } +} -- GitLab