Skip to content

Commit d5826cc

Browse files
author
Christian Wimmer
committed
Fill unused vtable slots with a stub that reports a fatal error
1 parent c886ff7 commit d5826cc

File tree

7 files changed

+172
-7
lines changed

7 files changed

+172
-7
lines changed

substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/AMD64CalleeSavedRegisters.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,17 @@
3636
import org.graalvm.nativeimage.ImageSingletons;
3737
import org.graalvm.nativeimage.Platform;
3838
import org.graalvm.nativeimage.Platforms;
39+
import org.graalvm.word.Pointer;
3940

4041
import com.oracle.svm.core.CalleeSavedRegisters;
4142
import com.oracle.svm.core.FrameAccess;
43+
import com.oracle.svm.core.RegisterDumper;
4244
import com.oracle.svm.core.SubstrateOptions;
4345
import com.oracle.svm.core.SubstrateTargetDescription;
4446
import com.oracle.svm.core.amd64.AMD64CPUFeatureAccess;
4547
import com.oracle.svm.core.config.ConfigurationValues;
4648
import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig;
49+
import com.oracle.svm.core.log.Log;
4750
import com.oracle.svm.core.util.VMError;
4851

4952
import jdk.vm.ci.amd64.AMD64;
@@ -162,4 +165,48 @@ public void emitRestore(AMD64MacroAssembler asm, int frameSize, Register exclude
162165
private AMD64Address calleeSaveAddress(AMD64MacroAssembler asm, int frameSize, Register register) {
163166
return asm.makeAddress(frameRegister, frameSize + getOffsetInFrame(register));
164167
}
168+
169+
@Override
170+
public void dumpRegisters(Log log, Pointer callerSP, boolean printLocationInfo, boolean allowJavaHeapAccess) {
171+
log.string("Callee saved registers (sp=").zhex(callerSP).string(")").indent(true);
172+
/*
173+
* The loop to print all registers is manually unrolled so that the register order is
174+
* defined, and also so that the lookup of the "offset in frame" can be constant folded at
175+
* image build time using a @Fold method.
176+
*/
177+
dumpReg(log, "RAX ", callerSP, offsetInFrameOrNull(AMD64.rax), printLocationInfo, allowJavaHeapAccess);
178+
dumpReg(log, "RBX ", callerSP, offsetInFrameOrNull(AMD64.rbx), printLocationInfo, allowJavaHeapAccess);
179+
dumpReg(log, "RCX ", callerSP, offsetInFrameOrNull(AMD64.rcx), printLocationInfo, allowJavaHeapAccess);
180+
dumpReg(log, "RDX ", callerSP, offsetInFrameOrNull(AMD64.rdx), printLocationInfo, allowJavaHeapAccess);
181+
dumpReg(log, "RBP ", callerSP, offsetInFrameOrNull(AMD64.rbp), printLocationInfo, allowJavaHeapAccess);
182+
dumpReg(log, "RSI ", callerSP, offsetInFrameOrNull(AMD64.rsi), printLocationInfo, allowJavaHeapAccess);
183+
dumpReg(log, "RDI ", callerSP, offsetInFrameOrNull(AMD64.rdi), printLocationInfo, allowJavaHeapAccess);
184+
dumpReg(log, "RSP ", callerSP, offsetInFrameOrNull(AMD64.rsp), printLocationInfo, allowJavaHeapAccess);
185+
dumpReg(log, "R8 ", callerSP, offsetInFrameOrNull(AMD64.r8), printLocationInfo, allowJavaHeapAccess);
186+
dumpReg(log, "R9 ", callerSP, offsetInFrameOrNull(AMD64.r9), printLocationInfo, allowJavaHeapAccess);
187+
dumpReg(log, "R10 ", callerSP, offsetInFrameOrNull(AMD64.r10), printLocationInfo, allowJavaHeapAccess);
188+
dumpReg(log, "R11 ", callerSP, offsetInFrameOrNull(AMD64.r11), printLocationInfo, allowJavaHeapAccess);
189+
dumpReg(log, "R12 ", callerSP, offsetInFrameOrNull(AMD64.r12), printLocationInfo, allowJavaHeapAccess);
190+
dumpReg(log, "R13 ", callerSP, offsetInFrameOrNull(AMD64.r13), printLocationInfo, allowJavaHeapAccess);
191+
dumpReg(log, "R14 ", callerSP, offsetInFrameOrNull(AMD64.r14), printLocationInfo, allowJavaHeapAccess);
192+
dumpReg(log, "R15 ", callerSP, offsetInFrameOrNull(AMD64.r15), printLocationInfo, allowJavaHeapAccess);
193+
log.indent(false);
194+
}
195+
196+
private static void dumpReg(Log log, String label, Pointer callerSP, int offsetInFrameOrNull, boolean printLocationInfo, boolean allowJavaHeapAccess) {
197+
if (offsetInFrameOrNull != 0) {
198+
long value = callerSP.readLong(offsetInFrameOrNull);
199+
RegisterDumper.dumpReg(log, label, value, printLocationInfo, allowJavaHeapAccess);
200+
}
201+
}
202+
203+
@Fold
204+
static int offsetInFrameOrNull(Register register) {
205+
AMD64CalleeSavedRegisters that = AMD64CalleeSavedRegisters.singleton();
206+
if (that.calleeSavedRegisters.contains(register)) {
207+
return that.getOffsetInFrame(register);
208+
} else {
209+
return 0;
210+
}
211+
}
165212
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/CalleeSavedRegisters.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@
3131
import org.graalvm.nativeimage.ImageSingletons;
3232
import org.graalvm.nativeimage.Platform;
3333
import org.graalvm.nativeimage.Platforms;
34+
import org.graalvm.word.Pointer;
3435

3536
import com.oracle.svm.core.code.FrameInfoEncoder;
3637
import com.oracle.svm.core.heap.SubstrateReferenceMapBuilder;
38+
import com.oracle.svm.core.log.Log;
3739
import com.oracle.svm.core.util.VMError;
3840

3941
import jdk.vm.ci.code.Register;
@@ -107,4 +109,13 @@ public int getOffsetInFrame(Register register) {
107109
assert result < 0 : "Note that the offset of a callee save register is negative, because it is located in the callee frame";
108110
return result;
109111
}
112+
113+
/**
114+
* Optional method for subclasses to implement. It is called during diagnostic printing to print
115+
* the values of saved registers of the provided stack frame. The caller must ensure that the
116+
* provided frame really has callee saved registers, since that cannot be checked automatically.
117+
*/
118+
@SuppressWarnings("unused")
119+
public void dumpRegisters(Log log, Pointer callerSP, boolean printLocationInfo, boolean allowJavaHeapAccess) {
120+
}
110121
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.core;
26+
27+
import java.lang.reflect.Method;
28+
29+
import org.graalvm.nativeimage.ImageSingletons;
30+
import org.graalvm.nativeimage.LogHandler;
31+
import org.graalvm.nativeimage.c.function.CodePointer;
32+
import org.graalvm.word.Pointer;
33+
import org.graalvm.word.WordFactory;
34+
35+
import com.oracle.svm.core.annotate.NeverInline;
36+
import com.oracle.svm.core.annotate.StubCallingConvention;
37+
import com.oracle.svm.core.log.Log;
38+
import com.oracle.svm.core.snippets.KnownIntrinsics;
39+
import com.oracle.svm.core.stack.StackOverflowCheck;
40+
import com.oracle.svm.core.thread.VMThreads;
41+
import com.oracle.svm.util.ReflectionUtil;
42+
43+
/**
44+
* Provides a stub method that can be used to fill otherwise unused vtable slots. Instead of a
45+
* segfault, this method provides a full diagnostic output with a stack trace.
46+
*/
47+
public final class InvalidVTableEntryHandler {
48+
public static final Method HANDLER_METHOD = ReflectionUtil.lookupMethod(InvalidVTableEntryHandler.class, "invalidVTableEntryHandler");
49+
public static final String MSG = "Fatal error: Virtual method call used an illegal vtable entry that was seen as unused by the static analysis";
50+
51+
@StubCallingConvention
52+
@NeverInline("We need a separate frame that stores all registers")
53+
private static void invalidVTableEntryHandler() {
54+
VMThreads.StatusSupport.setStatusIgnoreSafepoints();
55+
StackOverflowCheck.singleton().disableStackOverflowChecksForFatalError();
56+
57+
/*
58+
* Since this is so far the only use case we have for a fatal error with
59+
* frameHasCalleeSavedRegisters=true, we inline the usual fatal error handling. Note that
60+
* this has the added benefit that the instructions printed as part of the crash dump are
61+
* from the method that has the illegal vtable call. That can be helpful when debugging the
62+
* cause of the fatal error.
63+
*/
64+
Pointer callerSP = KnownIntrinsics.readCallerStackPointer();
65+
CodePointer callerIP = KnownIntrinsics.readReturnAddress();
66+
LogHandler logHandler = ImageSingletons.lookup(LogHandler.class);
67+
Log log = Log.enterFatalContext(logHandler, callerIP, MSG, null);
68+
if (log != null) {
69+
SubstrateDiagnostics.print(log, callerSP, callerIP, WordFactory.nullPointer(), true);
70+
log.string(MSG).newline();
71+
}
72+
logHandler.fatalError();
73+
}
74+
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,19 +112,19 @@ public static void printLocationInfo(Log log, UnsignedWord value, boolean allowJ
112112

113113
/** Prints extensive diagnostic information to the given Log. */
114114
public static boolean print(Log log, Pointer sp, CodePointer ip) {
115-
return print(log, sp, ip, WordFactory.nullPointer());
115+
return print(log, sp, ip, WordFactory.nullPointer(), false);
116116
}
117117

118118
/**
119119
* Print diagnostics for the various subsystems. If a fatal error occurs while printing
120120
* diagnostics, it can happen that the same thread enters this method multiple times.
121121
*/
122122
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate during printing diagnostics.")
123-
static boolean print(Log log, Pointer sp, CodePointer ip, RegisterDumper.Context context) {
123+
public static boolean print(Log log, Pointer sp, CodePointer ip, RegisterDumper.Context context, boolean frameHasCalleeSavedRegisters) {
124124
log.newline();
125125
// Save the state of the initial error so that this state is consistently used, even if
126126
// further errors occur while printing diagnostics.
127-
if (!state.trySet(log, sp, ip, context) && !isInProgressByCurrentThread()) {
127+
if (!state.trySet(log, sp, ip, context, frameHasCalleeSavedRegisters) && !isInProgressByCurrentThread()) {
128128
log.string("Error: printDiagnostics already in progress by another thread.").newline();
129129
log.newline();
130130
return false;
@@ -244,16 +244,18 @@ private static class PrintDiagnosticsState {
244244
Pointer sp;
245245
CodePointer ip;
246246
RegisterDumper.Context context;
247+
boolean frameHasCalleeSavedRegisters;
247248

248249
@SuppressWarnings("hiding")
249-
public boolean trySet(Log log, Pointer sp, CodePointer ip, RegisterDumper.Context context) {
250+
public boolean trySet(Log log, Pointer sp, CodePointer ip, RegisterDumper.Context context, boolean frameHasCalleeSavedRegisters) {
250251
if (diagnosticThread.compareAndSet(WordFactory.nullPointer(), CurrentIsolate.getCurrentThread())) {
251252
assert diagnosticThunkIndex == 0;
252253
assert invocationCount == 0;
253254
this.log = log;
254255
this.sp = sp;
255256
this.ip = ip;
256257
this.context = context;
258+
this.frameHasCalleeSavedRegisters = frameHasCalleeSavedRegisters;
257259
return true;
258260
}
259261
return false;
@@ -264,6 +266,7 @@ public void clear() {
264266
sp = WordFactory.nullPointer();
265267
ip = WordFactory.nullPointer();
266268
context = WordFactory.nullPointer();
269+
frameHasCalleeSavedRegisters = false;
267270

268271
diagnosticThunkIndex = 0;
269272
invocationCount = 0;
@@ -286,6 +289,9 @@ public void printDiagnostics(Log log, int invocationCount) {
286289
log.string("General purpose register values:").indent(true);
287290
RegisterDumper.singleton().dumpRegisters(log, context, invocationCount <= 2, invocationCount == 1);
288291
log.indent(false);
292+
} else if (CalleeSavedRegisters.supportedByPlatform() && state.frameHasCalleeSavedRegisters) {
293+
CalleeSavedRegisters.singleton().dumpRegisters(log, state.sp, invocationCount <= 2, invocationCount == 1);
294+
289295
}
290296
}
291297
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateSegfaultHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ private static void dumpInterruptibly(PointerBase signalInfo, RegisterDumper.Con
155155

156156
PointerBase sp = RegisterDumper.singleton().getSP(context);
157157
PointerBase ip = RegisterDumper.singleton().getIP(context);
158-
boolean printedDiagnostics = SubstrateDiagnostics.print(log, (Pointer) sp, (CodePointer) ip, context);
158+
boolean printedDiagnostics = SubstrateDiagnostics.print(log, (Pointer) sp, (CodePointer) ip, context, false);
159159
if (printedDiagnostics) {
160160
log.string("Segfault detected, aborting process. Use runtime option -R:-InstallSegfaultHandler if you don't want to use SubstrateSegfaultHandler.").newline();
161161
log.newline();

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ public interface Thunk {
254254

255255
/** Prints extensive diagnostic information to the given Log. */
256256
public static boolean printDiagnostics(Log log, Pointer sp, CodePointer ip) {
257-
return SubstrateDiagnostics.print(log, sp, ip, WordFactory.nullPointer());
257+
return SubstrateDiagnostics.print(log, sp, ip, WordFactory.nullPointer(), false);
258258
}
259259

260260
/**

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.graalvm.nativeimage.c.function.CEntryPointLiteral;
4949
import org.graalvm.nativeimage.c.function.CFunction;
5050
import org.graalvm.nativeimage.c.function.CFunctionPointer;
51+
import org.graalvm.nativeimage.hosted.Feature;
5152

5253
import com.oracle.graal.pointsto.BigBang;
5354
import com.oracle.graal.pointsto.constraints.UnsupportedFeatures;
@@ -60,9 +61,11 @@
6061
import com.oracle.graal.pointsto.meta.AnalysisType;
6162
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
6263
import com.oracle.graal.pointsto.results.AbstractAnalysisResultsBuilder;
64+
import com.oracle.svm.core.InvalidVTableEntryHandler;
6365
import com.oracle.svm.core.StaticFieldsSupport;
6466
import com.oracle.svm.core.SubstrateOptions;
6567
import com.oracle.svm.core.SubstrateUtil;
68+
import com.oracle.svm.core.annotate.AutomaticFeature;
6669
import com.oracle.svm.core.annotate.ExcludeFromReferenceMap;
6770
import com.oracle.svm.core.c.BoxedRelocatedPointer;
6871
import com.oracle.svm.core.c.function.CFunctionOptions;
@@ -80,6 +83,7 @@
8083
import com.oracle.svm.core.hub.DynamicHubSupport;
8184
import com.oracle.svm.core.hub.LayoutEncoding;
8285
import com.oracle.svm.core.util.VMError;
86+
import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl;
8387
import com.oracle.svm.hosted.HostedConfiguration;
8488
import com.oracle.svm.hosted.NativeImageOptions;
8589
import com.oracle.svm.hosted.config.HybridLayout;
@@ -651,6 +655,12 @@ private void buildVTables() {
651655
*/
652656
buildVTable(objectClass, vtablesMap, usedSlotsMap, vtablesSlots);
653657

658+
/*
659+
* To avoid segfaults when jumping to address 0, all unused vtable entries are filled with a
660+
* stub that reports a fatal error.
661+
*/
662+
HostedMethod invalidVTableEntryHandler = hMetaAccess.lookupJavaMethod(InvalidVTableEntryHandler.HANDLER_METHOD);
663+
654664
for (HostedType type : hUniverse.getTypes()) {
655665
if (type.isArray()) {
656666
type.vtable = objectClass.vtable;
@@ -659,13 +669,20 @@ private void buildVTables() {
659669
assert type.isInterface() || type.isPrimitive();
660670
type.vtable = new HostedMethod[0];
661671
}
672+
673+
HostedMethod[] vtableArray = type.vtable;
674+
for (int i = 0; i < vtableArray.length; i++) {
675+
if (vtableArray[i] == null) {
676+
vtableArray[i] = invalidVTableEntryHandler;
677+
}
678+
}
662679
}
663680

664681
if (SubstrateUtil.assertionsEnabled()) {
665682
/* Check that all vtable entries are the correctly resolved methods. */
666683
for (HostedType type : hUniverse.getTypes()) {
667684
for (HostedMethod m : type.vtable) {
668-
assert m == null || m.equals(hUniverse.lookup(type.wrapped.resolveConcreteMethod(m.wrapped, type.wrapped)));
685+
assert m == null || m.equals(invalidVTableEntryHandler) || m.equals(hUniverse.lookup(type.wrapped.resolveConcreteMethod(m.wrapped, type.wrapped)));
669686
}
670687
}
671688
}
@@ -960,3 +977,13 @@ private void processFieldLocations() {
960977
}
961978
}
962979
}
980+
981+
@AutomaticFeature
982+
final class InvalidVTableEntryFeature implements Feature {
983+
984+
@Override
985+
public void beforeAnalysis(BeforeAnalysisAccess a) {
986+
BeforeAnalysisAccessImpl access = (BeforeAnalysisAccessImpl) a;
987+
access.registerAsCompiled(InvalidVTableEntryHandler.HANDLER_METHOD);
988+
}
989+
}

0 commit comments

Comments
 (0)