未验证 提交 8fe1ee11 编写于 作者: J Jan S 提交者: GitHub

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: Nskylot <118523+skylot@users.noreply.github.com>
上级 d2bef108
......@@ -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;
}
......
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) {
}
}
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 <code>JDWP.Tag</code> 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);
}
}
}
......@@ -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;
}
}
}
}
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;
}
}
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;
}
}
}
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<DeviceInfo> deviceInfoList = new ArrayList<>(deviceLines.length);
List<ADBDeviceInfo> 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<String> forwardList = new ArrayList<>(forwards.length);
for (String forward : forwards) {
forwardList.add(forward.trim());
}
List<String> 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<String> id);
void jdwpProcessOccurred(ADBDevice device, Set<String> id);
void jdwpListenerClosed(Device device);
void jdwpListenerClosed(ADBDevice device);
}
public interface DeviceStateListener {
void onDeviceStatusChange(List<DeviceInfo> deviceInfoList);
void onDeviceStatusChange(List<ADBDeviceInfo> 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<String> 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<String> getProp(String entry) throws IOException {
Socket socket = connect(info.adbHost, info.adbPort);
List<String> 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<Process> getProcessByPkg(String pkg) throws IOException {
return getProcessList("ps | grep " + pkg, 0);
}
@NonNull
public List<Process> getProcessList() throws IOException {
return getProcessList("ps", 1);
}
private List<Process> getProcessList(String cmd, int index) throws IOException {
Socket socket = connect(info.adbHost, info.adbPort);
List<Process> 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<String> 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();
}
}
}
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<String> 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<String> getProp(String entry) throws IOException {
try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
List<String> 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<Process> getProcessByPkg(String pkg) throws IOException {
return getProcessList("ps | grep " + pkg, 0);
}
@NonNull
public List<Process> getProcessList() throws IOException {
return getProcessList("ps", 1);
}
private List<Process> getProcessList(String cmd, int index) throws IOException {
try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
List<Process> 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<String> 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;
}
}
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;
}
}
......@@ -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<ADB.DeviceInfo> deviceInfoList) {
public void onDeviceStatusChange(List<ADBDeviceInfo> deviceInfoList) {
List<DeviceNode> 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<String> id) {
public void jdwpProcessOccurred(ADBDevice device, Set<String> id) {
List<ADB.Process> 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;
......
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;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册