mirror of
https://github.com/revanced/smali.git
synced 2025-05-11 11:54:29 +02:00
Added initial support for method level debug info (currently line info only)
git-svn-id: https://smali.googlecode.com/svn/trunk@44 55b6fa8a-2a1e-11de-a435-ffa8d773f76a
This commit is contained in:
parent
51b326d5a6
commit
aa7e507bac
@ -464,6 +464,11 @@ CATCH_PHRASE
|
||||
WS 'using' WS
|
||||
(LABEL_EMIT | OFFSET_EMIT);
|
||||
|
||||
LINE_PHRASE
|
||||
: LINE_DIRECTIVE_EMIT
|
||||
WS
|
||||
INTEGRAL_LITERAL_EMITCHILD;
|
||||
|
||||
|
||||
//TODO: add support for both relative and absolute offsets?
|
||||
fragment OFFSET_EMIT
|
||||
@ -551,6 +556,11 @@ fragment CATCH_DIRECTIVE_EMIT
|
||||
fragment CATCH_DIRECTIVE
|
||||
: '.catch';
|
||||
|
||||
fragment LINE_DIRECTIVE_EMIT
|
||||
: LINE_DIRECTIVE {emit($LINE_DIRECTIVE, LINE_DIRECTIVE);};
|
||||
fragment LINE_DIRECTIVE
|
||||
: '.line';
|
||||
|
||||
fragment REGISTER_EMIT
|
||||
: REGISTER {emit($REGISTER, REGISTER);};
|
||||
fragment REGISTER
|
||||
|
@ -65,6 +65,8 @@ tokens {
|
||||
I_CATCH;
|
||||
I_CATCHES;
|
||||
I_CATCH_ADDRESS;
|
||||
I_LINE;
|
||||
I_LINES;
|
||||
I_STATEMENTS;
|
||||
I_STATEMENT_FORMAT10t;
|
||||
I_STATEMENT_FORMAT10x;
|
||||
@ -213,13 +215,18 @@ statements
|
||||
: {$statements::currentAddress = 0;}
|
||||
( instruction {$statements::currentAddress += $instruction.size/2;}
|
||||
| catch_directive
|
||||
| line_directive
|
||||
| label)*
|
||||
-> ^(I_LABELS label*) ^(I_STATEMENTS instruction*) ^(I_CATCHES catch_directive*);
|
||||
-> ^(I_LABELS label*) ^(I_STATEMENTS instruction*) ^(I_CATCHES catch_directive*) ^(I_LINES line_directive*);
|
||||
|
||||
catch_directive
|
||||
: CATCH_DIRECTIVE field_type_descriptor from=offset_or_label to=offset_or_label using=offset_or_label
|
||||
-> ^(I_CATCH[$start, "I_CATCH"] I_CATCH_ADDRESS[$start, Integer.toString($statements::currentAddress)] field_type_descriptor $from $to $using)
|
||||
;
|
||||
|
||||
line_directive
|
||||
: LINE_DIRECTIVE integral_literal -> ^(I_LINE integral_literal {new CommonTree(new CommonToken(INTEGER_LITERAL,Integer.toString($statements::currentAddress)))});
|
||||
|
||||
label
|
||||
: LABEL -> ^(I_LABEL LABEL {new CommonTree(new CommonToken(INTEGER_LITERAL,Integer.toString($statements::currentAddress)))});
|
||||
|
||||
|
@ -286,13 +286,15 @@ method returns[ClassDataItem.EncodedMethod encodedMethod]
|
||||
HashMap<String, Integer> labels;
|
||||
TryListBuilder tryList;
|
||||
int currentAddress;
|
||||
DebugInfoBuilder debugInfo;
|
||||
}
|
||||
: {
|
||||
$method::labels = new HashMap<String, Integer>();
|
||||
$method::tryList = new TryListBuilder();
|
||||
$method::currentAddress = 0;
|
||||
$method::debugInfo = new DebugInfoBuilder();
|
||||
}
|
||||
^(I_METHOD method_name_and_prototype access_list registers_directive labels statements catches)
|
||||
^(I_METHOD method_name_and_prototype access_list registers_directive labels statements catches lines)
|
||||
{
|
||||
MethodIdItem methodIdItem = $method_name_and_prototype.methodIdItem;
|
||||
int registers = $registers_directive.registers;
|
||||
@ -304,7 +306,15 @@ method returns[ClassDataItem.EncodedMethod encodedMethod]
|
||||
List<CodeItem.TryItem> tries = temp.first;
|
||||
List<CodeItem.EncodedCatchHandler> handlers = temp.second;
|
||||
|
||||
CodeItem codeItem = new CodeItem(dexFile, registers, methodIdItem.getParameterWordCount(isStatic), instructions, tries, handlers);
|
||||
DebugInfoItem debugInfoItem = $method::debugInfo.encodeDebugInfo(dexFile);
|
||||
|
||||
CodeItem codeItem = new CodeItem(dexFile,
|
||||
registers,
|
||||
methodIdItem.getParameterWordCount(isStatic),
|
||||
instructions,
|
||||
debugInfoItem,
|
||||
tries,
|
||||
handlers);
|
||||
|
||||
$encodedMethod = new ClassDataItem.EncodedMethod(dexFile, methodIdItem, access, codeItem);
|
||||
};
|
||||
@ -376,6 +386,15 @@ catch_directive
|
||||
|
||||
$method::tryList.addHandler(type, startAddress, endAddress, handlerAddress);
|
||||
};
|
||||
|
||||
lines
|
||||
: ^(I_LINES line*);
|
||||
|
||||
line
|
||||
: ^(I_LINE integral_literal integer_literal)
|
||||
{
|
||||
$method::debugInfo.addLine($integer_literal.value, $integral_literal.value);
|
||||
};
|
||||
|
||||
labels
|
||||
: ^(I_LABELS label_def*);
|
||||
@ -844,6 +863,16 @@ short_integral_literal returns[short value]
|
||||
}
|
||||
| short_literal {$value = $short_literal.value;}
|
||||
| byte_literal {$value = $byte_literal.value;};
|
||||
|
||||
integral_literal returns[int value]
|
||||
: long_literal
|
||||
{
|
||||
literalTools.checkInt($long_literal.value);
|
||||
$value = (short)$long_literal.value;
|
||||
}
|
||||
| integer_literal {$value = (short)$integer_literal.value;}
|
||||
| short_literal {$value = $short_literal.value;}
|
||||
| byte_literal {$value = $byte_literal.value;};
|
||||
|
||||
|
||||
integer_literal returns[int value]
|
||||
|
@ -81,7 +81,13 @@ public class CodeItem extends OffsettedItem<CodeItem> {
|
||||
|
||||
|
||||
|
||||
public CodeItem(final DexFile dexFile, int registersCount, int inArguments, ArrayList<Instruction> instructions, List<TryItem> tries, List<EncodedCatchHandler> handlers) {
|
||||
public CodeItem(final DexFile dexFile,
|
||||
int registersCount,
|
||||
int inArguments,
|
||||
ArrayList<Instruction> instructions,
|
||||
DebugInfoItem debugInfo,
|
||||
List<TryItem> tries,
|
||||
List<EncodedCatchHandler> handlers) {
|
||||
super(-1);
|
||||
|
||||
this.instructionList = new ArrayList<Instruction>(instructions);
|
||||
@ -102,7 +108,7 @@ public class CodeItem extends OffsettedItem<CodeItem> {
|
||||
this.inArgumentCount = new ShortIntegerField(inArguments),
|
||||
this.outArgumentCount = new ShortIntegerField(instructionListField.getOutArguments()),
|
||||
this.triesCount = new ListSizeField(tryItems, new ShortIntegerField(0)),
|
||||
this.debugInfo = new OffsettedItemReference<DebugInfoItem>(dexFile, null, new IntegerField()),
|
||||
this.debugInfo = new OffsettedItemReference<DebugInfoItem>(dexFile, debugInfo, new IntegerField()),
|
||||
this.instructionsSize = new IntegerField(instructionListField.getInstructionWordCount()),
|
||||
instructionListField,
|
||||
this.padding = new PaddingField(),
|
||||
|
@ -36,6 +36,7 @@ import org.JesusFreke.dexlib.util.Output;
|
||||
import org.JesusFreke.dexlib.util.Input;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DebugInfoItem extends OffsettedItem<DebugInfoItem> {
|
||||
private final Field[] fields;
|
||||
@ -45,21 +46,42 @@ public class DebugInfoItem extends OffsettedItem<DebugInfoItem> {
|
||||
|
||||
private ArrayList<DebugInstruction> instructionFields = new ArrayList<DebugInstruction>();
|
||||
|
||||
private final Leb128Field lineStartField;
|
||||
private final ListSizeField parameterNamesSizeField;
|
||||
private final FieldListField<IndexedItemReference<StringIdItem>> parameterNamesField;
|
||||
private final DebugInstructionList debugInstructionListField;
|
||||
|
||||
public DebugInfoItem(final DexFile dexFile, int offset) {
|
||||
super(offset);
|
||||
|
||||
fields = new Field[] {
|
||||
new Leb128Field(),
|
||||
new ListSizeField(parameterNames, new Leb128Field()),
|
||||
new FieldListField<IndexedItemReference<StringIdItem>>(parameterNames) {
|
||||
lineStartField = new Leb128Field(),
|
||||
parameterNamesSizeField = new ListSizeField(parameterNames, new Leb128Field()),
|
||||
parameterNamesField = new FieldListField<IndexedItemReference<StringIdItem>>(parameterNames) {
|
||||
protected IndexedItemReference<StringIdItem> make() {
|
||||
return new IndexedItemReference<StringIdItem>(dexFile.StringIdsSection, new Leb128p1Field());
|
||||
}
|
||||
},
|
||||
new DebugInstructionList(dexFile)
|
||||
debugInstructionListField = new DebugInstructionList(dexFile)
|
||||
};
|
||||
}
|
||||
|
||||
public DebugInfoItem(final DexFile dexFile,
|
||||
int lineStart,
|
||||
List<StringIdItem> parameterNames,
|
||||
List<DebugInstruction> debugInstructions) {
|
||||
this(dexFile, 0);
|
||||
|
||||
this.lineStartField.cacheValue(lineStart);
|
||||
|
||||
for (StringIdItem parameterName: parameterNames) {
|
||||
this.parameterNames.add(new IndexedItemReference<StringIdItem>(dexFile, parameterName,
|
||||
new Leb128p1Field()));
|
||||
}
|
||||
|
||||
this.instructionFields.addAll(debugInstructions);
|
||||
}
|
||||
|
||||
protected int getAlignment() {
|
||||
return 1;
|
||||
}
|
||||
|
@ -36,13 +36,21 @@ import org.JesusFreke.dexlib.SignedLeb128Field;
|
||||
public class AdvanceLine extends CompositeField<AdvanceLine> implements DebugInstruction<AdvanceLine> {
|
||||
private final Field[] fields;
|
||||
|
||||
private final ByteField opcodeField;
|
||||
private final SignedLeb128Field lineDeltaField;
|
||||
|
||||
public AdvanceLine() {
|
||||
fields = new Field[] {
|
||||
new ByteField((byte)0x02),
|
||||
new SignedLeb128Field()
|
||||
opcodeField = new ByteField((byte)0x02),
|
||||
lineDeltaField = new SignedLeb128Field()
|
||||
};
|
||||
}
|
||||
|
||||
public AdvanceLine(int lineDelta) {
|
||||
this();
|
||||
lineDeltaField.cacheValue(lineDelta);
|
||||
}
|
||||
|
||||
protected Field[] getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
@ -33,13 +33,21 @@ import org.JesusFreke.dexlib.*;
|
||||
public class AdvancePC extends CompositeField<AdvancePC> implements DebugInstruction<AdvancePC> {
|
||||
private final Field[] fields;
|
||||
|
||||
private final ByteField opcodeField;
|
||||
private final Leb128Field addressDeltaField;
|
||||
|
||||
public AdvancePC() {
|
||||
fields = new Field[] {
|
||||
new ByteField((byte)0x01),
|
||||
new Leb128Field()
|
||||
opcodeField = new ByteField((byte)0x01),
|
||||
addressDeltaField = new Leb128Field()
|
||||
};
|
||||
}
|
||||
|
||||
public AdvancePC(int addressDelta) {
|
||||
this();
|
||||
addressDeltaField.cacheValue(addressDelta);
|
||||
}
|
||||
|
||||
protected Field[] getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
151
src/main/java/org/JesusFreke/dexlib/util/DebugInfoBuilder.java
Normal file
151
src/main/java/org/JesusFreke/dexlib/util/DebugInfoBuilder.java
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* [The "BSD licence"]
|
||||
* Copyright (c) 2009 Ben Gruver
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. 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.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.JesusFreke.dexlib.util;
|
||||
|
||||
import org.JesusFreke.dexlib.debug.*;
|
||||
import org.JesusFreke.dexlib.DexFile;
|
||||
import org.JesusFreke.dexlib.DebugInfoItem;
|
||||
import org.JesusFreke.dexlib.StringIdItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class is intended to provide an easy to use container to build up a method's debug info. You can easily add
|
||||
* an "event" at a specific address, where an event is something like a line number, start/end local, etc.
|
||||
* The events must be added such that the code addresses increase monotonically. This matches how a parser would
|
||||
* generally behave, and is intended to increase performance.
|
||||
*/
|
||||
public class DebugInfoBuilder
|
||||
{
|
||||
//TODO: take a look at the debug bytecode generation logic in dx, and make sure that this does the same thing
|
||||
//(in the interest of being able to exactly reproduce a given dx-generated dex file)
|
||||
|
||||
private static final int LINE_BASE = -4;
|
||||
private static final int LINE_RANGE = 15;
|
||||
private static final int FIRST_SPECIAL = 0x0a;
|
||||
|
||||
private int lineStart = 0;
|
||||
private ArrayList<String> parameterNames = new ArrayList<String>();
|
||||
private ArrayList<Event> events = new ArrayList<Event>();
|
||||
private int lastAddress = 0;
|
||||
|
||||
private boolean hasData;
|
||||
|
||||
private int currentAddress;
|
||||
private int currentLine;
|
||||
|
||||
public DebugInfoBuilder() {
|
||||
}
|
||||
|
||||
public void addLine(int address, int line) {
|
||||
hasData = true;
|
||||
|
||||
if (lastAddress > address) {
|
||||
throw new RuntimeException("Cannot add an event with an address before the address of the prior event");
|
||||
}
|
||||
|
||||
if (lineStart == 0) {
|
||||
lineStart = line;
|
||||
}
|
||||
|
||||
events.add(new LineEvent(address, line));
|
||||
}
|
||||
|
||||
public DebugInfoItem encodeDebugInfo(DexFile dexFile) {
|
||||
if (!hasData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ArrayList<DebugInstruction> debugInstructions = new ArrayList<DebugInstruction>();
|
||||
ArrayList<StringIdItem> parameterNameReferences = new ArrayList<StringIdItem>();
|
||||
|
||||
if (lineStart == 0) {
|
||||
lineStart = 1;
|
||||
}
|
||||
|
||||
currentLine = lineStart;
|
||||
|
||||
for (Event event: events) {
|
||||
event.emit(debugInstructions);
|
||||
}
|
||||
debugInstructions.add(new EndSequence());
|
||||
|
||||
for (String parameterName: parameterNames) {
|
||||
parameterNameReferences.add(new StringIdItem(dexFile, parameterName));
|
||||
}
|
||||
|
||||
return new DebugInfoItem(dexFile, lineStart, parameterNameReferences, debugInstructions);
|
||||
}
|
||||
|
||||
private interface Event
|
||||
{
|
||||
int getAddress();
|
||||
void emit(List<DebugInstruction> debugInstructions);
|
||||
}
|
||||
|
||||
private class LineEvent implements Event
|
||||
{
|
||||
private final int address;
|
||||
private final int line;
|
||||
|
||||
public LineEvent(int address, int line) {
|
||||
this.address = address;
|
||||
this.line = line;
|
||||
}
|
||||
|
||||
public int getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void emit(List<DebugInstruction> debugInstructions) {
|
||||
int lineDelta = line - currentLine;
|
||||
int addressDelta = address - currentAddress;
|
||||
|
||||
if (lineDelta < -4 || lineDelta > 10) {
|
||||
debugInstructions.add(new AdvanceLine(lineDelta));
|
||||
lineDelta = 0;
|
||||
}
|
||||
if (lineDelta < 2 && addressDelta > 16 || lineDelta > 1 && addressDelta > 15) {
|
||||
debugInstructions.add(new AdvancePC(addressDelta));
|
||||
addressDelta = 0;
|
||||
}
|
||||
|
||||
debugInstructions.add(new SpecialOpcode(calculateSpecialOpcode(lineDelta, addressDelta)));
|
||||
|
||||
|
||||
currentAddress = address;
|
||||
currentLine = line;
|
||||
}
|
||||
|
||||
private byte calculateSpecialOpcode(int lineDelta, int addressDelta) {
|
||||
return (byte)(FIRST_SPECIAL + (addressDelta * LINE_RANGE) + (lineDelta - LINE_BASE));
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@
|
||||
|
||||
|
||||
.method public println(Ljava/lang/String;)V
|
||||
.registers 1
|
||||
.registers 2
|
||||
|
||||
return-void
|
||||
.end method
|
||||
@ -20,7 +20,7 @@
|
||||
|
||||
|
||||
.method public onAccountsUpdated([Ljava/lang/String;)V
|
||||
.registers 1
|
||||
.registers 2
|
||||
|
||||
return-void
|
||||
.end method
|
||||
@ -360,6 +360,8 @@ SparseSwitch:
|
||||
.method public testTry()Ljava/lang/String;
|
||||
.registers 2
|
||||
|
||||
.line 4
|
||||
|
||||
;0
|
||||
const-string v0, "This shouldn't be displayed!"
|
||||
|
||||
@ -368,6 +370,8 @@ SparseSwitch:
|
||||
tryStart:
|
||||
new-instance v1, Ljava/lang/Exception;
|
||||
|
||||
.line 2
|
||||
|
||||
;4
|
||||
|
||||
invoke-direct {v1}, java/lang/Exception/<init>()V
|
||||
@ -379,12 +383,16 @@ SparseSwitch:
|
||||
nop
|
||||
nop
|
||||
|
||||
;8
|
||||
.line 5
|
||||
|
||||
;10
|
||||
tryEnd:
|
||||
|
||||
return-object v0
|
||||
|
||||
;9
|
||||
.line 90
|
||||
|
||||
;11
|
||||
|
||||
.catch Ljava/lang/Exception; from tryStart: to tryEnd: using handler:
|
||||
|
||||
@ -399,6 +407,8 @@ SparseSwitch:
|
||||
.method public onCreate(Landroid/os/Bundle;)V
|
||||
.registers 6
|
||||
|
||||
.line 1
|
||||
|
||||
invoke-super {v4,v5}, android/app/Activity/onCreate(Landroid/os/Bundle;)V
|
||||
|
||||
const-string v3, "\n"
|
||||
@ -406,6 +416,8 @@ SparseSwitch:
|
||||
new-instance v0, Landroid/widget/TextView;
|
||||
invoke-direct {v0,v4}, android/widget/TextView/<init>(Landroid/content/Context;)V
|
||||
|
||||
.line 3
|
||||
|
||||
iget-object v1, v4, org/JesusFreke/HelloWorld2/HelloWorld2/helloWorld Ljava/lang/String;
|
||||
|
||||
invoke-virtual {v1, v3}, java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String;
|
||||
|
Loading…
x
Reference in New Issue
Block a user