diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java index bb375834d5a2..bc187e6ed7c4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoQueryResult.java @@ -353,6 +353,10 @@ public int getSourceLineNumber() { * Returns the name and source code location of the method. */ public StackTraceElement getSourceReference() { + return getSourceReference(sourceClass, sourceMethodName, sourceLineNumber); + } + + public static StackTraceElement getSourceReference(Class sourceClass, String sourceMethodName, int sourceLineNumber) { if (sourceClass == null) { return new StackTraceElement("", sourceMethodName, null, sourceLineNumber); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BacktraceDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BacktraceDecoder.java index 91cffd6f4f88..ba2e2f4849f0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BacktraceDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BacktraceDecoder.java @@ -37,6 +37,10 @@ import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.util.VMError; +/** + * Decoder for backtraces computed by {@link BacktraceVisitor} and stored in + * {@link Target_java_lang_Throwable#backtrace}. + */ public abstract class BacktraceDecoder { /** @@ -44,7 +48,7 @@ public abstract class BacktraceDecoder { * * @param backtrace internal backtrace stored in {@link Target_java_lang_Throwable#backtrace} * @param maxFramesProcessed the maximum number of frames that should be - * {@linkplain #processFrameInfo processed} + * {@linkplain #processSourceReference processed} * @param maxFramesDecode the maximum number of frames that should be decoded (0 means all) * @return the number of decoded frames */ @@ -53,12 +57,23 @@ protected final int visitBacktrace(Object backtrace, int maxFramesProcessed, int int framesDecoded = 0; if (backtrace != null) { final long[] trace = (long[]) backtrace; - for (long address : trace) { - if (address == 0) { - break; + int backtraceIndex = 0; + while (backtraceIndex < trace.length && trace[backtraceIndex] != 0) { + long entry = trace[backtraceIndex]; + if (BacktraceVisitor.isSourceReference(entry)) { + /* Entry is an encoded source reference. */ + VMError.guarantee(backtraceIndex + BacktraceVisitor.entriesPerSourceReference() <= trace.length, "Truncated backtrace array"); + visitSourceReference(maxFramesProcessed, framesDecoded, trace, backtraceIndex); + /* Always a single frame. */ + framesDecoded++; + backtraceIndex += BacktraceVisitor.entriesPerSourceReference(); + } else { + /* Entry is a raw code pointer. */ + CodePointer ip = WordFactory.pointer(entry); + /* Arbitrary number of Java frames for a single native frame (inlining). */ + framesDecoded = visitCodePointer(ip, framesDecoded, maxFramesProcessed, maxFramesDecodeLimit); + backtraceIndex++; } - CodePointer ip = WordFactory.pointer(address); - framesDecoded = visitCodePointer(ip, framesDecoded, maxFramesProcessed, maxFramesDecodeLimit); if (framesDecoded == maxFramesDecodeLimit) { break; } @@ -67,6 +82,16 @@ protected final int visitBacktrace(Object backtrace, int maxFramesProcessed, int return framesDecoded - maxFramesProcessed; } + private void visitSourceReference(int maxFramesProcessed, int framesDecoded, long[] trace, int backtraceIndex) { + int sourceLineNumber = BacktraceVisitor.readSourceLineNumber(trace, backtraceIndex); + Class sourceClass = BacktraceVisitor.readSourceClass(trace, backtraceIndex); + String sourceMethodName = BacktraceVisitor.readSourceMethodName(trace, backtraceIndex); + + if (framesDecoded < maxFramesProcessed) { + processSourceReference(sourceClass, sourceMethodName, sourceLineNumber); + } + } + @Uninterruptible(reason = "Prevent the GC from freeing the CodeInfo object.") private int visitCodePointer(CodePointer ip, int oldFramesDecoded, int maxFramesProcessed, int maxFramesDecode) { int framesDecoded = oldFramesDecoded; @@ -106,7 +131,7 @@ private int visitFrame(CodePointer ip, CodeInfo tetheredCodeInfo, int oldFramesD continue; } if (framesDecoded < maxFramesProcessed) { - processFrameInfo(frameInfo); + processSourceReference(frameInfo.getSourceClass(), frameInfo.getSourceMethodName(), frameInfo.getSourceLineNumber()); } framesDecoded++; if (framesDecoded == maxFramesDecode) { @@ -117,5 +142,5 @@ private int visitFrame(CodePointer ip, CodeInfo tetheredCodeInfo, int oldFramesD } @RestrictHeapAccess(access = RestrictHeapAccess.Access.UNRESTRICTED, reason = "Some implementations allocate.") - protected abstract void processFrameInfo(FrameInfoQueryResult frameInfo); + protected abstract void processSourceReference(Class sourceClass, String sourceMethodName, int sourceLineNumber); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java index 051107df4cf6..73e22630509d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java @@ -32,22 +32,27 @@ import java.util.ArrayList; import java.util.Arrays; +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.compiler.word.Word; import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; -import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.code.CodeInfo; import com.oracle.svm.core.code.CodeInfoAccess; +import com.oracle.svm.core.code.CodeInfoQueryResult; import com.oracle.svm.core.code.CodeInfoTable; import com.oracle.svm.core.code.FrameInfoQueryResult; -import com.oracle.svm.core.code.UntetheredCodeInfo; +import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.deopt.DeoptimizedFrame; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.heap.ReferenceAccess; import com.oracle.svm.core.heap.VMOperationInfos; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.snippets.KnownIntrinsics; @@ -256,10 +261,66 @@ protected void operate() { /** * Visits the stack frames and collects a backtrace in an internal format to be stored in * {@link Target_java_lang_Throwable#backtrace}. + * + * The {@link Target_java_lang_Throwable#backtrace} is a {@code long} array that either stores a + * native instruction pointer (for AOT compiled methods) or an encoded Java source reference + * containing a source line number, a source class and a source method name (for JIT compiled + * methods). A native instruction pointer is always a single {@code long} element, while an encoded + * Java source reference takes {@linkplain #entriesPerSourceReference() 2 elements} if references + * are {@link #useCompressedReferences() compressed}, or 3 otherwise. Native instruction pointers + * and source references can be mixed. The source line number of the source reference is + * {@linkplain #encodeLineNumber encoded} in a way that it can be distinguished from a native + * instruction pointer. + * + *

Uncompressed References

+ * + *
+ *                      backtrace content      |   Number of Java frames
+ *                    ---------------------------------------------------
+ * backtrace[pos + 0] | native inst. pointer   |   0..n Java frames
+ *                    --------------------------
+ * backtrace[pos + 1] | encoded src line nr    |   1 Java frame
+ * backtrace[pos + 2] | source class ref       |
+ * backtrace[pos + 3] | source method name ref |
+ *                    --------------------------
+ *                    | ... remaining          |
+ *                    --------------------------
+ *                    | 0                      |   0 terminated if not all elements are used
+ * 
+ * + *

Compressed References

+ * + *
+ *                      backtrace content                                   |   Number of Java frames
+ *                    --------------------------------------------------------------------------------
+ * backtrace[pos + 0] | native inst. pointer                                |   0..n Java frames
+ *                    -------------------------------------------------------
+ * backtrace[pos + 1] | encoded src line nr                                 |   1 Java frame
+ * backtrace[pos + 2] | (source class ref) << 32 | (source method name ref) |
+ *                    -------------------------------------------------------
+ *                    | ... remaining                                       |
+ *                    -------------------------------------------------------
+ *                    | 0                                                   |   0 terminated if not all elements are used
+ * 
+ * + * @see #writeSourceReference writes the source references into the backtrace array + * @see #visitAOTFrame writes a native instruction pointer into the backtrace array + * @see BacktraceDecoder decodes the backtrace array + * */ final class BacktraceVisitor extends StackFrameVisitor { + /** + * Index into {@link #trace}. + */ private int index = 0; + + /** + * Number of frames stored (native instruction pointers or encoded Java source reference). + * Because Java frames take up more than one entry in {@link #trace} this number might be + * different to {@link #index}. + */ + private int numFrames = 0; private final int limit = computeNativeLimit(); /* @@ -271,6 +332,11 @@ final class BacktraceVisitor extends StackFrameVisitor { public static final int NATIVE_FRAME_LIMIT_MARGIN = 10; + @Fold + static int entriesPerSourceReference() { + return useCompressedReferences() ? 2 : 3; + } + /** * Gets the number of native frames to collect. Native frames and Java frames do not directly * relate. We cannot tell how many Java frames a native frame represents. Usually, a single @@ -293,45 +359,207 @@ private static int computeNativeLimit() { return maxJavaStackTraceDepthExtended > maxJavaStackTraceDepth ? maxJavaStackTraceDepthExtended : Integer.MAX_VALUE; } - @Uninterruptible(reason = "Prevent the GC from freeing the CodeInfo object.") - private static boolean decodeCodePointer(BuildStackTraceVisitor visitor, CodePointer ip) { - UntetheredCodeInfo untetheredInfo = CodeInfoTable.lookupCodeInfo(ip); - if (untetheredInfo.isNull()) { - /* Unknown frame. Must not happen for AOT-compiled code. */ - throw VMError.shouldNotReachHere("Stack walk must walk only frames of known code."); + @Override + protected boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptimizedFrame) { + if (deoptimizedFrame != null) { + for (DeoptimizedFrame.VirtualFrame frame = deoptimizedFrame.getTopFrame(); frame != null; frame = frame.getCaller()) { + FrameInfoQueryResult frameInfo = frame.getFrameInfo(); + if (!visitFrameInfo(frameInfo)) { + return false; + } + } + } else if (!isAOTCodePointer(ip)) { + CodeInfoQueryResult queryResult = CodeInfoTable.lookupCodeInfoQueryResult(codeInfo, ip); + for (FrameInfoQueryResult frameInfo = queryResult.getFrameInfo(); frameInfo != null; frameInfo = frameInfo.getCaller()) { + if (!visitFrameInfo(frameInfo)) { + return false; + } + } + } else { + visitAOTFrame(ip); } + return numFrames != limit; + } - Object tether = CodeInfoAccess.acquireTether(untetheredInfo); - try { - CodeInfo tetheredCodeInfo = CodeInfoAccess.convert(untetheredInfo, tether); - if (!visitFrame(visitor, ip, tetheredCodeInfo)) { - return true; - } - } finally { - CodeInfoAccess.releaseTether(untetheredInfo, tether); + private void visitAOTFrame(CodePointer ip) { + long rawValue = ip.rawValue(); + VMError.guarantee(rawValue != 0, "Unexpected code pointer: 0"); + if (isSourceReference(rawValue)) { + throw VMError.shouldNotReachHere("Not a code pointer: 0x" + Long.toHexString(rawValue)); } - return false; + ensureSize(index + 1); + trace[index++] = rawValue; + numFrames++; } - @Uninterruptible(reason = "Wraps the now safe call to the possibly interruptible visitor.", callerMustBe = true, calleeMustBe = false) - private static boolean visitFrame(BuildStackTraceVisitor visitor, CodePointer ip, CodeInfo tetheredCodeInfo) { - return visitor.visitFrame(WordFactory.nullPointer(), ip, tetheredCodeInfo, null); + private boolean visitFrameInfo(FrameInfoQueryResult frameInfo) { + if (!StackTraceUtils.shouldShowFrame(frameInfo, false, true, false)) { + /* Always ignore the frame. It is an internal frame of the VM. */ + return true; + + } else if (index == 0 && Throwable.class.isAssignableFrom(frameInfo.getSourceClass())) { + /* + * We are still in the constructor invocation chain at the beginning of the stack trace, + * which is also filtered by the Java HotSpot VM. + */ + return true; + } + int sourceLineNumber = frameInfo.getSourceLineNumber(); + Class sourceClass = frameInfo.getSourceClass(); + String sourceMethodName = frameInfo.getSourceMethodName(); + + VMError.guarantee(Heap.getHeap().isInImageHeap(sourceClass), "Source class must be in the image heap"); + VMError.guarantee(Heap.getHeap().isInImageHeap(sourceMethodName), "Source method name string must be in the image heap"); + + ensureSize(index + entriesPerSourceReference()); + writeSourceReference(trace, index, sourceLineNumber, sourceClass, sourceMethodName); + index += entriesPerSourceReference(); + numFrames++; + return numFrames != limit; } - @Override - protected boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptimizedFrame) { - VMError.guarantee(deoptimizedFrame == null, "Deoptimization not supported"); - long rawValue = ip.rawValue(); - VMError.guarantee(rawValue != 0, "Unexpected code pointer: 0"); - add(rawValue); - return index != limit; + /** + * Determines whether a {@link CodePointer} refers to AOT compiled code that is stored in the + * image heap and therefore cannot be garbage collected. + */ + public static boolean isAOTCodePointer(CodePointer ip) { + return CodeInfoAccess.contains(CodeInfoTable.getImageCodeInfo(), ip); } - private void add(long value) { - if (index == trace.length) { - trace = Arrays.copyOf(trace, Math.min(trace.length * 2, limit)); + /** + * Determines whether an entry in the {@link Target_java_lang_Throwable#backtrace} array is a + * source reference, as written by {@link #writeSourceReference}. + * + * @implNote A source reference entry has their high bit set, i.e., it is a negative number in + * the two's complement representation (see {@link #encodeLineNumber}). + * @see #writeSourceReference + * @see #encodeLineNumber + */ + public static boolean isSourceReference(long entry) { + return entry < 0; + } + + /** + * Encodes line number information of a source reference to be stored in the + * {@link Target_java_lang_Throwable#backtrace} array. Line numbers can be positive for regular + * line number, or zero or negative for to mark special source references. + * + * @implNote A line number ({@code int}) is stored as a negative {@code long} value to + * distinguish it from an ordinary {@link CodePointer}. + * + * @see #isSourceReference + */ + public static long encodeLineNumber(int lineNumber) { + return 0xffffffff_00000000L | lineNumber; + } + + /** + * Decodes a line number previously encoded by {@link #encodeLineNumber}. + */ + public static int decodeLineNumber(long entry) { + return (int) entry; + } + + /** + * Writes source reference to a backtrace array. + * + * @see #readSourceLineNumber + * @see #readSourceClass + * @see #readSourceMethodName + */ + static void writeSourceReference(long[] backtrace, int pos, int sourceLineNumber, Class sourceClass, String sourceMethodName) { + long encodedLineNumber = encodeLineNumber(sourceLineNumber); + if (!isSourceReference(encodedLineNumber)) { + throw VMError.shouldNotReachHere("Encoded line number looks like a code pointer: " + encodedLineNumber); + } + backtrace[pos] = encodedLineNumber; + if (useCompressedReferences()) { + long sourceClassOop = assertNonZero(ReferenceAccess.singleton().getCompressedRepresentation(sourceClass).rawValue()); + long sourceMethodNameOop = assertNonZero(ReferenceAccess.singleton().getCompressedRepresentation(sourceMethodName).rawValue()); + VMError.guarantee((0xffffffff_00000000L & sourceClassOop) == 0L, "Compressed source class reference with high bits"); + VMError.guarantee((0xffffffff_00000000L & sourceMethodNameOop) == 0L, "Compressed source methode name reference with high bits"); + backtrace[pos + 1] = (sourceClassOop << 32) | sourceMethodNameOop; + } else { + backtrace[pos + 1] = assertNonZero(Word.objectToUntrackedPointer(sourceClass).rawValue()); + backtrace[pos + 2] = assertNonZero(Word.objectToUntrackedPointer(sourceMethodName).rawValue()); + } + } + + /** + * Return the source line number of a source reference entry created by + * {@link #writeSourceReference}. + * + * @param backtrace the backtrace array + * @param pos the start position of the source reference entry + * @return the source line number + * + * @see #writeSourceReference + */ + static int readSourceLineNumber(long[] backtrace, int pos) { + return BacktraceVisitor.decodeLineNumber(backtrace[pos]); + } + + /** + * Return the source class of a source reference entry created by {@link #writeSourceReference}. + * + * @param backtrace the backtrace array + * @param pos the start position of the source reference entry + * @return the source class + */ + static Class readSourceClass(long[] backtrace, int pos) { + if (useCompressedReferences()) { + UnsignedWord ref = WordFactory.unsigned(backtrace[pos + 1]).unsignedShiftRight(32); + return (Class) ReferenceAccess.singleton().uncompressReference(ref); + } else { + Word sourceClassPtr = WordFactory.pointer(backtrace[pos + 1]); + return sourceClassPtr.toObject(Class.class, true); + } + } + + /** + * Return the source method name of a source reference entry created by + * {@link #writeSourceReference}. + * + * @param backtrace the backtrace array + * @param pos the start position of the source reference entry + * @return the source method name + */ + static String readSourceMethodName(long[] backtrace, int pos) { + if (useCompressedReferences()) { + UnsignedWord ref = WordFactory.unsigned(backtrace[pos + 1]).and(WordFactory.unsigned(0xffffffffL)); + return (String) ReferenceAccess.singleton().uncompressReference(ref); + } else { + Word sourceMethodNamePtr = WordFactory.pointer(backtrace[pos + 2]); + return sourceMethodNamePtr.toObject(String.class, true); } - trace[index++] = value; + } + + /** + * Determines whether compressed references are enabled. If so, two references can be packed in + * a single {@code long} entry. + */ + @Fold + static boolean useCompressedReferences() { + return ConfigurationValues.getObjectLayout().getReferenceSize() == 4; + } + + private static long assertNonZero(long rawValue) { + VMError.guarantee(rawValue != 0, "Must not write 0 values to backtrace"); + return rawValue; + } + + private void ensureSize(int minLength) { + if (minLength > trace.length) { + trace = Arrays.copyOf(trace, saturatedMultiply(trace.length, 2)); + } + } + + static int saturatedMultiply(int a, int b) { + long r = (long) a * (long) b; + if ((int) r != r) { + return Integer.MAX_VALUE; + } + return (int) r; } /** @@ -369,8 +597,8 @@ static StackTraceElement[] build(Object backtrace) { private final ArrayList trace = new ArrayList<>(); @Override - protected void processFrameInfo(FrameInfoQueryResult frameInfo) { - StackTraceElement sourceReference = frameInfo.getSourceReference(); + protected void processSourceReference(Class sourceClass, String sourceMethodName, int sourceLineNumber) { + StackTraceElement sourceReference = FrameInfoQueryResult.getSourceReference(sourceClass, sourceMethodName, sourceLineNumber); trace.add(sourceReference); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/RealLog.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/RealLog.java index b99fce5d4e8f..8bd06abc4601 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/RealLog.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/RealLog.java @@ -42,10 +42,10 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.c.NonmovableArrays; -import com.oracle.svm.core.code.FrameInfoQueryResult; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.heap.RestrictHeapAccess; +import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.jdk.BacktraceDecoder; import com.oracle.svm.core.jdk.JDKUtils; import com.oracle.svm.core.locks.VMMutex; @@ -701,8 +701,10 @@ protected final int printBacktrace(Object backtrace, int maxFramesProcessed) { @Override @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate when logging.") - protected void processFrameInfo(FrameInfoQueryResult frameInfo) { - printJavaFrame(frameInfo.getSourceClassName(), frameInfo.getSourceMethodName(), frameInfo.getSourceFileName(), frameInfo.getSourceLineNumber()); + protected void processSourceReference(Class sourceClass, String sourceMethodName, int sourceLineNumber) { + String sourceClassName = sourceClass != null ? sourceClass.getName() : ""; + String sourceFileName = sourceClass != null ? DynamicHub.fromClass(sourceClass).getSourceFileName() : null; + printJavaFrame(sourceClassName, sourceMethodName, sourceFileName, sourceLineNumber); } } }