Skip to content

Commit

Permalink
feat(java-input): support jsr/ret opcodes (#2039)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Jan 15, 2024
1 parent 8ed4818 commit 306bc7a
Show file tree
Hide file tree
Showing 19 changed files with 346 additions and 29 deletions.
12 changes: 12 additions & 0 deletions jadx-core/src/main/java/jadx/core/codegen/InsnGen.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import jadx.core.dex.instructions.args.Named;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.java.JsrNode;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode;
Expand Down Expand Up @@ -633,6 +634,17 @@ private void makeInsnBody(ICodeWriter code, InsnNode insn, Set<Flags> state) thr
}
break;

case JAVA_JSR:
fallbackOnlyInsn(insn);
code.add("jsr -> ").add(MethodGen.getLabelName(((JsrNode) insn).getTarget()));
break;

case JAVA_RET:
fallbackOnlyInsn(insn);
code.add("ret ");
addArg(code, insn.getArg(0));
break;

default:
throw new CodegenException(mth, "Unknown instruction: " + insn.getType());
}
Expand Down
2 changes: 2 additions & 0 deletions jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,6 @@ public enum AFlag {
CLASS_UNLOADED, // class was completely unloaded

DONT_UNLOAD_CLASS, // don't unload class after code generation (only for tests and debug!)

RESOLVE_JAVA_JSR,
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import jadx.api.plugins.input.insns.custom.ICustomPayload;
import jadx.api.plugins.input.insns.custom.ISwitchPayload;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.info.FieldInfo;
Expand All @@ -23,6 +24,7 @@
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.java.JsrNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
Expand Down Expand Up @@ -332,6 +334,16 @@ protected InsnNode decode(InsnData insn) throws DecodeException {
case GOTO:
return new GotoNode(insn.getTarget());

case JAVA_JSR:
method.add(AFlag.RESOLVE_JAVA_JSR);
JsrNode jsr = new JsrNode(insn.getTarget());
jsr.setResult(InsnArg.reg(insn, 0, ArgType.UNKNOWN_INT));
return jsr;

case JAVA_RET:
method.add(AFlag.RESOLVE_JAVA_JSR);
return insn(InsnType.JAVA_RET, null, InsnArg.reg(insn, 0, ArgType.UNKNOWN_INT));

case THROW:
return insn(InsnType.THROW, null, InsnArg.reg(insn, 0, ArgType.THROWABLE));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,9 @@ public enum InsnType {
PHI,

// fake insn to keep arguments which will be used in regions codegen
REGION_ARG
REGION_ARG,

// Java specific dynamic jump instructions
JAVA_JSR,
JAVA_RET,
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ public abstract class ArgType {
public static final ArgType INT_BOOLEAN = unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN);
public static final ArgType BYTE_BOOLEAN = unknown(PrimitiveType.BYTE, PrimitiveType.BOOLEAN);

public static final ArgType UNKNOWN_INT = unknown(PrimitiveType.INT);

protected int hash;

private static ArgType primitive(PrimitiveType stype) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package jadx.core.dex.instructions.java;

import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.TargetInsnNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;

public class JsrNode extends TargetInsnNode {

protected final int target;

public JsrNode(int target) {
this(InsnType.JAVA_JSR, target, 0);
}

protected JsrNode(InsnType type, int target, int argsCount) {
super(type, argsCount);
this.target = target;
}

public int getTarget() {
return target;
}

@Override
public InsnNode copy() {
return copyCommonParams(new JsrNode(target));
}

@Override
public String toString() {
return baseString() + " -> " + InsnUtils.formatOffset(target) + attributesString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public void visit(MethodNode mth) throws JadxException {
case RETURN:
case IF:
case GOTO:
case JAVA_JSR:
case MOVE:
case MOVE_EXCEPTION:
case ARITH: // ??
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ private static boolean processMove(MethodNode mth, InsnNode move) {
}
}
SSAVar ssaVar = resultArg.getSVar();
if (ssaVar.getUseList().isEmpty()) {
// unused result
return true;
}

if (ssaVar.isUsedInPhi()) {
return false;
// TODO: review conditions of 'up' move inline (test TestMoveInline)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import jadx.core.dex.instructions.SwitchInsn;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.java.JsrNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.blocks.BlockSplitter;
Expand Down Expand Up @@ -73,6 +74,14 @@ private static void initJumps(MethodNode mth, InsnNode[] insnByOffset) {
addJump(mth, insnByOffset, offset, ((GotoNode) insn).getTarget());
break;

case JAVA_JSR:
addJump(mth, insnByOffset, offset, ((JsrNode) insn).getTarget());
int onRet = getNextInsnOffset(insnByOffset, offset);
if (onRet != -1) {
addJump(mth, insnByOffset, offset, onRet);
}
break;

case INVOKE:
if (insn.getResult() == null) {
ArgType retType = ((BaseInvokeNode) insn).getCallMth().getReturnType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@

public class BlockSplitter extends AbstractVisitor {

// leave these instructions alone in block node
/**
* Leave these instructions alone in the block node
*/
private static final Set<InsnType> SEPARATE_INSNS = EnumSet.of(
InsnType.RETURN,
InsnType.IF,
Expand All @@ -42,6 +44,18 @@ public static boolean isSeparate(InsnType insnType) {
return SEPARATE_INSNS.contains(insnType);
}

/**
* Split without connecting to the next block
*/
private static final Set<InsnType> SPLIT_WITHOUT_CONNECT = EnumSet.of(
InsnType.RETURN,
InsnType.THROW,
InsnType.GOTO,
InsnType.IF,
InsnType.SWITCH,
InsnType.JAVA_JSR,
InsnType.JAVA_RET);

@Override
public void visit(MethodNode mth) {
if (mth.isNoCode()) {
Expand All @@ -54,6 +68,10 @@ public void visit(MethodNode mth) {
addTempConnectionsForExcHandlers(mth, blocksMap);

expandMoveMulti(mth);
if (mth.contains(AFlag.RESOLVE_JAVA_JSR)) {
ResolveJavaJSR.process(mth);
}

removeJumpAttr(mth);
removeInsns(mth);
removeEmptyDetachedBlocks(mth);
Expand Down Expand Up @@ -88,27 +106,16 @@ private static Map<Integer, BlockNode> splitBasicBlocks(MethodNode mth) {
curBlock = connectNewBlock(mth, curBlock, insnOffset);
} else {
InsnType prevType = prevInsn.getType();
switch (prevType) {
case RETURN:
case THROW:
case GOTO:
case IF:
case SWITCH:
// split without connect to next block
curBlock = startNewBlock(mth, insnOffset);
break;

default:
if (isSeparate(prevType)
|| isSeparate(insn.getType())
|| insn.contains(AFlag.TRY_ENTER)
|| prevInsn.contains(AFlag.TRY_LEAVE)
|| insn.contains(AType.EXC_HANDLER)
|| isSplitByJump(prevInsn, insn)
|| isDoWhile(blocksMap, curBlock, insn)) {
curBlock = connectNewBlock(mth, curBlock, insnOffset);
}
break;
if (SPLIT_WITHOUT_CONNECT.contains(prevType)) {
curBlock = startNewBlock(mth, insnOffset);
} else if (isSeparate(prevType)
|| isSeparate(insn.getType())
|| insn.contains(AFlag.TRY_ENTER)
|| prevInsn.contains(AFlag.TRY_LEAVE)
|| insn.contains(AType.EXC_HANDLER)
|| isSplitByJump(prevInsn, insn)
|| isDoWhile(blocksMap, curBlock, insn)) {
curBlock = connectNewBlock(mth, curBlock, insnOffset);
}
}
blocksMap.put(insnOffset, curBlock);
Expand Down Expand Up @@ -201,6 +208,33 @@ static void copyBlockData(BlockNode from, BlockNode to) {
to.copyAttributesFrom(from);
}

static List<BlockNode> copyBlocksTree(MethodNode mth, List<BlockNode> blocks) {
List<BlockNode> copyBlocks = new ArrayList<>(blocks.size());
Map<BlockNode, BlockNode> map = new HashMap<>();
for (BlockNode block : blocks) {
BlockNode newBlock = startNewBlock(mth, block.getStartOffset());
copyBlockData(block, newBlock);
copyBlocks.add(newBlock);
map.put(block, newBlock);
}
for (BlockNode block : blocks) {
BlockNode newBlock = getNewBlock(block, map);
for (BlockNode successor : block.getSuccessors()) {
BlockNode newSuccessor = getNewBlock(successor, map);
BlockSplitter.connect(newBlock, newSuccessor);
}
}
return copyBlocks;
}

private static BlockNode getNewBlock(BlockNode block, Map<BlockNode, BlockNode> map) {
BlockNode newBlock = map.get(block);
if (newBlock == null) {
throw new JadxRuntimeException("Copy blocks tree failed. Missing block for connection: " + block);
}
return newBlock;
}

static void replaceTarget(BlockNode source, BlockNode oldTarget, BlockNode newTarget) {
InsnNode lastInsn = BlockUtils.getLastInsn(source);
if (lastInsn instanceof TargetInsnNode) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package jadx.core.dex.visitors.blocks;

import java.util.ArrayList;
import java.util.List;

import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;

/**
* Duplicate code to resolve java jsr/ret.
* JSR (jump subroutine) allows executing the same code from different places.
* Used mostly for 'finally' blocks, deprecated in Java 7.
*/
public class ResolveJavaJSR {

public static void process(MethodNode mth) {
int blocksCount = mth.getBasicBlocks().size();
int k = 0;
while (true) {
boolean changed = resolve(mth);
if (!changed) {
break;
}
if (k++ > blocksCount) {
throw new JadxRuntimeException("Fail to resolve jsr instructions");
}
}
}

private static boolean resolve(MethodNode mth) {
List<BlockNode> blocks = mth.getBasicBlocks();
int blocksCount = blocks.size();
for (BlockNode block : blocks) {
if (BlockUtils.checkLastInsnType(block, InsnType.JAVA_RET)) {
resolveForRetBlock(mth, block);
if (blocksCount != mth.getBasicBlocks().size()) {
return true;
}
}
}
return false;
}

private static void resolveForRetBlock(MethodNode mth, BlockNode retBlock) {
BlockUtils.visitPredecessorsUntil(mth, retBlock, startBlock -> {
List<BlockNode> preds = startBlock.getPredecessors();
if (preds.size() > 1
&& preds.stream().allMatch(p -> BlockUtils.checkLastInsnType(p, InsnType.JAVA_JSR))) {
List<BlockNode> jsrBlocks = new ArrayList<>(preds);
List<BlockNode> dupBlocks = BlockUtils.collectAllSuccessors(mth, startBlock, false);
removeInsns(retBlock, startBlock, jsrBlocks);
processBlocks(mth, retBlock, startBlock, jsrBlocks, dupBlocks);
return true;
}
return false;
});
}

private static void removeInsns(BlockNode retBlock, BlockNode startBlock, List<BlockNode> jsrBlocks) {
InsnNode retInsn = ListUtils.removeLast(retBlock.getInstructions());
if (retInsn != null && retInsn.getType() == InsnType.JAVA_RET) {
InsnArg retArg = retInsn.getArg(0);
if (retArg.isRegister()) {
int regNum = ((RegisterArg) retArg).getRegNum();
InsnNode startInsn = BlockUtils.getFirstInsn(startBlock);
if (startInsn != null
&& startInsn.getType() == InsnType.MOVE
&& startInsn.getResult().getRegNum() == regNum) {
startBlock.getInstructions().remove(0);
}
}
}
jsrBlocks.forEach(p -> ListUtils.removeLast(p.getInstructions()));
}

private static void processBlocks(MethodNode mth, BlockNode retBlock, BlockNode startBlock,
List<BlockNode> jsrBlocks, List<BlockNode> dupBlocks) {
BlockNode first = null;
for (BlockNode jsrBlock : jsrBlocks) {
if (first == null) {
first = jsrBlock;
} else {
BlockNode pathBlock = BlockUtils.selectOther(startBlock, jsrBlock.getSuccessors());
BlockSplitter.removeConnection(jsrBlock, startBlock);
BlockSplitter.removeConnection(jsrBlock, pathBlock);
List<BlockNode> newBlocks = BlockSplitter.copyBlocksTree(mth, dupBlocks);
BlockNode newStart = newBlocks.get(dupBlocks.indexOf(startBlock));
BlockNode newRetBlock = newBlocks.get(dupBlocks.indexOf(retBlock));
BlockSplitter.connect(jsrBlock, newStart);
BlockSplitter.connect(newRetBlock, pathBlock);
}
}
if (first != null) {
BlockNode pathBlock = BlockUtils.selectOther(startBlock, first.getSuccessors());
BlockSplitter.removeConnection(first, pathBlock);
BlockSplitter.connect(retBlock, pathBlock);
}
}
}
Loading

0 comments on commit 306bc7a

Please sign in to comment.