/* * [The "BSD licence"] * Copyright (c) 2010 Ben Gruver (JesusFreke) * 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.jf.dexlib.Util; import org.jf.dexlib.*; 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 { 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() { } private void checkAddress(int address) { if (lastAddress > address) { throw new RuntimeException("Cannot add an event with an address before the address of the prior event"); } } public void addParameterName(String parameterName) { if (parameterName != null) { hasData = true; } parameterNames.add(parameterName); } public void addLine(int address, int line) { hasData = true; checkAddress(address); if (lineStart == 0) { lineStart = line; } events.add(new LineEvent(address, line)); } public void addLocal(int address, int registerNumber, String localName, String localType) { hasData = true; checkAddress(address); events.add(new StartLocalEvent(address, registerNumber, localName, localType)); } public void addLocalExtended(int address, int registerNumber, String localName, String localType, String signature) { hasData = true; checkAddress(address); events.add(new StartLocalExtendedEvent(address, registerNumber, localName, localType, signature)); } public void addEndLocal(int address, int registerNumber) { hasData = true; checkAddress(address); events.add(new EndLocalEvent(address, registerNumber)); } public void addRestartLocal(int address, int registerNumber) { hasData = true; checkAddress(address); events.add(new RestartLocalEvent(address, registerNumber)); } public void addPrologue(int address) { hasData = true; checkAddress(address); events.add(new PrologueEvent(address)); } public void addEpilogue(int address) { hasData = true; checkAddress(address); events.add(new EpilogueEvent(address)); } public void addSetFile(int address, String fileName) { hasData = true; checkAddress(address); events.add(new SetFileEvent(address, fileName)); } public int getParameterNameCount() { return parameterNames.size(); } public DebugInfoItem encodeDebugInfo(DexFile dexFile) { if (!hasData) { return null; } ByteArrayOutput out = new ByteArrayOutput(); StringIdItem[] parameterNamesArray = new StringIdItem[parameterNames.size()]; ArrayList<Item> referencedItems = new ArrayList<Item>(); if (lineStart == 0) { lineStart = 1; } currentLine = lineStart; for (Event event: events) { event.emit(dexFile, out, referencedItems); } emitEndSequence(out); int index = 0; for (String parameterName: parameterNames) { if (parameterName == null) { parameterNamesArray[index++] = null; } else { parameterNamesArray[index++] = StringIdItem.internStringIdItem(dexFile, parameterName); } } Item[] referencedItemsArray = new Item[referencedItems.size()]; referencedItems.toArray(referencedItemsArray); return DebugInfoItem.internDebugInfoItem(dexFile, lineStart, parameterNamesArray, out.toByteArray(), referencedItemsArray); } public static byte calculateSpecialOpcode(int lineDelta, int addressDelta) { return (byte)(FIRST_SPECIAL + (addressDelta * LINE_RANGE) + (lineDelta - LINE_BASE)); } private interface Event { int getAddress(); void emit(DexFile dexFile, Output out, List<Item> referencedItems); } private void emitEndSequence(Output out) { out.writeByte(0); } private void emitAdvancePC(Output out, int address) { int addressDelta = address-currentAddress; if (addressDelta > 0) { out.writeByte(1); out.writeUnsignedLeb128(addressDelta); currentAddress = address; } } private void emitAdvanceLine(Output out, int lineDelta) { out.writeByte(2); out.writeSignedLeb128(lineDelta); } private void emitStartLocal(Output out, int registerNum) { out.writeByte(3); out.writeUnsignedLeb128(registerNum); out.writeByte(1); out.writeByte(1); } private void emitStartLocalExtended(Output out, int registerNum) { out.writeByte(4); out.writeUnsignedLeb128(registerNum); out.writeByte(1); out.writeByte(1); out.writeByte(1); } private void emitEndLocal(Output out, int registerNum) { out.writeByte(5); out.writeUnsignedLeb128(registerNum); } private void emitRestartLocal(Output out, int registerNum) { out.writeByte(6); out.writeUnsignedLeb128(registerNum); } private void emitSetPrologueEnd(Output out) { out.writeByte(7); } private void emitSetEpilogueBegin(Output out) { out.writeByte(8); } private void emitSetFile(Output out) { out.writeByte(9); out.writeByte(1); } private void emitSpecialOpcode(Output out, byte opcode) { out.writeByte(opcode); } 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(DexFile dexFile, Output out, List<Item> referencedItems) { int lineDelta = line - currentLine; int addressDelta = address - currentAddress; if (lineDelta < -4 || lineDelta > 10) { emitAdvanceLine(out, lineDelta); lineDelta = 0; } if (lineDelta < 2 && addressDelta > 16 || lineDelta > 1 && addressDelta > 15) { emitAdvancePC(out, address); addressDelta = 0; } //TODO: need to handle the case when the line delta is larger than a signed int emitSpecialOpcode(out, calculateSpecialOpcode(lineDelta, addressDelta)); currentAddress = address; currentLine = line; } } private class StartLocalEvent implements Event { private final int address; private final int registerNum; private final String localName; private final String localType; public StartLocalEvent(int address, int registerNum, String localName, String localType) { this.address = address; this.registerNum = registerNum; this.localName = localName; this.localType = localType; } public int getAddress() { return address; } public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { emitAdvancePC(out, address); emitStartLocal(out, registerNum); referencedItems.add(localName==null?null:StringIdItem.internStringIdItem(dexFile, localName)); referencedItems.add(localType==null?null:TypeIdItem.internTypeIdItem(dexFile, StringIdItem.internStringIdItem(dexFile, localType))); } } private class StartLocalExtendedEvent implements Event { private final int address; private final int registerNum; private final String localName; private final String localType; private final String signature; public StartLocalExtendedEvent(int address, int registerNum, String localName, String localType, String signature) { this.address = address; this.registerNum = registerNum; this.localName = localName; this.localType = localType; this.signature = signature; } public int getAddress() { return address; } public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { emitAdvancePC(out, address); emitStartLocalExtended(out, registerNum); if (localName != null) { referencedItems.add(StringIdItem.internStringIdItem(dexFile, localName)); } if (localType != null) { referencedItems.add(TypeIdItem.internTypeIdItem(dexFile, StringIdItem.internStringIdItem(dexFile, localType))); } if (signature != null) { referencedItems.add(StringIdItem.internStringIdItem(dexFile, signature)); } } } private class EndLocalEvent implements Event { private final int address; private final int registerNum; public EndLocalEvent(int address, int registerNum) { this.address = address; this.registerNum = registerNum; } public int getAddress() { return address; } public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { emitAdvancePC(out, address); emitEndLocal(out, registerNum); } } private class RestartLocalEvent implements Event { private final int address; private final int registerNum; public RestartLocalEvent(int address, int registerNum) { this.address = address; this.registerNum = registerNum; } public int getAddress() { return address; } public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { emitAdvancePC(out, address); emitRestartLocal(out, registerNum); } } private class PrologueEvent implements Event { private final int address; public PrologueEvent(int address) { this.address = address; } public int getAddress() { return address; } public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { emitAdvancePC(out, address); emitSetPrologueEnd(out); } } private class EpilogueEvent implements Event { private final int address; public EpilogueEvent(int address) { this.address = address; } public int getAddress() { return address; } public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { emitAdvancePC(out, address); emitSetEpilogueBegin(out); } } private class SetFileEvent implements Event { private final int address; private final String fileName; public SetFileEvent(int address, String fileName) { this.address = address; this.fileName = fileName; } public int getAddress() { return address; } public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { emitAdvancePC(out, address); emitSetFile(out); if (fileName != null) { referencedItems.add(StringIdItem.internStringIdItem(dexFile, fileName)); } } } }