Duplicate a switch payload that is refered to multiple times

This commit is contained in:
Ben Gruver 2015-03-18 20:01:49 -07:00 committed by Connor Tumbleson
parent 395043667a
commit 1c084171ed
9 changed files with 473 additions and 55 deletions

View File

@ -130,26 +130,37 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
}
if (instruction instanceof Instruction31t) {
Opcode payloadOpcode;
boolean validPayload = true;
switch (instruction.getOpcode()) {
case PACKED_SWITCH:
payloadOpcode = Opcode.PACKED_SWITCH_PAYLOAD;
int baseAddress = methodDef.getPackedSwitchBaseAddress(
this.codeAddress + ((Instruction31t)instruction).getCodeOffset());
if (baseAddress == -1) {
validPayload = false;
}
break;
case SPARSE_SWITCH:
payloadOpcode = Opcode.SPARSE_SWITCH_PAYLOAD;
baseAddress = methodDef.getSparseSwitchBaseAddress(
this.codeAddress + ((Instruction31t)instruction).getCodeOffset());
if (baseAddress == -1) {
validPayload = false;
}
break;
case FILL_ARRAY_DATA:
payloadOpcode = Opcode.ARRAY_PAYLOAD;
try {
methodDef.findPayloadOffset(this.codeAddress + ((Instruction31t)instruction).getCodeOffset(),
Opcode.ARRAY_PAYLOAD);
} catch (InvalidSwitchPayload ex) {
validPayload = false;
}
break;
default:
throw new ExceptionWithContext("Invalid 31t opcode: %s", instruction.getOpcode());
}
try {
methodDef.findSwitchPayload(this.codeAddress + ((Instruction31t)instruction).getCodeOffset(),
payloadOpcode);
} catch (InvalidSwitchPayload ex) {
writer.write("#invalid payload reference");
if (!validPayload) {
writer.write("#invalid payload reference\n");
commentOutInstruction = true;
}
}

View File

@ -29,6 +29,7 @@
package org.jf.baksmali.Adaptors;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.jf.baksmali.Adaptors.Debug.DebugMethodItem;
import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory;
import org.jf.baksmali.baksmaliOptions;
@ -45,11 +46,14 @@ import org.jf.dexlib2.iface.debug.DebugItem;
import org.jf.dexlib2.iface.instruction.Instruction;
import org.jf.dexlib2.iface.instruction.OffsetInstruction;
import org.jf.dexlib2.iface.instruction.ReferenceInstruction;
import org.jf.dexlib2.iface.instruction.formats.Instruction31t;
import org.jf.dexlib2.iface.reference.MethodReference;
import org.jf.dexlib2.immutable.instruction.ImmutableInstruction31t;
import org.jf.dexlib2.util.InstructionOffsetMap;
import org.jf.dexlib2.util.InstructionOffsetMap.InvalidInstructionOffset;
import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.dexlib2.util.SyntheticAccessorResolver;
import org.jf.dexlib2.util.SyntheticAccessorResolver.AccessedMember;
import org.jf.dexlib2.util.TypeUtils;
import org.jf.util.ExceptionWithContext;
import org.jf.util.IndentingWriter;
@ -65,6 +69,8 @@ public class MethodDefinition {
@Nonnull public final Method method;
@Nonnull public final MethodImplementation methodImpl;
@Nonnull public final ImmutableList<Instruction> instructions;
@Nonnull public final List<Instruction> effectiveInstructions;
@Nonnull public final ImmutableList<MethodParameter> methodParameters;
public RegisterFormatter registerFormatter;
@ -86,10 +92,15 @@ public class MethodDefinition {
instructions = ImmutableList.copyOf(methodImpl.getInstructions());
methodParameters = ImmutableList.copyOf(method.getParameters());
effectiveInstructions = Lists.newArrayList(instructions);
packedSwitchMap = new SparseIntArray(0);
sparseSwitchMap = new SparseIntArray(0);
instructionOffsetMap = new InstructionOffsetMap(instructions);
int endOffset = instructionOffsetMap.getInstructionCodeOffset(instructions.size()-1) +
instructions.get(instructions.size()-1).getCodeUnits();
for (int i=0; i<instructions.size(); i++) {
Instruction instruction = instructions.get(i);
@ -99,11 +110,20 @@ public class MethodDefinition {
int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i);
int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset();
try {
targetOffset = findSwitchPayload(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD);
targetOffset = findPayloadOffset(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD);
} catch (InvalidSwitchPayload ex) {
valid = false;
}
if (valid) {
if (packedSwitchMap.get(targetOffset, -1) != -1) {
Instruction payloadInstruction =
findSwitchPayload(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD);
targetOffset = endOffset;
effectiveInstructions.set(i, new ImmutableInstruction31t(opcode,
((Instruction31t)instruction).getRegisterA(), targetOffset-codeOffset));
effectiveInstructions.add(payloadInstruction);
endOffset += payloadInstruction.getCodeUnits();
}
packedSwitchMap.append(targetOffset, codeOffset);
}
} else if (opcode == Opcode.SPARSE_SWITCH) {
@ -111,18 +131,27 @@ public class MethodDefinition {
int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i);
int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset();
try {
targetOffset = findSwitchPayload(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD);
targetOffset = findPayloadOffset(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD);
} catch (InvalidSwitchPayload ex) {
valid = false;
// The offset to the payload instruction was invalid. Nothing to do, except that we won't
// add this instruction to the map.
}
if (valid) {
if (sparseSwitchMap.get(targetOffset, -1) != -1) {
Instruction payloadInstruction =
findSwitchPayload(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD);
targetOffset = endOffset;
effectiveInstructions.set(i, new ImmutableInstruction31t(opcode,
((Instruction31t)instruction).getRegisterA(), targetOffset-codeOffset));
effectiveInstructions.add(payloadInstruction);
endOffset += payloadInstruction.getCodeUnits();
}
sparseSwitchMap.append(targetOffset, codeOffset);
}
}
}
}catch (Exception ex) {
} catch (Exception ex) {
String methodString;
try {
methodString = ReferenceUtil.getMethodDescriptor(method);
@ -216,7 +245,36 @@ public class MethodDefinition {
writer.write(".end method\n");
}
public int findSwitchPayload(int targetOffset, Opcode type) {
public Instruction findSwitchPayload(int targetOffset, Opcode type) {
int targetIndex;
try {
targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset);
} catch (InvalidInstructionOffset ex) {
throw new InvalidSwitchPayload(targetOffset);
}
//TODO: does dalvik let you pad with multiple nops?
//TODO: does dalvik let a switch instruction point to a non-payload instruction?
Instruction instruction = instructions.get(targetIndex);
if (instruction.getOpcode() != type) {
// maybe it's pointing to a NOP padding instruction. Look at the next instruction
if (instruction.getOpcode() == Opcode.NOP) {
targetIndex += 1;
if (targetIndex < instructions.size()) {
instruction = instructions.get(targetIndex);
if (instruction.getOpcode() == type) {
return instruction;
}
}
}
throw new InvalidSwitchPayload(targetOffset);
} else {
return instruction;
}
}
public int findPayloadOffset(int targetOffset, Opcode type) {
int targetIndex;
try {
targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset);
@ -343,15 +401,16 @@ public class MethodDefinition {
private void addInstructionMethodItems(List<MethodItem> methodItems) {
int currentCodeAddress = 0;
for (int i=0; i<instructions.size(); i++) {
Instruction instruction = instructions.get(i);
for (int i=0; i<effectiveInstructions.size(); i++) {
Instruction instruction = effectiveInstructions.get(i);
MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
currentCodeAddress, instruction);
methodItems.add(methodItem);
if (i != instructions.size() - 1) {
if (i != effectiveInstructions.size() - 1) {
methodItems.add(new BlankMethodItem(currentCodeAddress));
}
@ -386,7 +445,7 @@ public class MethodDefinition {
if (methodReference != null &&
SyntheticAccessorResolver.looksLikeSyntheticAccessor(methodReference.getName())) {
SyntheticAccessorResolver.AccessedMember accessedMember =
AccessedMember accessedMember =
classDef.options.syntheticAccessorResolver.getAccessedMember(methodReference);
if (accessedMember != null) {
methodItems.add(new SyntheticAccessCommentMethodItem(accessedMember, currentCodeAddress));

View File

@ -31,6 +31,7 @@
package org.jf.baksmali;
import com.google.common.io.ByteStreams;
import junit.framework.Assert;
import org.antlr.runtime.RecognitionException;
@ -40,7 +41,9 @@ import org.jf.smali.SmaliTestUtils;
import org.jf.util.IndentingWriter;
import org.jf.util.TextUtils;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
public class BaksmaliTestUtils {
@ -50,24 +53,9 @@ public class BaksmaliTestUtils {
ClassDef classDef = SmaliTestUtils.compileSmali(source, options.apiLevel,
options.experimental);
StringWriter stringWriter = new StringWriter();
IndentingWriter writer = new IndentingWriter(stringWriter);
ClassDefinition classDefinition = new ClassDefinition(options, classDef);
classDefinition.writeTo(writer);
writer.close();
// Remove unnecessary whitespace and optionally strip all comments from smali file
String normalizedExpected = expected;
if (stripComments) {
normalizedExpected = TextUtils.stripComments(normalizedExpected);
}
normalizedExpected = TextUtils.normalizeWhitespace(normalizedExpected);
String normalizedActual = stringWriter.toString();
if (stripComments) {
normalizedActual = TextUtils.stripComments(normalizedActual);
}
normalizedActual = TextUtils.normalizeWhitespace(normalizedActual);
String normalizedActual = getNormalizedSmali(classDef, options, stripComments);
String normalizedExpected = normalizeSmali(expected, stripComments);
// Assert that normalized strings are now equal
Assert.assertEquals(normalizedExpected, normalizedActual);
@ -84,6 +72,48 @@ public class BaksmaliTestUtils {
assertSmaliCompiledEquals(source, expected, options);
}
@Nonnull
public static String normalizeSmali(@Nonnull String smaliText, boolean stripComments) {
if (stripComments) {
smaliText = TextUtils.stripComments(smaliText);
}
return TextUtils.normalizeWhitespace(smaliText);
}
@Nonnull
public static String getNormalizedSmali(@Nonnull ClassDef classDef, @Nonnull baksmaliOptions options,
boolean stripComments)
throws IOException {
StringWriter stringWriter = new StringWriter();
IndentingWriter writer = new IndentingWriter(stringWriter);
ClassDefinition classDefinition = new ClassDefinition(options, classDef);
classDefinition.writeTo(writer);
writer.close();
return normalizeSmali(stringWriter.toString(), stripComments);
}
@Nonnull
public static byte[] readResourceBytesFully(@Nonnull String fileName) throws IOException {
InputStream smaliStream = RoundtripTest.class.getClassLoader().
getResourceAsStream(fileName);
if (smaliStream == null) {
org.junit.Assert.fail("Could not load " + fileName);
}
return ByteStreams.toByteArray(smaliStream);
}
@Nonnull
public static String readResourceFully(@Nonnull String fileName) throws IOException {
return readResourceFully(fileName, "UTF-8");
}
@Nonnull
public static String readResourceFully(@Nonnull String fileName, @Nonnull String encoding)
throws IOException {
return new String(readResourceBytesFully(fileName), encoding);
}
// Static helpers class; do not instantiate.
private BaksmaliTestUtils() { throw new AssertionError(); }
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jf.baksmali;
import com.google.common.collect.Iterables;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.iface.ClassDef;
import org.junit.Assert;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
/**
* A base test class for performing a disassembly on a dex file and verifying the results
*
* The test accepts a single-class dex file as input, disassembles it, and verifies that
* the result equals a known-good output smali file.
*
* By default, the input and output files should be resources at [testDir]/[testName]Input.dex
* and [testDir]/[testName]Output.smali respectively
*/
public class DisassemblyTest {
protected final String testDir;
protected DisassemblyTest(@Nonnull String testDir) {
this.testDir = testDir;
}
protected DisassemblyTest() {
this.testDir = this.getClass().getSimpleName();
}
@Nonnull
protected String getInputFilename(@Nonnull String testName) {
return String.format("%s%s%sInput.dex", testDir, File.separatorChar, testName);
}
@Nonnull
protected String getOutputFilename(@Nonnull String testName) {
return String.format("%s%s%sOutput.smali", testDir, File.separatorChar, testName);
}
protected void runTest(@Nonnull String testName) {
runTest(testName, new baksmaliOptions());
}
protected void runTest(@Nonnull String testName, @Nonnull baksmaliOptions options) {
try {
// Load file from resources as a stream
String inputFilename = getInputFilename(testName);
byte[] inputBytes = BaksmaliTestUtils.readResourceBytesFully(getInputFilename(testName));
DexBackedDexFile inputDex = new DexBackedDexFile(new Opcodes(options.apiLevel, false), inputBytes);
Assert.assertEquals(1, inputDex.getClassCount());
ClassDef inputClass = Iterables.getFirst(inputDex.getClasses(), null);
Assert.assertNotNull(inputClass);
String input = BaksmaliTestUtils.getNormalizedSmali(inputClass, options, true);
String output;
if (getOutputFilename(testName).equals(inputFilename)) {
output = input;
} else {
output = BaksmaliTestUtils.readResourceFully(getOutputFilename(testName));
}
output = BaksmaliTestUtils.normalizeSmali(output, true);
// Run smali, baksmali, and then compare strings are equal (minus comments/whitespace)
Assert.assertEquals(output, input);
} catch (IOException ex) {
Assert.fail();
}
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jf.baksmali;
import org.junit.Test;
public class MultiSwitchTest extends DisassemblyTest {
@Test
public void testMultiSwitch() {
runTest("MultiSwitch");
}
}

View File

@ -31,14 +31,12 @@
package org.jf.baksmali;
import com.google.common.io.ByteStreams;
import org.antlr.runtime.RecognitionException;
import org.junit.Assert;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
* A base test class for performing a roundtrip assembly/disassembly
@ -78,12 +76,12 @@ public abstract class RoundtripTest {
try {
// Load file from resources as a stream
String inputFilename = getInputFilename(testName);
String input = readResourceFully(getInputFilename(testName));
String input = BaksmaliTestUtils.readResourceFully(getInputFilename(testName));
String output;
if (getOutputFilename(testName).equals(inputFilename)) {
output = input;
} else {
output = readResourceFully(getOutputFilename(testName));
output = BaksmaliTestUtils.readResourceFully(getOutputFilename(testName));
}
// Run smali, baksmali, and then compare strings are equal (minus comments/whitespace)
@ -94,21 +92,4 @@ public abstract class RoundtripTest {
Assert.fail();
}
}
@Nonnull
public static String readResourceFully(@Nonnull String fileName) throws IOException {
return readResourceFully(fileName, "UTF-8");
}
@Nonnull
public static String readResourceFully(@Nonnull String fileName, @Nonnull String encoding)
throws IOException {
InputStream smaliStream = RoundtripTest.class.getClassLoader().
getResourceAsStream(fileName);
if (smaliStream == null) {
Assert.fail("Could not load " + fileName);
}
return new String(ByteStreams.toByteArray(smaliStream), encoding);
}
}

View File

@ -0,0 +1,72 @@
.class public LMultiSwitch;
.super Ljava/lang/Object;
.source "Format31t.smali"
.method public multi-packed-switch()V
.registers 1
const p0, 0xc
packed-switch p0, :pswitch_data_12
goto :goto_b
:pswitch_7
return-void
:pswitch_8
return-void
:pswitch_9
return-void
:pswitch_a
return-void
:goto_b
packed-switch p0, :pswitch_data_12
nop
return-void
:pswitch_f
return-void
:pswitch_10
return-void
:pswitch_11
return-void
:pswitch_12
:pswitch_data_12
.packed-switch 0xa
:pswitch_7
:pswitch_8
:pswitch_9
:pswitch_a
.end packed-switch
.end method
.method public multi-sparse-switch()V
.registers 1
const p0, 0xd
sparse-switch p0, :sswitch_data_12
goto :goto_b
:sswitch_7
return-void
:sswitch_8
return-void
:sswitch_9
return-void
:sswitch_a
return-void
:goto_b
sparse-switch p0, :sswitch_data_12
nop
return-void
:sswitch_f
return-void
:sswitch_10
return-void
:sswitch_11
return-void
:sswitch_12
:sswitch_data_12
.sparse-switch
0xa -> :sswitch_7
0xf -> :sswitch_9
0x14 -> :sswitch_8
0x63 -> :sswitch_a
.end sparse-switch
.end method

View File

@ -0,0 +1,119 @@
.class public LMultiSwitch;
.super Ljava/lang/Object;
.source "Format31t.smali"
# virtual methods
.method public multi-packed-switch()V
.registers 1
const p0, 0xc
packed-switch p0, :pswitch_data_14
goto :goto_b
:pswitch_7
return-void
:pswitch_8
return-void
:pswitch_9
return-void
:pswitch_a
return-void
:goto_b
packed-switch p0, :pswitch_data_20
nop
:pswitch_f
return-void
:pswitch_10
return-void
:pswitch_11
return-void
:pswitch_12
return-void
nop
:pswitch_data_14
.packed-switch 0xa
:pswitch_7
:pswitch_8
:pswitch_9
:pswitch_a
.end packed-switch
:pswitch_data_20
.packed-switch 0xa
:pswitch_f
:pswitch_10
:pswitch_11
:pswitch_12
.end packed-switch
.end method
.method public multi-sparse-switch()V
.registers 1
const p0, 0xd
sparse-switch p0, :sswitch_data_14
goto :goto_b
:sswitch_7
return-void
:sswitch_8
return-void
:sswitch_9
return-void
:sswitch_a
return-void
:goto_b
sparse-switch p0, :sswitch_data_26
nop
:sswitch_f
return-void
:sswitch_10
return-void
:sswitch_11
return-void
:sswitch_12
return-void
nop
:sswitch_data_14
.sparse-switch
0xa -> :sswitch_7
0xf -> :sswitch_9
0x14 -> :sswitch_8
0x63 -> :sswitch_a
.end sparse-switch
:sswitch_data_26
.sparse-switch
0xa -> :sswitch_f
0xf -> :sswitch_11
0x14 -> :sswitch_10
0x63 -> :sswitch_12
.end sparse-switch
.end method