mirror of
https://github.com/revanced/smali.git
synced 2025-05-01 15:14:32 +02:00
Implement instruction fixing in MutableMethodImplementation
This commit is contained in:
parent
e80efa670f
commit
160449b83a
@ -1,8 +1,6 @@
|
||||
package org.jf.dexlib2.builder.instruction;
|
||||
package org.jf.dexlib2.builder;
|
||||
|
||||
import org.jf.dexlib2.Opcode;
|
||||
import org.jf.dexlib2.builder.BuilderInstruction;
|
||||
import org.jf.dexlib2.builder.Label;
|
||||
import org.jf.dexlib2.iface.instruction.OffsetInstruction;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@ -18,6 +16,21 @@ public abstract class BuilderOffsetInstruction extends BuilderInstruction implem
|
||||
}
|
||||
|
||||
@Override public int getCodeOffset() {
|
||||
int codeOffset = internalGetCodeOffset();
|
||||
if ((this.getCodeUnits() == 1 && (codeOffset < Byte.MIN_VALUE || codeOffset > Byte.MAX_VALUE)) ||
|
||||
(this.getCodeUnits() == 2 && (codeOffset < Short.MIN_VALUE || codeOffset > Short.MAX_VALUE))) {
|
||||
throw new IllegalStateException("Target is out of range");
|
||||
}
|
||||
return codeOffset;
|
||||
}
|
||||
|
||||
|
||||
int internalGetCodeOffset() {
|
||||
return target.getCodeAddress() - this.getLocation().getCodeAddress();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Label getTarget() {
|
||||
return target;
|
||||
}
|
||||
}
|
@ -18,13 +18,13 @@ public class MethodImplementationBuilder {
|
||||
|
||||
private MethodLocation currentLocation;
|
||||
|
||||
public MethodImplementationBuilder() {
|
||||
this.impl = new MutableMethodImplementation();
|
||||
public MethodImplementationBuilder(int registerCount) {
|
||||
this.impl = new MutableMethodImplementation(registerCount);
|
||||
this.currentLocation = impl.instructionList.get(0);
|
||||
}
|
||||
|
||||
public MethodImplementation buildMethodImplementation() {
|
||||
return impl.buildMethodImplementation();
|
||||
public MethodImplementation getMethodImplementation() {
|
||||
return impl;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,8 +22,7 @@ public class MethodLocation {
|
||||
private List<Label> labels = Lists.newArrayList();
|
||||
List<BuilderDebugItem> debugItems = Lists.newArrayList();
|
||||
|
||||
MethodLocation(@Nullable BuilderInstruction instruction,
|
||||
int codeAddress, int index) {
|
||||
MethodLocation(@Nullable BuilderInstruction instruction, int codeAddress, int index) {
|
||||
this.instruction = instruction;
|
||||
this.codeAddress = codeAddress;
|
||||
this.index = index;
|
||||
|
@ -1,29 +1,71 @@
|
||||
package org.jf.dexlib2.builder;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.jf.dexlib2.Opcode;
|
||||
import org.jf.dexlib2.builder.instruction.BuilderInstruction10x;
|
||||
import org.jf.dexlib2.builder.instruction.BuilderInstruction20t;
|
||||
import org.jf.dexlib2.builder.instruction.BuilderInstruction30t;
|
||||
import org.jf.dexlib2.iface.ExceptionHandler;
|
||||
import org.jf.dexlib2.iface.MethodImplementation;
|
||||
import org.jf.dexlib2.iface.TryBlock;
|
||||
import org.jf.dexlib2.iface.debug.DebugItem;
|
||||
import org.jf.dexlib2.iface.instruction.Instruction;
|
||||
import org.jf.dexlib2.iface.reference.TypeReference;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
public class MutableMethodImplementation {
|
||||
public class MutableMethodImplementation implements MethodImplementation {
|
||||
private final int registerCount;
|
||||
final ArrayList<MethodLocation> instructionList = Lists.newArrayList(new MethodLocation(null, 0, 0));
|
||||
private final ArrayList<BuilderTryBlock> tryBlocks = Lists.newArrayList();
|
||||
private boolean fixInstructions = true;
|
||||
|
||||
public MutableMethodImplementation() {
|
||||
public MutableMethodImplementation(int registerCount) {
|
||||
this.registerCount = registerCount;
|
||||
}
|
||||
|
||||
public MethodImplementation buildMethodImplementation() {
|
||||
return null;
|
||||
@Override public int getRegisterCount() {
|
||||
return registerCount;
|
||||
}
|
||||
|
||||
public List<MethodLocation> getInstructions() {
|
||||
return Collections.unmodifiableList(instructionList);
|
||||
@Nonnull
|
||||
public List<Instruction> getInstructions() {
|
||||
if (fixInstructions) {
|
||||
fixInstructions();
|
||||
}
|
||||
|
||||
return new AbstractList<Instruction>() {
|
||||
@Override public Instruction get(int i) {
|
||||
if (i >= size()) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return instructionList.get(i).instruction;
|
||||
}
|
||||
|
||||
@Override public int size() {
|
||||
// don't include the last MethodLocation, which always has a null instruction
|
||||
return instructionList.size() - 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull @Override public List<? extends TryBlock<? extends ExceptionHandler>> getTryBlocks() {
|
||||
return Collections.unmodifiableList(tryBlocks);
|
||||
}
|
||||
|
||||
@Nonnull @Override public Iterable<? extends DebugItem> getDebugItems() {
|
||||
return Iterables.concat(
|
||||
Iterables.transform(instructionList, new Function<MethodLocation, Iterable<? extends DebugItem>>() {
|
||||
@Nullable @Override public Iterable<? extends DebugItem> apply(@Nullable MethodLocation input) {
|
||||
assert input != null;
|
||||
return input.getDebugItems();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public void addCatch(@Nullable TypeReference type, @Nonnull Label from,
|
||||
@ -68,14 +110,45 @@ public class MutableMethodImplementation {
|
||||
assert index == instructionList.size()-1;
|
||||
}
|
||||
}
|
||||
|
||||
this.fixInstructions = true;
|
||||
}
|
||||
|
||||
public void addInstruction(BuilderInstruction instruction) {
|
||||
public void addInstruction(@Nonnull BuilderInstruction instruction) {
|
||||
MethodLocation last = instructionList.get(instructionList.size()-1);
|
||||
last.instruction = instruction;
|
||||
|
||||
int nextCodeAddress = last.codeAddress + instruction.getCodeUnits();
|
||||
instructionList.add(new MethodLocation(null, nextCodeAddress, instructionList.size()));
|
||||
|
||||
this.fixInstructions = true;
|
||||
}
|
||||
|
||||
public void replaceInstruction(int index, @Nonnull BuilderInstruction replacementInstruction) {
|
||||
if (index >= instructionList.size() - 1) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
MethodLocation replaceLocation = instructionList.get(index);
|
||||
replacementInstruction.location = replaceLocation;
|
||||
BuilderInstruction old = replaceLocation.instruction;
|
||||
assert old != null;
|
||||
old.location = null;
|
||||
replaceLocation.instruction = replacementInstruction;
|
||||
|
||||
// TODO: factor out index/address fix up loop
|
||||
int codeAddress = replaceLocation.codeAddress + replaceLocation.instruction.getCodeUnits();
|
||||
for (int i=index+1; i<instructionList.size(); i++) {
|
||||
MethodLocation location = instructionList.get(i);
|
||||
location.codeAddress = codeAddress;
|
||||
|
||||
Instruction instruction = location.getInstruction();
|
||||
if (instruction != null) {
|
||||
codeAddress += instruction.getCodeUnits();
|
||||
} else {
|
||||
assert i == instructionList.size() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeInstruction(int index) {
|
||||
@ -102,6 +175,8 @@ public class MutableMethodImplementation {
|
||||
assert i == instructionList.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.fixInstructions = true;
|
||||
}
|
||||
|
||||
public void swapInstructions(int index1, int index2) {
|
||||
@ -139,5 +214,128 @@ public class MutableMethodImplementation {
|
||||
assert instruction != null;
|
||||
codeAddress += location.instruction.getCodeUnits();
|
||||
}
|
||||
|
||||
this.fixInstructions = true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BuilderInstruction getFirstNonNop(int startIndex) {
|
||||
|
||||
for (int i=startIndex; i<instructionList.size()-1; i++) {
|
||||
BuilderInstruction instruction = instructionList.get(i).instruction;
|
||||
assert instruction != null;
|
||||
if (instruction.getOpcode() != Opcode.NOP) {
|
||||
return instruction;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void fixInstructions() {
|
||||
HashSet<MethodLocation> payloadLocations = Sets.newHashSet();
|
||||
|
||||
for (MethodLocation location: instructionList) {
|
||||
BuilderInstruction instruction = location.instruction;
|
||||
if (instruction != null) {
|
||||
switch (instruction.getOpcode()) {
|
||||
case SPARSE_SWITCH:
|
||||
case PACKED_SWITCH: {
|
||||
MethodLocation targetLocation =
|
||||
((BuilderOffsetInstruction)instruction).getTarget().getLocation();
|
||||
BuilderInstruction targetInstruction = targetLocation.instruction;
|
||||
if (targetInstruction == null) {
|
||||
throw new IllegalStateException(String.format("Switch instruction at address/index " +
|
||||
"0x%x/%d points to the end of the method.", location.codeAddress, location.index));
|
||||
}
|
||||
|
||||
if (targetInstruction.getOpcode() == Opcode.NOP) {
|
||||
targetInstruction = getFirstNonNop(targetLocation.index+1);
|
||||
}
|
||||
if (targetInstruction == null || !(targetInstruction instanceof BuilderSwitchPayload)) {
|
||||
throw new IllegalStateException(String.format("Switch instruction at address/index " +
|
||||
"0x%x/%d does not refer to a payload instruction.",
|
||||
location.codeAddress, location.index));
|
||||
}
|
||||
if ((instruction.opcode == Opcode.PACKED_SWITCH &&
|
||||
targetInstruction.getOpcode() != Opcode.PACKED_SWITCH_PAYLOAD) ||
|
||||
(instruction.opcode == Opcode.SPARSE_SWITCH &&
|
||||
targetInstruction.getOpcode() != Opcode.SPARSE_SWITCH_PAYLOAD)) {
|
||||
throw new IllegalStateException(String.format("Switch instruction at address/index " +
|
||||
"0x%x/%d refers to the wrong type of payload instruction.",
|
||||
location.codeAddress, location.index));
|
||||
}
|
||||
|
||||
if (!payloadLocations.add(targetLocation)) {
|
||||
throw new IllegalStateException("Multiple switch instructions refer to the same payload. " +
|
||||
"This is not currently supported. Please file a bug :)");
|
||||
}
|
||||
|
||||
((BuilderSwitchPayload)targetInstruction).referrer = location;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean madeChanges;
|
||||
do {
|
||||
madeChanges = false;
|
||||
|
||||
for (int index=0; index<instructionList.size(); index++) {
|
||||
MethodLocation location = instructionList.get(index);
|
||||
BuilderInstruction instruction = location.instruction;
|
||||
if (instruction != null) {
|
||||
switch (instruction.getOpcode()) {
|
||||
case GOTO: {
|
||||
int offset = ((BuilderOffsetInstruction)instruction).internalGetCodeOffset();
|
||||
if (offset < Byte.MIN_VALUE || offset > Byte.MAX_VALUE) {
|
||||
BuilderOffsetInstruction replacement;
|
||||
if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) {
|
||||
replacement = new BuilderInstruction30t(Opcode.GOTO_32,
|
||||
((BuilderOffsetInstruction)instruction).getTarget());
|
||||
} else {
|
||||
replacement = new BuilderInstruction20t(Opcode.GOTO_16,
|
||||
((BuilderOffsetInstruction)instruction).getTarget());
|
||||
}
|
||||
replaceInstruction(location.index, replacement);
|
||||
madeChanges = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GOTO_16: {
|
||||
int offset = ((BuilderOffsetInstruction)instruction).internalGetCodeOffset();
|
||||
if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) {
|
||||
BuilderOffsetInstruction replacement = new BuilderInstruction30t(Opcode.GOTO_32,
|
||||
((BuilderOffsetInstruction)instruction).getTarget());
|
||||
replaceInstruction(location.index, replacement);
|
||||
madeChanges = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SPARSE_SWITCH_PAYLOAD:
|
||||
case PACKED_SWITCH_PAYLOAD:
|
||||
case ARRAY_PAYLOAD: {
|
||||
if ((location.codeAddress & 0x01) != 0) {
|
||||
int previousIndex = location.index - 1;
|
||||
MethodLocation previousLocation = instructionList.get(previousIndex);
|
||||
Instruction previousInstruction = previousLocation.instruction;
|
||||
assert previousInstruction != null;
|
||||
if (previousInstruction.getOpcode() == Opcode.NOP) {
|
||||
removeInstruction(previousIndex);
|
||||
index--;
|
||||
} else {
|
||||
addInstruction(location.index, new BuilderInstruction10x(Opcode.NOP));
|
||||
index++;
|
||||
}
|
||||
madeChanges = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (madeChanges);
|
||||
|
||||
fixInstructions = false;
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ package org.jf.dexlib2.builder.instruction;
|
||||
|
||||
import org.jf.dexlib2.Format;
|
||||
import org.jf.dexlib2.Opcode;
|
||||
import org.jf.dexlib2.builder.BuilderOffsetInstruction;
|
||||
import org.jf.dexlib2.builder.Label;
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction10t;
|
||||
|
||||
|
@ -33,6 +33,7 @@ package org.jf.dexlib2.builder.instruction;
|
||||
|
||||
import org.jf.dexlib2.Format;
|
||||
import org.jf.dexlib2.Opcode;
|
||||
import org.jf.dexlib2.builder.BuilderOffsetInstruction;
|
||||
import org.jf.dexlib2.builder.Label;
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction20t;
|
||||
|
||||
|
@ -33,6 +33,7 @@ package org.jf.dexlib2.builder.instruction;
|
||||
|
||||
import org.jf.dexlib2.Format;
|
||||
import org.jf.dexlib2.Opcode;
|
||||
import org.jf.dexlib2.builder.BuilderOffsetInstruction;
|
||||
import org.jf.dexlib2.builder.Label;
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction21t;
|
||||
import org.jf.dexlib2.util.Preconditions;
|
||||
|
@ -33,6 +33,7 @@ package org.jf.dexlib2.builder.instruction;
|
||||
|
||||
import org.jf.dexlib2.Format;
|
||||
import org.jf.dexlib2.Opcode;
|
||||
import org.jf.dexlib2.builder.BuilderOffsetInstruction;
|
||||
import org.jf.dexlib2.builder.Label;
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction22t;
|
||||
import org.jf.dexlib2.util.Preconditions;
|
||||
|
@ -33,6 +33,7 @@ package org.jf.dexlib2.builder.instruction;
|
||||
|
||||
import org.jf.dexlib2.Format;
|
||||
import org.jf.dexlib2.Opcode;
|
||||
import org.jf.dexlib2.builder.BuilderOffsetInstruction;
|
||||
import org.jf.dexlib2.builder.Label;
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction30t;
|
||||
|
||||
|
@ -33,6 +33,7 @@ package org.jf.dexlib2.builder.instruction;
|
||||
|
||||
import org.jf.dexlib2.Format;
|
||||
import org.jf.dexlib2.Opcode;
|
||||
import org.jf.dexlib2.builder.BuilderOffsetInstruction;
|
||||
import org.jf.dexlib2.builder.Label;
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction31t;
|
||||
import org.jf.dexlib2.util.Preconditions;
|
||||
|
@ -37,6 +37,7 @@ options {
|
||||
package org.jf.smali;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.antlr.runtime.BitSet;
|
||||
@ -358,7 +359,6 @@ method returns[BuilderMethod ret]
|
||||
$method::methodParameterRegisters = 0;
|
||||
int accessFlags = 0;
|
||||
$method::isStatic = false;
|
||||
$method::methodBuilder = new MethodImplementationBuilder();
|
||||
}
|
||||
:
|
||||
^(I_METHOD
|
||||
@ -370,15 +370,24 @@ method returns[BuilderMethod ret]
|
||||
$method::methodParameterRegisters =
|
||||
MethodUtil.getParameterRegisterCount($method_name_and_prototype.parameters, $method::isStatic);
|
||||
}
|
||||
(registers_directive
|
||||
{
|
||||
if ($registers_directive.isLocalsDirective) {
|
||||
$method::totalMethodRegisters = $registers_directive.registers + $method::methodParameterRegisters;
|
||||
} else {
|
||||
$method::totalMethodRegisters = $registers_directive.registers;
|
||||
}
|
||||
}
|
||||
)?
|
||||
(
|
||||
(registers_directive
|
||||
{
|
||||
if ($registers_directive.isLocalsDirective) {
|
||||
$method::totalMethodRegisters = $registers_directive.registers + $method::methodParameterRegisters;
|
||||
} else {
|
||||
$method::totalMethodRegisters = $registers_directive.registers;
|
||||
}
|
||||
|
||||
$method::methodBuilder = new MethodImplementationBuilder($method::totalMethodRegisters);
|
||||
|
||||
})
|
||||
|
|
||||
/* epsilon */
|
||||
{
|
||||
$method::methodBuilder = new MethodImplementationBuilder(0);
|
||||
}
|
||||
)
|
||||
ordered_method_items
|
||||
catches
|
||||
parameters[$method_name_and_prototype.parameters]
|
||||
@ -397,7 +406,9 @@ method returns[BuilderMethod ret]
|
||||
isNative = true;
|
||||
}
|
||||
|
||||
/*if ($statements.instructions.size() == 0) {
|
||||
methodImplementation = $method::methodBuilder.getMethodImplementation();
|
||||
|
||||
if (Iterables.isEmpty(methodImplementation.getInstructions())) {
|
||||
if (!isAbstract && !isNative) {
|
||||
throw new SemanticException(input, $I_METHOD, "A non-abstract/non-native method must have at least 1 instruction");
|
||||
}
|
||||
@ -417,19 +428,17 @@ method returns[BuilderMethod ret]
|
||||
}
|
||||
}
|
||||
|
||||
if (labels.size() > 0) {
|
||||
throw new SemanticException(input, $I_METHOD, "Labels cannot be present in \%s method", methodType);
|
||||
}
|
||||
|
||||
if ((tryBlocks != null && tryBlocks.size() > 0)) {
|
||||
if (methodImplementation.getTryBlocks().size() > 0) {
|
||||
throw new SemanticException(input, $I_METHOD, "try/catch blocks cannot be present in \%s method", methodType);
|
||||
}
|
||||
|
||||
if (debugItems != null && debugItems.size() > 0) {
|
||||
if (!Iterables.isEmpty(methodImplementation.getDebugItems())) {
|
||||
throw new SemanticException(input, $I_METHOD, "debug directives cannot be present in \%s method", methodType);
|
||||
}
|
||||
} else */{
|
||||
/*if (isAbstract) {
|
||||
|
||||
methodImplementation = null;
|
||||
} else {
|
||||
if (isAbstract) {
|
||||
throw new SemanticException(input, $I_METHOD, "An abstract method cannot have any instructions");
|
||||
}
|
||||
if (isNative) {
|
||||
@ -444,9 +453,7 @@ method returns[BuilderMethod ret]
|
||||
throw new SemanticException(input, $registers_directive.start, "This method requires at least " +
|
||||
Integer.toString($method::methodParameterRegisters) +
|
||||
" registers, for the method parameters");
|
||||
}*/
|
||||
|
||||
methodImplementation = $method::methodBuilder.buildMethodImplementation();
|
||||
}
|
||||
}
|
||||
|
||||
$ret = dexBuilder.internMethod(
|
||||
|
Loading…
x
Reference in New Issue
Block a user