blob: 23d5c403efd7d2f29ccf82856f42e28b03f09531 [file] [log] [blame]
/*
* Copyright 2012, 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.dexlib2.writer;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.jf.dexlib2.DebugItemType;
import org.jf.dexlib2.iface.Method;
import org.jf.dexlib2.iface.MethodImplementation;
import org.jf.dexlib2.iface.MethodParameter;
import org.jf.dexlib2.iface.debug.*;
import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class DebugInfoPool {
@Nonnull private final Map<Method, Integer> debugInfoOffsetMap = Maps.newHashMap();
@Nonnull private final DexFile dexFile;
private int sectionOffset = -1;
public DebugInfoPool(@Nonnull DexFile dexFile) {
this.dexFile = dexFile;
}
public boolean intern(@Nonnull Method method) {
boolean hasDebugInfo = false;
for (MethodParameter param: method.getParameters()) {
String paramName = param.getName();
if (paramName != null) {
hasDebugInfo = true;
dexFile.stringPool.intern(paramName);
}
}
MethodImplementation methodImpl = method.getImplementation();
if (methodImpl != null) {
for (DebugItem debugItem: methodImpl.getDebugItems()) {
hasDebugInfo = true;
switch (debugItem.getDebugItemType()) {
case DebugItemType.START_LOCAL:
StartLocal startLocal = (StartLocal)debugItem;
dexFile.stringPool.internNullable(startLocal.getName());
dexFile.typePool.internNullable(startLocal.getType());
dexFile.stringPool.internNullable(startLocal.getSignature());
break;
case DebugItemType.SET_SOURCE_FILE:
dexFile.stringPool.internNullable(((SetSourceFile) debugItem).getSourceFile());
break;
}
}
}
if (hasDebugInfo) {
debugInfoOffsetMap.put(method, 0);
}
return hasDebugInfo;
}
public int getOffset(@Nonnull Method method) {
Integer offset = debugInfoOffsetMap.get(method);
if (offset == null) {
return 0;
}
return offset;
}
public int getNumItems() {
return debugInfoOffsetMap.size();
}
public int getSectionOffset() {
if (sectionOffset < 0) {
throw new ExceptionWithContext("Section offset has not been set yet!");
}
return sectionOffset;
}
public void write(@Nonnull DexWriter writer) throws IOException {
List<Method> methods = Lists.newArrayList(debugInfoOffsetMap.keySet());
Collections.sort(methods);
sectionOffset = writer.getPosition();
for (Method method: methods) {
debugInfoOffsetMap.put(method, writer.getPosition());
int startingLineNumber = 0;
MethodImplementation methodImpl = method.getImplementation();
List<DebugItem> debugItems = null;
if (methodImpl != null) {
debugItems = Lists.newArrayList(methodImpl.getDebugItems());
for (DebugItem item: debugItems) {
if (item.getDebugItemType() == DebugItemType.LINE_NUMBER) {
startingLineNumber = ((LineNumber)item).getLineNumber();
}
}
}
writer.writeUleb128(startingLineNumber);
// TODO: do we need to write out all param names, even if the last n are null?
List<? extends MethodParameter> parameters = method.getParameters();
writer.writeUleb128(parameters.size());
for (MethodParameter parameter: parameters) {
writer.writeUleb128(dexFile.stringPool.getIndexNullable(parameter.getName())+1);
}
if (debugItems != null && debugItems.size() > 0) {
DebugWriter debugWriter = new DebugWriter(dexFile, writer, startingLineNumber);
for (DebugItem debugItem: debugItems) {
switch (debugItem.getDebugItemType()) {
case DebugItemType.START_LOCAL:
debugWriter.emitStartLocal((StartLocal)debugItem);
break;
case DebugItemType.END_LOCAL:
debugWriter.emitEndLocal((EndLocal)debugItem);
break;
case DebugItemType.RESTART_LOCAL:
debugWriter.emitRestartLocal((RestartLocal)debugItem);
break;
case DebugItemType.PROLOGUE_END:
debugWriter.emitPrologueEnd((PrologueEnd)debugItem);
break;
case DebugItemType.EPILOGUE_BEGIN:
debugWriter.emitEpilogueBegin((EpilogueBegin)debugItem);
break;
case DebugItemType.SET_SOURCE_FILE:
debugWriter.emitSetSourceFile((SetSourceFile)debugItem);
break;
case DebugItemType.LINE_NUMBER:
debugWriter.emitLineNumber((LineNumber)debugItem);
break;
default:
throw new ExceptionWithContext("Unexpected debug item type: %d",
debugItem.getDebugItemType());
}
}
}
// write an END_SEQUENCE opcode, to end the debug item
writer.write(0);
}
}
// TODO: add some validation here.
private static class DebugWriter {
@Nonnull private final DexFile dexFile;
@Nonnull private final DexWriter writer;
private int currentAddress = 0;
private int currentLine;
public DebugWriter(@Nonnull DexFile dexFile, @Nonnull DexWriter writer, int startLine) {
this.dexFile = dexFile;
this.writer = writer;
this.currentLine = startLine;
}
private void emitAdvancePC(int address) throws IOException {
int addressDelta = address-currentAddress;
if (addressDelta > 0) {
writer.write(1);
writer.writeUleb128(addressDelta);
currentAddress = address;
}
}
private void emitAdvanceLine(int line) throws IOException {
int lineDelta = line-currentAddress;
if (lineDelta != 0) {
writer.write(2);
writer.writeSleb128(lineDelta);
currentLine = line;
}
}
public void emitLineNumber(@Nonnull LineNumber lineNumber) throws IOException {
int lineDelta = lineNumber.getLineNumber() - currentLine;
int addressDelta = lineNumber.getCodeAddress() - currentAddress;
if (lineDelta < -4 || lineDelta > 10) {
emitAdvanceLine(lineNumber.getLineNumber());
lineDelta = 0;
} else if ((lineDelta < 2 && addressDelta > 16) || (lineDelta > 1 && addressDelta > 15)) {
emitAdvancePC(lineNumber.getCodeAddress());
addressDelta = 0;
}
//TODO: need to handle the case when the line delta is larger than a signed int
emitSpecialOpcode(lineDelta, addressDelta);
}
public void emitStartLocal(@Nonnull StartLocal startLocal) throws IOException {
int nameIndex = dexFile.stringPool.getIndexNullable(startLocal.getName());
int typeIndex = dexFile.typePool.getIndexNullable(startLocal.getType());
int signatureIndex = dexFile.stringPool.getIndexNullable(startLocal.getSignature());
emitAdvancePC(startLocal.getCodeAddress());
if (signatureIndex == -1) {
writer.write(3);
writer.writeUleb128(startLocal.getRegister());
writer.writeUleb128(nameIndex+1);
writer.writeUleb128(typeIndex+1);
} else {
writer.write(4);
writer.writeUleb128(startLocal.getRegister());
writer.writeUleb128(nameIndex+1);
writer.writeUleb128(typeIndex+1);
writer.writeUleb128(signatureIndex+1);
}
}
public void emitEndLocal(@Nonnull EndLocal endLocal) throws IOException {
emitAdvancePC(endLocal.getCodeAddress());
writer.write(5);
writer.writeUleb128(endLocal.getRegister());
}
public void emitRestartLocal(@Nonnull RestartLocal restartLocal) throws IOException {
emitAdvancePC(restartLocal.getCodeAddress());
writer.write(6);
writer.writeUleb128(restartLocal.getRegister());
}
public void emitPrologueEnd(@Nonnull PrologueEnd prologueEnd) throws IOException {
emitAdvancePC(prologueEnd.getCodeAddress());
writer.write(7);
}
public void emitEpilogueBegin(@Nonnull EpilogueBegin epilogueBegin) throws IOException {
emitAdvancePC(epilogueBegin.getCodeAddress());
writer.write(8);
}
public void emitSetSourceFile(@Nonnull SetSourceFile setSourceFile) throws IOException {
emitAdvancePC(setSourceFile.getCodeAddress());
writer.write(9);
writer.write(dexFile.stringPool.getIndexNullable(setSourceFile.getSourceFile()));
}
private static final int LINE_BASE = -4;
private static final int LINE_RANGE = 15;
private static final int FIRST_SPECIAL = 0x0a;
private void emitSpecialOpcode(int lineDelta, int addressDelta) throws IOException {
writer.write((byte)(FIRST_SPECIAL + (addressDelta * LINE_RANGE) + (lineDelta - LINE_BASE)));
currentLine += lineDelta;
addressDelta += addressDelta;
}
}
}