From aa7e507bac4c8900e01d22f7913051455010089b Mon Sep 17 00:00:00 2001 From: "JesusFreke@JesusFreke.com" Date: Mon, 18 May 2009 06:45:09 +0000 Subject: [PATCH] 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 --- .../antlr3/org/JesusFreke/smali/smaliLexer.g | 10 ++ .../antlr3/org/JesusFreke/smali/smaliParser.g | 9 +- .../org/JesusFreke/smali/smaliTreeWalker.g | 33 +++- .../java/org/JesusFreke/dexlib/CodeItem.java | 10 +- .../org/JesusFreke/dexlib/DebugInfoItem.java | 30 +++- .../JesusFreke/dexlib/debug/AdvanceLine.java | 12 +- .../JesusFreke/dexlib/debug/AdvancePC.java | 12 +- .../dexlib/util/DebugInfoBuilder.java | 151 ++++++++++++++++++ src/test/resources/examples/HelloWorld2.smali | 20 ++- 9 files changed, 270 insertions(+), 17 deletions(-) create mode 100644 src/main/java/org/JesusFreke/dexlib/util/DebugInfoBuilder.java diff --git a/src/main/antlr3/org/JesusFreke/smali/smaliLexer.g b/src/main/antlr3/org/JesusFreke/smali/smaliLexer.g index 2c0245c6..cd774499 100644 --- a/src/main/antlr3/org/JesusFreke/smali/smaliLexer.g +++ b/src/main/antlr3/org/JesusFreke/smali/smaliLexer.g @@ -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 diff --git a/src/main/antlr3/org/JesusFreke/smali/smaliParser.g b/src/main/antlr3/org/JesusFreke/smali/smaliParser.g index 73e01541..bbeea421 100644 --- a/src/main/antlr3/org/JesusFreke/smali/smaliParser.g +++ b/src/main/antlr3/org/JesusFreke/smali/smaliParser.g @@ -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)))}); diff --git a/src/main/antlr3/org/JesusFreke/smali/smaliTreeWalker.g b/src/main/antlr3/org/JesusFreke/smali/smaliTreeWalker.g index d981bf2b..d5c3bb15 100644 --- a/src/main/antlr3/org/JesusFreke/smali/smaliTreeWalker.g +++ b/src/main/antlr3/org/JesusFreke/smali/smaliTreeWalker.g @@ -286,13 +286,15 @@ method returns[ClassDataItem.EncodedMethod encodedMethod] HashMap labels; TryListBuilder tryList; int currentAddress; + DebugInfoBuilder debugInfo; } : { $method::labels = new HashMap(); $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 tries = temp.first; List 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] diff --git a/src/main/java/org/JesusFreke/dexlib/CodeItem.java b/src/main/java/org/JesusFreke/dexlib/CodeItem.java index cbd3c2e2..f005ea77 100644 --- a/src/main/java/org/JesusFreke/dexlib/CodeItem.java +++ b/src/main/java/org/JesusFreke/dexlib/CodeItem.java @@ -81,7 +81,13 @@ public class CodeItem extends OffsettedItem { - public CodeItem(final DexFile dexFile, int registersCount, int inArguments, ArrayList instructions, List tries, List handlers) { + public CodeItem(final DexFile dexFile, + int registersCount, + int inArguments, + ArrayList instructions, + DebugInfoItem debugInfo, + List tries, + List handlers) { super(-1); this.instructionList = new ArrayList(instructions); @@ -102,7 +108,7 @@ public class CodeItem extends OffsettedItem { this.inArgumentCount = new ShortIntegerField(inArguments), this.outArgumentCount = new ShortIntegerField(instructionListField.getOutArguments()), this.triesCount = new ListSizeField(tryItems, new ShortIntegerField(0)), - this.debugInfo = new OffsettedItemReference(dexFile, null, new IntegerField()), + this.debugInfo = new OffsettedItemReference(dexFile, debugInfo, new IntegerField()), this.instructionsSize = new IntegerField(instructionListField.getInstructionWordCount()), instructionListField, this.padding = new PaddingField(), diff --git a/src/main/java/org/JesusFreke/dexlib/DebugInfoItem.java b/src/main/java/org/JesusFreke/dexlib/DebugInfoItem.java index e15c33f7..1c32d200 100644 --- a/src/main/java/org/JesusFreke/dexlib/DebugInfoItem.java +++ b/src/main/java/org/JesusFreke/dexlib/DebugInfoItem.java @@ -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 { private final Field[] fields; @@ -45,21 +46,42 @@ public class DebugInfoItem extends OffsettedItem { private ArrayList instructionFields = new ArrayList(); + private final Leb128Field lineStartField; + private final ListSizeField parameterNamesSizeField; + private final FieldListField> 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>(parameterNames) { + lineStartField = new Leb128Field(), + parameterNamesSizeField = new ListSizeField(parameterNames, new Leb128Field()), + parameterNamesField = new FieldListField>(parameterNames) { protected IndexedItemReference make() { return new IndexedItemReference(dexFile.StringIdsSection, new Leb128p1Field()); } }, - new DebugInstructionList(dexFile) + debugInstructionListField = new DebugInstructionList(dexFile) }; } + public DebugInfoItem(final DexFile dexFile, + int lineStart, + List parameterNames, + List debugInstructions) { + this(dexFile, 0); + + this.lineStartField.cacheValue(lineStart); + + for (StringIdItem parameterName: parameterNames) { + this.parameterNames.add(new IndexedItemReference(dexFile, parameterName, + new Leb128p1Field())); + } + + this.instructionFields.addAll(debugInstructions); + } + protected int getAlignment() { return 1; } diff --git a/src/main/java/org/JesusFreke/dexlib/debug/AdvanceLine.java b/src/main/java/org/JesusFreke/dexlib/debug/AdvanceLine.java index 01b984dc..83b8dc77 100644 --- a/src/main/java/org/JesusFreke/dexlib/debug/AdvanceLine.java +++ b/src/main/java/org/JesusFreke/dexlib/debug/AdvanceLine.java @@ -36,13 +36,21 @@ import org.JesusFreke.dexlib.SignedLeb128Field; public class AdvanceLine extends CompositeField implements DebugInstruction { 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; } diff --git a/src/main/java/org/JesusFreke/dexlib/debug/AdvancePC.java b/src/main/java/org/JesusFreke/dexlib/debug/AdvancePC.java index 887bdf16..89881346 100644 --- a/src/main/java/org/JesusFreke/dexlib/debug/AdvancePC.java +++ b/src/main/java/org/JesusFreke/dexlib/debug/AdvancePC.java @@ -33,13 +33,21 @@ import org.JesusFreke.dexlib.*; public class AdvancePC extends CompositeField implements DebugInstruction { 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; } diff --git a/src/main/java/org/JesusFreke/dexlib/util/DebugInfoBuilder.java b/src/main/java/org/JesusFreke/dexlib/util/DebugInfoBuilder.java new file mode 100644 index 00000000..d18f5d57 --- /dev/null +++ b/src/main/java/org/JesusFreke/dexlib/util/DebugInfoBuilder.java @@ -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 parameterNames = new ArrayList(); + private ArrayList events = new ArrayList(); + 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 debugInstructions = new ArrayList(); + ArrayList parameterNameReferences = new ArrayList(); + + 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 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 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)); + } + } +} diff --git a/src/test/resources/examples/HelloWorld2.smali b/src/test/resources/examples/HelloWorld2.smali index efb8ca49..fe1ae031 100644 --- a/src/test/resources/examples/HelloWorld2.smali +++ b/src/test/resources/examples/HelloWorld2.smali @@ -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/()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/(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;