diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java index 23649ffde96530727f2f7451ac38bf85b115ee9f..438dcd4dc37b4dad7c32fe218b2e56c9e4cec075 100644 --- a/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java +++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java @@ -7,6 +7,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; @@ -72,6 +73,8 @@ public class Smali { private final boolean printFileOffset = true; private final boolean printBytecode = true; + private boolean isJavaBytecode; + private Smali() { } @@ -79,6 +82,7 @@ public class Smali { cls = cls.getTopParentClass(); SmaliWriter code = new SmaliWriter(cls); Smali smali = new Smali(); + smali.isJavaBytecode = cls.getInputFileName().endsWith(".class"); // TODO: add flag to api smali.writeClass(code, cls); smali.codeInfo = code.finish(); return smali; @@ -334,33 +338,41 @@ public class Smali { } private void formatInsn(InsnData insn, InsnNode node, LineInfo line) { - line.getLineWriter().delete(0, line.getLineWriter().length()); + StringBuilder lw = line.getLineWriter(); + lw.delete(0, lw.length()); fmtCols(insn, line); if (fmtPayloadInsn(insn, line)) { return; } - line.getLineWriter() - .append(String.format(FMT_INSN_COL, MNEMONIC.MNEMONICS[getOpenCodeByte(insn)])) - .append(" "); + lw.append(formatInsnName(insn)).append(" "); fmtRegs(insn, node.getType(), line); if (!tryFormatTargetIns(insn, node.getType(), line)) { if (hasLiteral(insn)) { - line.getLineWriter().append(", ").append(literal(insn)); + lw.append(", ").append(literal(insn)); } else if (node.getType() == InsnType.INVOKE) { - line.getLineWriter().append(", ").append(method(insn)); + lw.append(", ").append(method(insn)); } else if (insn.getIndexType() == InsnIndexType.FIELD_REF) { - line.getLineWriter().append(", ").append(field(insn)); + lw.append(", ").append(field(insn)); } else if (insn.getIndexType() == InsnIndexType.STRING_REF) { - line.getLineWriter().append(", ").append(str(insn)); + lw.append(", ").append(str(insn)); } else if (insn.getIndexType() == InsnIndexType.TYPE_REF) { - line.getLineWriter().append(", ").append(type(insn)); + lw.append(", ").append(type(insn)); } else if (insn.getOpcode() == CONST_METHOD_HANDLE) { - line.getLineWriter().append(", ").append(methodHandle(insn)); + lw.append(", ").append(methodHandle(insn)); } else if (insn.getOpcode() == CONST_METHOD_TYPE) { - line.getLineWriter().append(", ").append(proto(insn, insn.getIndex())); + lw.append(", ").append(proto(insn, insn.getIndex())); } } - line.addInsnLine(insn.getOffset(), line.getLineWriter().toString()); + line.addInsnLine(insn.getOffset(), lw.toString()); + } + + private String formatInsnName(InsnData insn) { + if (isJavaBytecode) { + // add api opcode, because registers not used + return String.format("%-" + INSN_COL_WIDTH + "s | %-15s", + insn.getOpcodeMnemonic(), insn.getOpcode().name().toLowerCase(Locale.ROOT).replace('_', '-')); + } + return String.format(FMT_INSN_COL, insn.getOpcodeMnemonic()); } private boolean tryFormatTargetIns(InsnData insn, InsnType insnType, LineInfo line) { @@ -647,23 +659,30 @@ public class Smali { private void fmtRegs(InsnData insn, InsnType insnType, LineInfo line) { boolean appendBrace = insnType == InsnType.INVOKE || isRegList(insn); + StringBuilder lw = line.getLineWriter(); + if (insnType == InsnType.INVOKE) { + int resultReg = insn.getResultReg(); + if (resultReg != -1) { + lw.append(line.getRegName(resultReg)).append(" <= "); + } + } if (appendBrace) { - line.getLineWriter().append("{"); + lw.append("{"); } if (isRangeRegIns(insn)) { - line.getLineWriter().append(line.getRegName(insn.getReg(0))) + lw.append(line.getRegName(insn.getReg(0))) .append(" .. ") .append(line.getRegName(insn.getReg(insn.getRegsCount() - 1))); } else if (insn.getRegsCount() > 0) { for (int i = 0; i < insn.getRegsCount(); i++) { if (i > 0) { - line.getLineWriter().append(", "); + lw.append(", "); } - line.getLineWriter().append(line.getRegName(insn.getReg(i))); + lw.append(line.getRegName(insn.getReg(i))); } } if (appendBrace) { - line.getLineWriter().append("}"); + lw.append("}"); } } @@ -692,9 +711,11 @@ public class Smali { private void formatByteCode(StringBuilder smali, byte[] bytes) { int maxLen = Math.min(bytes.length, 4 * 2); // limit to 4 units StringBuilder inHex = new StringBuilder(); - for (int i = 0; i < maxLen - 1; i += 2) { - int temp = ((bytes[i] & 0xff) << 8) | (bytes[i + 1] & 0xff); - inHex.append(String.format("%04x ", temp)); + for (int i = 0; i < maxLen; i++) { + inHex.append(String.format("%02x", bytes[i])); + if (i % 2 == 1) { + inHex.append(' '); + } } smali.append(String.format(FMT_BYTECODE_COL, inHex)); if (maxLen < bytes.length) { diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnData.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnData.java index 87bdb1d944e6b91c212e0fc49dc2bd146d37ddb8..b813aaaddba23342d97cbc532e94abfe6176590a 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnData.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnData.java @@ -66,6 +66,11 @@ public class DexInsnData implements InsnData { return info.getApiOpcode(); } + @Override + public String getOpcodeMnemonic() { + return DexInsnMnemonics.get(opcodeUnit); + } + @Override public byte[] getByteCode() { return externalReader.getByteCode(insnStart, length * 2); // a unit is 2 bytes diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/MNEMONIC.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnMnemonics.java similarity index 79% rename from jadx-gui/src/main/java/jadx/gui/device/debugger/smali/MNEMONIC.java rename to jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnMnemonics.java index c58a01b5223d8bbc8b4cb47f35496f5060b587f8..f53c8df407a9c35c32b618f8a111d5a7015c528b 100644 --- a/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/MNEMONIC.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnMnemonics.java @@ -1,21 +1,28 @@ -package jadx.gui.device.debugger.smali; +package jadx.plugins.input.dex.insns; -public class MNEMONIC { - public static final String[] MNEMONICS = new String[] { - "nop", "move", "move/from16", "move/16", "move-wide", +public class DexInsnMnemonics { + + public static String get(int opcode) { + return MNEMONICS[opcode & 0xFF]; + } + + private static final String[] MNEMONICS = new String[] { + "nop", + "move", "move/from16", "move/16", "move-wide", "move-wide/from16", "move-wide/16", "move-object", "move-object/from16", "move-object/16", "move-result", "move-result-wide", "move-result-object", "move-exception", "return-void", - "return", "return-wide", "return-object", "const/4", "const/16", - "const", "const/high16", "const-wide/16", "const-wide/32", "const-wide", + "return", "return-wide", "return-object", + "const/4", "const/16", "const", "const/high16", "const-wide/16", "const-wide/32", "const-wide", "const-wide/high16", "const-string", "const-string/jumbo", "const-class", "monitor-enter", "monitor-exit", "check-cast", "instance-of", "array-length", "new-instance", "new-array", "filled-new-array", "filled-new-array/range", "fill-array-data", "throw", - "goto", "goto/16", "goto/32", "packed-switch", "sparse-switch", + "goto", "goto/16", "goto/32", + "packed-switch", "sparse-switch", "cmpl-float", "cmpg-float", "cmpl-double", "cmpg-double", "cmp-long", - "if-eq", "if-ne", "if-lt", "if-ge", "if-gt", - "if-le", "if-eqz", "if-nez", "if-ltz", "if-gez", - "if-gtz", "if-lez", "(unused)", "(unused)", "(unused)", - "(unused)", "(unused)", "(unused)", "aget", "aget-wide", + "if-eq", "if-ne", "if-lt", "if-ge", "if-gt", "if-le", + "if-eqz", "if-nez", "if-ltz", "if-gez", "if-gtz", "if-lez", + "(unused)", "(unused)", "(unused)", "(unused)", "(unused)", "(unused)", + "aget", "aget-wide", "aget-object", "aget-boolean", "aget-byte", "aget-char", "aget-short", "aput", "aput-wide", "aput-object", "aput-boolean", "aput-byte", "aput-char", "aput-short", "iget", "iget-wide", "iget-object", @@ -52,6 +59,10 @@ public class MNEMONIC { "(unused)", "(unused)", "(unused)", "(unused)", "(unused)", "(unused)", "(unused)", "(unused)", "(unused)", "(unused)", "(unused)", "(unused)", "(unused)", "(unused)", "(unused)", - "invoke-polymorphic", "invoke-polymorphic/range", "invoke-custom", "invoke-custom/range", "const-method-handle", + "invoke-polymorphic", + "invoke-polymorphic/range", + "invoke-custom", + "invoke-custom/range", + "const-method-handle", "const-method-type" }; } diff --git a/jadx-plugins/jadx-java-input/build.gradle b/jadx-plugins/jadx-java-input/build.gradle index fa1c146e24d4595a95c4ecf7bc5f3573e2e80b84..5665daafbad9feeb7f5632581ffd4eacec202f23 100644 --- a/jadx-plugins/jadx-java-input/build.gradle +++ b/jadx-plugins/jadx-java-input/build.gradle @@ -4,4 +4,8 @@ plugins { dependencies { api(project(":jadx-plugins:jadx-plugins-api")) + + // show bytecode disassemble + implementation 'org.ow2.asm:asm:9.2' + implementation 'org.ow2.asm:asm-util:9.2' } diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaCodeReader.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaCodeReader.java index dd2dee98332a7debb7ae9d924a81b0a573362826..8c5783eacf67576b392fa0a071cb2792194176b8 100644 --- a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaCodeReader.java +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaCodeReader.java @@ -61,15 +61,16 @@ public class JavaCodeReader implements ICodeReader { state.setInsn(insn); int offset = 0; while (offset < codeSize) { + insn.setDecoded(false); + insn.setOffset(offset); + insn.setInsnStart(reader.getOffset()); + int opcode = reader.readU1(); JavaInsnInfo insnInfo = JavaInsnsRegister.get(opcode); if (insnInfo == null) { throw new JavaClassParseException("Unknown opcode: 0x" + Integer.toHexString(opcode)); } - insn.setDecoded(false); insn.setInsnInfo(insnInfo); - insn.setInsnStart(reader.getOffset()); - insn.setOffset(offset); insn.setInsnInfo(insnInfo); insn.setRegsCount(insnInfo.getRegsCount()); insn.setOpcode(insnInfo.getApiOpcode()); diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaInsnData.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaInsnData.java index 6478190c80c758ae16854bea419601a5181278df..0150fbc4709240c259322579c4e75d271ef16806 100644 --- a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaInsnData.java +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaInsnData.java @@ -12,6 +12,7 @@ import jadx.api.plugins.input.insns.InsnIndexType; import jadx.api.plugins.input.insns.Opcode; import jadx.api.plugins.input.insns.custom.ICustomPayload; import jadx.plugins.input.java.data.ConstPoolReader; +import jadx.plugins.input.java.data.DataReader; import jadx.plugins.input.java.data.code.decoders.IJavaInsnDecoder; public class JavaInsnData implements InsnData { @@ -74,9 +75,21 @@ public class JavaInsnData implements InsnData { this.opcode = opcode; } + @Override + public String getOpcodeMnemonic() { + return insnInfo.getName(); + } + @Override public byte[] getByteCode() { - return new byte[0]; + DataReader reader = state.reader(); + int startOffset = reader.getOffset(); + try { + reader.absPos(insnStart); + return reader.readBytes(1 + payloadSize); + } finally { + reader.absPos(startOffset); + } } @Override diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/DisasmUtils.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/DisasmUtils.java index f8c7ba449feb9447bdc1382eb5c9a5e4d1a32334..5c9e0eb50d7d7633918040535bfd3a237beb4352 100644 --- a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/DisasmUtils.java +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/DisasmUtils.java @@ -3,27 +3,42 @@ package jadx.plugins.input.java.utils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.concurrent.TimeUnit; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.util.TraceClassVisitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DisasmUtils { private static final Logger LOG = LoggerFactory.getLogger(DisasmUtils.class); + public static String get(byte[] bytes) { + return useASM(bytes); + } + + private static String useASM(byte[] bytes) { + StringWriter out = new StringWriter(); + TraceClassVisitor tcv = new TraceClassVisitor(new PrintWriter(out)); + new ClassReader(bytes).accept(tcv, 0); + return out.toString(); + } + /** * Use javap as a temporary disassembler for java bytecode + * Don't remove! Useful for debug. */ - public static String get(byte[] bytes) { + private static String useSystemJavaP(byte[] bytes) { try { Path tmpCls = null; try { tmpCls = Files.createTempFile("jadx", ".class"); Files.write(tmpCls, bytes, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); - Process process = Runtime.getRuntime().exec(new String[] { "javap", "-constants", "-v", "-p", "-c", tmpCls.toAbsolutePath().toString() diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/CallSite.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/CallSite.java index 1cb7f9b08ad6ae56da9886b0644e9f71d764c2c4..c2307217b8303a589f15ae8d707b9538f7157e06 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/CallSite.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/CallSite.java @@ -33,4 +33,9 @@ public class CallSite implements ICallSite { public List getValues() { return values; } + + @Override + public String toString() { + return "CallSite{" + values + '}'; + } } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnData.java index 51181d807258727b43934e88d35b56fe39d5c158..ae020fc87c5e1e455e6e6f005bc18f84e41fd9ca 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnData.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnData.java @@ -17,6 +17,8 @@ public interface InsnData { Opcode getOpcode(); + String getOpcodeMnemonic(); + byte[] getByteCode(); InsnIndexType getIndexType();