| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.jack.dx.dex.file; |
| |
| import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_LINE; |
| import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_PC; |
| import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_END_LOCAL; |
| import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_END_SEQUENCE; |
| import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_FIRST_SPECIAL; |
| import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_LINE_BASE; |
| import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_LINE_RANGE; |
| import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_RESTART_LOCAL; |
| import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_SET_PROLOGUE_END; |
| import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL; |
| import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL_EXTENDED; |
| |
| import com.android.jack.dx.dex.code.LocalList; |
| import com.android.jack.dx.dex.code.PositionList; |
| import com.android.jack.dx.rop.code.RegisterSpec; |
| import com.android.jack.dx.rop.code.SourcePosition; |
| import com.android.jack.dx.rop.cst.CstMethodRef; |
| import com.android.jack.dx.rop.cst.CstString; |
| import com.android.jack.dx.rop.cst.CstType; |
| import com.android.jack.dx.rop.type.Prototype; |
| import com.android.jack.dx.rop.type.StdTypeList; |
| import com.android.jack.dx.rop.type.Type; |
| import com.android.jack.dx.util.AnnotatedOutput; |
| import com.android.jack.dx.util.ByteArrayAnnotatedOutput; |
| import com.android.jack.dx.util.ExceptionWithContext; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.BitSet; |
| import java.util.Collections; |
| import java.util.Comparator; |
| |
| /** |
| * An encoder for the dex debug info state machine format. The format |
| * for each method enrty is as follows: |
| * <ol> |
| * <li> signed LEB128: initial value for line register. |
| * <li> n instances of signed LEB128: string indicies (offset by 1) |
| * for each method argument in left-to-right order |
| * with {@code this} excluded. A value of '0' indicates "no name" |
| * <li> A sequence of special or normal opcodes as defined in |
| * {@code DebugInfoConstants}. |
| * <li> A single terminating {@code OP_END_SEQUENCE} |
| * </ol> |
| */ |
| public final class DebugInfoEncoder { |
| private static final boolean DEBUG = false; |
| |
| /** {@code null-ok;} positions (line numbers) to encode */ |
| private final PositionList positions; |
| |
| /** {@code null-ok;} local variables to encode */ |
| private final LocalList locals; |
| |
| private final ByteArrayAnnotatedOutput output; |
| private final DexFile file; |
| private final int codeSize; |
| private final int regSize; |
| |
| private final Prototype desc; |
| private final boolean isStatic; |
| |
| /** current encoding state: bytecode address */ |
| private int address = 0; |
| |
| /** current encoding state: line number */ |
| private int line = 1; |
| |
| /** |
| * if non-null: the output to write annotations to. No normal |
| * output is written to this. |
| */ |
| private AnnotatedOutput annotateTo; |
| |
| /** if non-null: another possible output for annotations */ |
| private PrintWriter debugPrint; |
| |
| /** if non-null: the prefix for each annotation or debugPrint line */ |
| private String prefix; |
| |
| /** true if output should be consumed during annotation */ |
| private boolean shouldConsume; |
| |
| /** indexed by register; last local alive in register */ |
| private final LocalList.Entry[] lastEntryForReg; |
| |
| /** |
| * Creates an instance. |
| * |
| * @param positions {@code null-ok;} positions (line numbers) to encode |
| * @param locals {@code null-ok;} local variables to encode |
| * @param file {@code null-ok;} may only be {@code null} if simply using |
| * this class to do a debug print |
| * @param codeSize |
| * @param regSize |
| * @param isStatic |
| * @param ref |
| */ |
| public DebugInfoEncoder(PositionList positions, |
| LocalList locals, |
| DexFile file, |
| int codeSize, |
| int regSize, |
| boolean isStatic, |
| CstMethodRef ref) { |
| this.positions = positions; |
| this.locals = locals; |
| this.file = file; |
| this.desc = ref.getPrototype(); |
| this.isStatic = isStatic; |
| this.codeSize = codeSize; |
| this.regSize = regSize; |
| |
| output = new ByteArrayAnnotatedOutput(); |
| lastEntryForReg = new LocalList.Entry[regSize]; |
| } |
| |
| /** |
| * Annotates or writes a message to the {@code debugPrint} writer |
| * if applicable. |
| * |
| * @param length the number of bytes associated with this message |
| * @param message the message itself |
| */ |
| private void annotate(int length, String message) { |
| if (prefix != null) { |
| message = prefix + message; |
| } |
| |
| if (annotateTo != null) { |
| annotateTo.annotate(shouldConsume ? length : 0, message); |
| } |
| |
| if (debugPrint != null) { |
| debugPrint.println(message); |
| } |
| } |
| |
| /** |
| * Converts this (PositionList, LocalList) pair into a state machine |
| * sequence. |
| * |
| * @return {@code non-null;} encoded byte sequence without padding and |
| * terminated with a {@code 0x00} byte |
| */ |
| public byte[] convert() { |
| try { |
| byte[] ret; |
| ret = convert0(); |
| |
| if (DEBUG) { |
| for (int i = 0; i < ret.length; i++) { |
| System.err.printf("byte %02x\n", (0xff & ret[i])); |
| } |
| } |
| |
| return ret; |
| } catch (IOException ex) { |
| throw ExceptionWithContext.withContext(ex, "...while encoding debug info"); |
| } |
| } |
| |
| /** |
| * Converts and produces annotations on a stream. Does not write |
| * actual bits to the {@code AnnotatedOutput}. |
| * |
| * @param prefix {@code null-ok;} prefix to attach to each line of output |
| * @param debugPrint {@code null-ok;} if specified, an alternate output for |
| * annotations |
| * @param out {@code null-ok;} if specified, where annotations should go |
| * @param consume whether to claim to have consumed output for |
| * {@code out} |
| * @return {@code non-null;} encoded output |
| */ |
| public byte[] convertAndAnnotate(String prefix, PrintWriter debugPrint, AnnotatedOutput out, |
| boolean consume) { |
| this.prefix = prefix; |
| this.debugPrint = debugPrint; |
| annotateTo = out; |
| shouldConsume = consume; |
| |
| byte[] result = convert(); |
| |
| return result; |
| } |
| |
| private byte[] convert0() throws IOException { |
| ArrayList<PositionList.Entry> sortedPositions = buildSortedPositions(); |
| ArrayList<LocalList.Entry> methodArgs = extractMethodArguments(); |
| |
| emitHeader(sortedPositions, methodArgs); |
| |
| // TODO(dx team): Make this mark be the actual prologue end. |
| output.writeByte(DBG_SET_PROLOGUE_END); |
| |
| if (annotateTo != null || debugPrint != null) { |
| annotate(1, String.format("%04x: prologue end", address)); |
| } |
| |
| int positionsSz = sortedPositions.size(); |
| int localsSz = locals.size(); |
| |
| // Current index in sortedPositions |
| int curPositionIdx = 0; |
| // Current index in locals |
| int curLocalIdx = 0; |
| |
| for (;;) { |
| /* |
| * Emit any information for the current address. |
| */ |
| |
| curLocalIdx = emitLocalsAtAddress(curLocalIdx); |
| curPositionIdx = emitPositionsAtAddress(curPositionIdx, sortedPositions); |
| |
| /* |
| * Figure out what the next important address is. |
| */ |
| |
| int nextAddrL = Integer.MAX_VALUE; // local variable |
| int nextAddrP = Integer.MAX_VALUE; // position (line number) |
| |
| if (curLocalIdx < localsSz) { |
| nextAddrL = locals.get(curLocalIdx).getAddress(); |
| } |
| |
| if (curPositionIdx < positionsSz) { |
| nextAddrP = sortedPositions.get(curPositionIdx).getAddress(); |
| } |
| |
| int next = Math.min(nextAddrP, nextAddrL); |
| |
| // No next important address == done. |
| if (next == Integer.MAX_VALUE) { |
| break; |
| } |
| |
| /* |
| * If the only work remaining are local ends at the end of the |
| * block, stop here. Those are implied anyway. |
| */ |
| if (next == codeSize && nextAddrL == Integer.MAX_VALUE && nextAddrP == Integer.MAX_VALUE) { |
| break; |
| } |
| |
| if (next == nextAddrP) { |
| // Combined advance PC + position entry |
| emitPosition(sortedPositions.get(curPositionIdx++)); |
| } else { |
| emitAdvancePc(next - address); |
| } |
| } |
| |
| emitEndSequence(); |
| |
| return output.toByteArray(); |
| } |
| |
| /** |
| * Emits all local variable activity that occurs at the current |
| * {@link #address} starting at the given index into {@code |
| * locals} and including all subsequent activity at the same |
| * address. |
| * |
| * @param curLocalIdx Current index in locals |
| * @return new value for {@code curLocalIdx} |
| * @throws IOException |
| */ |
| private int emitLocalsAtAddress(int curLocalIdx) throws IOException { |
| int sz = locals.size(); |
| |
| // TODO(dx team): Don't emit ends implied by starts. |
| |
| while ((curLocalIdx < sz) && (locals.get(curLocalIdx).getAddress() == address)) { |
| LocalList.Entry entry = locals.get(curLocalIdx++); |
| int reg = entry.getRegister(); |
| LocalList.Entry prevEntry = lastEntryForReg[reg]; |
| |
| if (entry == prevEntry) { |
| /* |
| * Here we ignore locals entries for parameters, |
| * which have already been represented and placed in the |
| * lastEntryForReg array. |
| */ |
| continue; |
| } |
| |
| // At this point we have a new entry one way or another. |
| lastEntryForReg[reg] = entry; |
| |
| if (entry.isStart()) { |
| if ((prevEntry != null) && entry.matches(prevEntry)) { |
| /* |
| * The previous local in this register has the same |
| * name and type as the one being introduced now, so |
| * use the more efficient "restart" form. |
| */ |
| if (prevEntry.isStart()) { |
| /* |
| * We should never be handed a start when a |
| * a matching local is already active. |
| */ |
| throw new RuntimeException("shouldn't happen"); |
| } |
| emitLocalRestart(entry); |
| } else { |
| emitLocalStart(entry); |
| } |
| } else { |
| /* |
| * Only emit a local end if it is *not* due to a direct |
| * replacement. Direct replacements imply an end of the |
| * previous local in the same register. |
| * |
| * TODO(dx team): Make sure the runtime can deal with implied |
| * local ends from category-2 interactions, and when so, |
| * also stop emitting local ends for those cases. |
| */ |
| if (entry.getDisposition() != LocalList.Disposition.END_REPLACED) { |
| emitLocalEnd(entry); |
| } |
| } |
| } |
| |
| return curLocalIdx; |
| } |
| |
| /** |
| * Emits all positions that occur at the current {@code address} |
| * |
| * @param curPositionIdx Current index in sortedPositions |
| * @param sortedPositions positions, sorted by ascending address |
| * @return new value for {@code curPositionIdx} |
| * @throws IOException |
| */ |
| private int emitPositionsAtAddress(int curPositionIdx, |
| ArrayList<PositionList.Entry> sortedPositions) throws IOException { |
| int positionsSz = sortedPositions.size(); |
| while ((curPositionIdx < positionsSz) |
| && (sortedPositions.get(curPositionIdx).getAddress() == address)) { |
| emitPosition(sortedPositions.get(curPositionIdx++)); |
| } |
| return curPositionIdx; |
| } |
| |
| /** |
| * Emits the header sequence, which consists of LEB128-encoded initial |
| * line number and string indicies for names of all non-"this" arguments. |
| * |
| * @param sortedPositions positions, sorted by ascending address |
| * @param methodArgs local list entries for method argumens arguments, |
| * in left-to-right order omitting "this" |
| * @throws IOException |
| */ |
| private void emitHeader(ArrayList<PositionList.Entry> sortedPositions, |
| ArrayList<LocalList.Entry> methodArgs) throws IOException { |
| boolean annotate = (annotateTo != null) || (debugPrint != null); |
| int mark = output.getCursor(); |
| |
| // Start by initializing the line number register. |
| if (sortedPositions.size() > 0) { |
| PositionList.Entry entry = sortedPositions.get(0); |
| line = entry.getPosition().getLine(); |
| } |
| output.writeUleb128(line); |
| |
| if (annotate) { |
| annotate(output.getCursor() - mark, "line_start: " + line); |
| } |
| |
| int curParam = getParamBase(); |
| // paramTypes will not include 'this' |
| StdTypeList paramTypes = desc.getParameterTypes(); |
| int szParamTypes = paramTypes.size(); |
| |
| /* |
| * Initialize lastEntryForReg to have an initial |
| * entry for the 'this' pointer. |
| */ |
| if (!isStatic) { |
| for (LocalList.Entry arg : methodArgs) { |
| if (curParam == arg.getRegister()) { |
| lastEntryForReg[curParam] = arg; |
| break; |
| } |
| } |
| curParam++; |
| } |
| |
| // Write out the number of parameter entries that will follow. |
| mark = output.getCursor(); |
| output.writeUleb128(szParamTypes); |
| |
| if (annotate) { |
| annotate(output.getCursor() - mark, String.format("parameters_size: %04x", szParamTypes)); |
| } |
| |
| /* |
| * Then emit the string indicies of all the method parameters. |
| * Note that 'this', if applicable, is excluded. |
| */ |
| for (int i = 0; i < szParamTypes; i++) { |
| Type pt = paramTypes.get(i); |
| LocalList.Entry found = null; |
| |
| mark = output.getCursor(); |
| |
| for (LocalList.Entry arg : methodArgs) { |
| if (curParam == arg.getRegister()) { |
| found = arg; |
| |
| if (arg.getSignature() != null) { |
| /* |
| * Parameters with signatures will be re-emitted |
| * in complete as LOCAL_START_EXTENDED's below. |
| */ |
| emitStringIndex(null); |
| } else { |
| emitStringIndex(arg.getName()); |
| } |
| lastEntryForReg[curParam] = arg; |
| |
| break; |
| } |
| } |
| |
| if (found == null) { |
| /* |
| * Emit a null symbol for "unnamed." This is common |
| * for, e.g., synthesized methods and inner-class |
| * this$0 arguments. |
| */ |
| emitStringIndex(null); |
| } |
| |
| if (annotate) { |
| String parameterName = (found == null || found.getSignature() != null) ? "<unnamed>" |
| : found.getName().toHuman(); |
| annotate(output.getCursor() - mark, |
| "parameter " + parameterName + " " + RegisterSpec.PREFIX + curParam); |
| } |
| |
| curParam += pt.getCategory(); |
| } |
| |
| /* |
| * If anything emitted above has a type signature, emit it again as |
| * a LOCAL_RESTART_EXTENDED |
| */ |
| |
| for (LocalList.Entry arg : lastEntryForReg) { |
| if (arg == null) { |
| continue; |
| } |
| |
| CstString signature = arg.getSignature(); |
| |
| if (signature != null) { |
| emitLocalStartExtended(arg); |
| } |
| } |
| } |
| |
| /** |
| * Builds a list of position entries, sorted by ascending address. |
| * |
| * @return A sorted positions list |
| */ |
| private ArrayList<PositionList.Entry> buildSortedPositions() { |
| int sz = (positions == null) ? 0 : positions.size(); |
| ArrayList<PositionList.Entry> result = new ArrayList<PositionList.Entry>(sz); |
| |
| for (int i = 0; i < sz; i++) { |
| result.add(positions.get(i)); |
| } |
| |
| // Sort ascending by address. |
| Collections.sort(result, new Comparator<PositionList.Entry>() { |
| @Override |
| public int compare(PositionList.Entry a, PositionList.Entry b) { |
| return a.getAddress() - b.getAddress(); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return obj == this; |
| } |
| }); |
| return result; |
| } |
| |
| /** |
| * Gets the register that begins the method's parameter range (including |
| * the 'this' parameter for non-static methods). The range continues until |
| * {@code regSize} |
| * |
| * @return register as noted above |
| */ |
| private int getParamBase() { |
| return regSize - desc.getParameterTypes().getWordCount() - (isStatic ? 0 : 1); |
| } |
| |
| /** |
| * Extracts method arguments from a locals list. These will be collected |
| * from the input list and sorted by ascending register in the |
| * returned list. |
| * |
| * @return list of non-{@code this} method argument locals, |
| * sorted by ascending register |
| */ |
| private ArrayList<LocalList.Entry> extractMethodArguments() { |
| ArrayList<LocalList.Entry> result = |
| new ArrayList<LocalList.Entry>(desc.getParameterTypes().size()); |
| int argBase = getParamBase(); |
| BitSet seen = new BitSet(regSize - argBase); |
| int sz = locals.size(); |
| |
| for (int i = 0; i < sz; i++) { |
| LocalList.Entry e = locals.get(i); |
| int reg = e.getRegister(); |
| |
| if (reg < argBase) { |
| continue; |
| } |
| |
| // only the lowest-start-address entry is included. |
| if (seen.get(reg - argBase)) { |
| continue; |
| } |
| |
| seen.set(reg - argBase); |
| result.add(e); |
| } |
| |
| // Sort by ascending register. |
| Collections.sort(result, new Comparator<LocalList.Entry>() { |
| @Override |
| public int compare(LocalList.Entry a, LocalList.Entry b) { |
| return a.getRegister() - b.getRegister(); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return obj == this; |
| } |
| }); |
| |
| return result; |
| } |
| |
| /** |
| * Returns a string representation of this LocalList entry that is |
| * appropriate for emitting as an annotation. |
| * |
| * @param e {@code non-null;} entry |
| * @return {@code non-null;} annotation string |
| */ |
| private String entryAnnotationString(LocalList.Entry e) { |
| StringBuilder sb = new StringBuilder(); |
| |
| sb.append(RegisterSpec.PREFIX); |
| sb.append(e.getRegister()); |
| sb.append(' '); |
| |
| CstString name = e.getName(); |
| if (name == null) { |
| sb.append("null"); |
| } else { |
| sb.append(name.toHuman()); |
| } |
| sb.append(' '); |
| |
| CstType type = e.getType(); |
| if (type == null) { |
| sb.append("null"); |
| } else { |
| sb.append(type.toHuman()); |
| } |
| |
| CstString signature = e.getSignature(); |
| |
| if (signature != null) { |
| sb.append(' '); |
| sb.append(signature.toHuman()); |
| } |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Emits a {@link DebugInfoConstants#DBG_RESTART_LOCAL DBG_RESTART_LOCAL} |
| * sequence. |
| * |
| * @param entry entry associated with this restart |
| * @throws IOException |
| */ |
| private void emitLocalRestart(LocalList.Entry entry) throws IOException { |
| |
| int mark = output.getCursor(); |
| |
| output.writeByte(DBG_RESTART_LOCAL); |
| emitUnsignedLeb128(entry.getRegister()); |
| |
| if (annotateTo != null || debugPrint != null) { |
| annotate(output.getCursor() - mark, |
| String.format("%04x: +local restart %s", address, entryAnnotationString(entry))); |
| } |
| |
| if (DEBUG) { |
| System.err.println("emit local restart"); |
| } |
| } |
| |
| /** |
| * Emits a string index as an unsigned LEB128. The actual value written |
| * is shifted by 1, so that the '0' value is reserved for "null". The |
| * null symbol is used in some cases by the parameter name list |
| * at the beginning of the sequence. |
| * |
| * @param string {@code null-ok;} string to emit |
| * @throws IOException |
| */ |
| private void emitStringIndex(CstString string) throws IOException { |
| if ((string == null) || (file == null)) { |
| output.writeUleb128(0); |
| } else { |
| output.writeUleb128(1 + file.getStringIds().indexOf(string)); |
| } |
| |
| if (DEBUG) { |
| System.err.printf("Emit string %s\n", string == null ? "<null>" : string.toQuoted()); |
| } |
| } |
| |
| /** |
| * Emits a type index as an unsigned LEB128. The actual value written |
| * is shifted by 1, so that the '0' value is reserved for "null". |
| * |
| * @param type {@code null-ok;} type to emit |
| * @throws IOException |
| */ |
| private void emitTypeIndex(CstType type) throws IOException { |
| if ((type == null) || (file == null)) { |
| output.writeUleb128(0); |
| } else { |
| output.writeUleb128(1 + file.getTypeIds().indexOf(type)); |
| } |
| |
| if (DEBUG) { |
| System.err.printf("Emit type %s\n", type == null ? "<null>" : type.toHuman()); |
| } |
| } |
| |
| /** |
| * Emits a {@link DebugInfoConstants#DBG_START_LOCAL DBG_START_LOCAL} or |
| * {@link DebugInfoConstants#DBG_START_LOCAL_EXTENDED |
| * DBG_START_LOCAL_EXTENDED} sequence. |
| * |
| * @param entry entry to emit |
| * @throws IOException |
| */ |
| private void emitLocalStart(LocalList.Entry entry) throws IOException { |
| |
| if (entry.getSignature() != null) { |
| emitLocalStartExtended(entry); |
| return; |
| } |
| |
| int mark = output.getCursor(); |
| |
| output.writeByte(DBG_START_LOCAL); |
| |
| emitUnsignedLeb128(entry.getRegister()); |
| emitStringIndex(entry.getName()); |
| emitTypeIndex(entry.getType()); |
| |
| if (annotateTo != null || debugPrint != null) { |
| annotate(output.getCursor() - mark, |
| String.format("%04x: +local %s", address, entryAnnotationString(entry))); |
| } |
| |
| if (DEBUG) { |
| System.err.println("emit local start"); |
| } |
| } |
| |
| /** |
| * Emits a {@link DebugInfoConstants#DBG_START_LOCAL_EXTENDED |
| * DBG_START_LOCAL_EXTENDED} sequence. |
| * |
| * @param entry entry to emit |
| * @throws IOException |
| */ |
| private void emitLocalStartExtended(LocalList.Entry entry) throws IOException { |
| |
| int mark = output.getCursor(); |
| |
| output.writeByte(DBG_START_LOCAL_EXTENDED); |
| |
| emitUnsignedLeb128(entry.getRegister()); |
| emitStringIndex(entry.getName()); |
| emitTypeIndex(entry.getType()); |
| emitStringIndex(entry.getSignature()); |
| |
| if (annotateTo != null || debugPrint != null) { |
| annotate(output.getCursor() - mark, |
| String.format("%04x: +localx %s", address, entryAnnotationString(entry))); |
| } |
| |
| if (DEBUG) { |
| System.err.println("emit local start"); |
| } |
| } |
| |
| /** |
| * Emits a {@link DebugInfoConstants#DBG_END_LOCAL DBG_END_LOCAL} sequence. |
| * |
| * @param entry {@code entry non-null;} entry associated with end. |
| * @throws IOException |
| */ |
| private void emitLocalEnd(LocalList.Entry entry) throws IOException { |
| |
| int mark = output.getCursor(); |
| |
| output.writeByte(DBG_END_LOCAL); |
| output.writeUleb128(entry.getRegister()); |
| |
| if (annotateTo != null || debugPrint != null) { |
| annotate(output.getCursor() - mark, |
| String.format("%04x: -local %s", address, entryAnnotationString(entry))); |
| } |
| |
| if (DEBUG) { |
| System.err.println("emit local end"); |
| } |
| } |
| |
| /** |
| * Emits the necessary byte sequences to emit the given position table |
| * entry. This will typically be a single special opcode, although |
| * it may also require DBG_ADVANCE_PC or DBG_ADVANCE_LINE. |
| * |
| * @param entry position entry to emit. |
| * @throws IOException |
| */ |
| private void emitPosition(PositionList.Entry entry) throws IOException { |
| |
| SourcePosition pos = entry.getPosition(); |
| int newLine = pos.getLine(); |
| int newAddress = entry.getAddress(); |
| |
| int opcode; |
| |
| int deltaLines = newLine - line; |
| int deltaAddress = newAddress - address; |
| |
| if (deltaAddress < 0) { |
| throw new RuntimeException("Position entries must be in ascending address order"); |
| } |
| |
| if ((deltaLines < DBG_LINE_BASE) || (deltaLines > (DBG_LINE_BASE + DBG_LINE_RANGE - 1))) { |
| emitAdvanceLine(deltaLines); |
| deltaLines = 0; |
| } |
| |
| opcode = computeOpcode(deltaLines, deltaAddress); |
| |
| if ((opcode & ~0xff) > 0) { |
| emitAdvancePc(deltaAddress); |
| deltaAddress = 0; |
| opcode = computeOpcode(deltaLines, deltaAddress); |
| |
| if ((opcode & ~0xff) > 0) { |
| emitAdvanceLine(deltaLines); |
| deltaLines = 0; |
| opcode = computeOpcode(deltaLines, deltaAddress); |
| } |
| } |
| |
| output.writeByte(opcode); |
| |
| line += deltaLines; |
| address += deltaAddress; |
| |
| if (annotateTo != null || debugPrint != null) { |
| annotate(1, String.format("%04x: line %d", address, line)); |
| } |
| } |
| |
| /** |
| * Computes a special opcode that will encode the given position change. |
| * If the return value is > 0xff, then the request cannot be fulfilled. |
| * Essentially the same as described in "DWARF Debugging Format Version 3" |
| * section 6.2.5.1. |
| * |
| * @param deltaLines {@code >= DBG_LINE_BASE, <= DBG_LINE_BASE + |
| * DBG_LINE_RANGE;} the line change to encode |
| * @param deltaAddress {@code >= 0;} the address change to encode |
| * @return {@code <= 0xff} if in range, otherwise parameters are out |
| * of range |
| */ |
| private static int computeOpcode(int deltaLines, int deltaAddress) { |
| if (deltaLines < DBG_LINE_BASE || deltaLines > (DBG_LINE_BASE + DBG_LINE_RANGE - 1)) { |
| |
| throw new RuntimeException("Parameter out of range"); |
| } |
| |
| return (deltaLines - DBG_LINE_BASE) + (DBG_LINE_RANGE * deltaAddress) + DBG_FIRST_SPECIAL; |
| } |
| |
| /** |
| * Emits an {@link DebugInfoConstants#DBG_ADVANCE_LINE DBG_ADVANCE_LINE} |
| * sequence. |
| * |
| * @param deltaLines amount to change line number register by |
| * @throws IOException |
| */ |
| private void emitAdvanceLine(int deltaLines) throws IOException { |
| int mark = output.getCursor(); |
| |
| output.writeByte(DBG_ADVANCE_LINE); |
| output.writeSleb128(deltaLines); |
| line += deltaLines; |
| |
| if (annotateTo != null || debugPrint != null) { |
| annotate(output.getCursor() - mark, String.format("line = %d", line)); |
| } |
| |
| if (DEBUG) { |
| System.err.printf("Emitting advance_line for %d\n", deltaLines); |
| } |
| } |
| |
| /** |
| * Emits an {@link DebugInfoConstants#DBG_ADVANCE_PC DBG_ADVANCE_PC} |
| * sequence. |
| * |
| * @param deltaAddress {@code >= 0;} amount to change program counter by |
| * @throws IOException |
| */ |
| private void emitAdvancePc(int deltaAddress) throws IOException { |
| int mark = output.getCursor(); |
| |
| output.writeByte(DBG_ADVANCE_PC); |
| output.writeUleb128(deltaAddress); |
| address += deltaAddress; |
| |
| if (annotateTo != null || debugPrint != null) { |
| annotate(output.getCursor() - mark, String.format("%04x: advance pc", address)); |
| } |
| |
| if (DEBUG) { |
| System.err.printf("Emitting advance_pc for %d\n", deltaAddress); |
| } |
| } |
| |
| /** |
| * Emits an unsigned LEB128 value. |
| * |
| * @param n {@code >= 0;} value to emit. Note that, although this can |
| * represent integers larger than Integer.MAX_VALUE, we currently don't |
| * allow that. |
| * @throws IOException |
| */ |
| private void emitUnsignedLeb128(int n) throws IOException { |
| // We'll never need the top end of the unsigned range anyway. |
| if (n < 0) { |
| throw new RuntimeException("Signed value where unsigned required: " + n); |
| } |
| |
| output.writeUleb128(n); |
| } |
| |
| /** |
| * Emits the {@link DebugInfoConstants#DBG_END_SEQUENCE DBG_END_SEQUENCE} |
| * bytecode. |
| */ |
| private void emitEndSequence() { |
| output.writeByte(DBG_END_SEQUENCE); |
| |
| if (annotateTo != null || debugPrint != null) { |
| annotate(1, "end sequence"); |
| } |
| } |
| } |