mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 23:04:50 +02:00
8298099: [JVMCI] decouple libgraal from JVMCI module at runtime
Reviewed-by: never
This commit is contained in:
parent
8a9911ef17
commit
8b69a2e434
18 changed files with 381 additions and 467 deletions
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.internal.vm;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
/**
|
||||
* Support for translating exceptions between the HotSpot heap and libjvmci heap.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
final class TranslatedException extends Exception {
|
||||
|
||||
/**
|
||||
* The value returned by {@link #encodeThrowable(Throwable)} when encoding
|
||||
* fails due to an {@link OutOfMemoryError}.
|
||||
*/
|
||||
private static final byte[] FALLBACK_ENCODED_OUTOFMEMORYERROR_BYTES;
|
||||
|
||||
/**
|
||||
* The value returned by {@link #encodeThrowable(Throwable)} when encoding
|
||||
* fails for any reason other than {@link OutOfMemoryError}.
|
||||
*/
|
||||
private static final byte[] FALLBACK_ENCODED_THROWABLE_BYTES;
|
||||
static {
|
||||
try {
|
||||
FALLBACK_ENCODED_THROWABLE_BYTES =
|
||||
encodeThrowable(new TranslatedException("error during encoding",
|
||||
"<unknown>"), false);
|
||||
FALLBACK_ENCODED_OUTOFMEMORYERROR_BYTES =
|
||||
encodeThrowable(new OutOfMemoryError(), false);
|
||||
} catch (IOException e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class name of exception that could not be instantiated.
|
||||
*/
|
||||
private String originalExceptionClassName;
|
||||
|
||||
private TranslatedException(String message, String originalExceptionClassName) {
|
||||
super(message);
|
||||
this.originalExceptionClassName = originalExceptionClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* No need to record an initial stack trace since
|
||||
* it will be manually overwritten.
|
||||
*/
|
||||
@SuppressWarnings("sync-override")
|
||||
@Override
|
||||
public Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String s;
|
||||
if (originalExceptionClassName.equals(TranslatedException.class.getName())) {
|
||||
s = getClass().getName();
|
||||
} else {
|
||||
s = getClass().getName() + "[" + originalExceptionClassName + "]";
|
||||
}
|
||||
String message = getMessage();
|
||||
return (message != null) ? (s + ": " + message) : s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a stack trace for {@code throwable} if the system property
|
||||
* {@code "jdk.internal.vm.TranslatedException.debug"} is true.
|
||||
*/
|
||||
private static void debugPrintStackTrace(Throwable throwable) {
|
||||
if (Boolean.getBoolean("jdk.internal.vm.TranslatedException.debug")) {
|
||||
System.err.print("DEBUG: ");
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static Throwable initCause(Throwable throwable, Throwable cause) {
|
||||
if (cause != null) {
|
||||
try {
|
||||
throwable.initCause(cause);
|
||||
} catch (IllegalStateException e) {
|
||||
// Cause could not be set or overwritten.
|
||||
debugPrintStackTrace(e);
|
||||
}
|
||||
}
|
||||
return throwable;
|
||||
}
|
||||
|
||||
private static Throwable create(String className, String message, Throwable cause) {
|
||||
// Try create with reflection first.
|
||||
try {
|
||||
Class<?> cls = Class.forName(className);
|
||||
if (cause != null) {
|
||||
// Handle known exception types whose cause must
|
||||
// be set in the constructor
|
||||
if (cls == InvocationTargetException.class) {
|
||||
return new InvocationTargetException(cause, message);
|
||||
}
|
||||
if (cls == ExceptionInInitializerError.class) {
|
||||
return new ExceptionInInitializerError(cause);
|
||||
}
|
||||
}
|
||||
if (message == null) {
|
||||
Constructor<?> cons = cls.getConstructor();
|
||||
return initCause((Throwable) cons.newInstance(), cause);
|
||||
}
|
||||
Constructor<?> cons = cls.getDeclaredConstructor(String.class);
|
||||
return initCause((Throwable) cons.newInstance(message), cause);
|
||||
} catch (Throwable translationFailure) {
|
||||
debugPrintStackTrace(translationFailure);
|
||||
return initCause(new TranslatedException(message, className), cause);
|
||||
}
|
||||
}
|
||||
|
||||
private static String emptyIfNull(String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
private static String emptyAsNull(String value) {
|
||||
return value.isEmpty() ? null : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes {@code throwable} including its stack and causes as a {@linkplain GZIPOutputStream
|
||||
* compressed} byte array that can be decoded by {@link #decodeThrowable}.
|
||||
*/
|
||||
static byte[] encodeThrowable(Throwable throwable) {
|
||||
try {
|
||||
return encodeThrowable(throwable, true);
|
||||
} catch (OutOfMemoryError e) {
|
||||
return FALLBACK_ENCODED_OUTOFMEMORYERROR_BYTES;
|
||||
} catch (Throwable e) {
|
||||
return FALLBACK_ENCODED_THROWABLE_BYTES;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] encodeThrowable(Throwable throwable,
|
||||
boolean withCauseAndStack) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(baos))) {
|
||||
List<Throwable> throwables = new ArrayList<>();
|
||||
for (Throwable current = throwable; current != null; current = current.getCause()) {
|
||||
throwables.add(current);
|
||||
if (!withCauseAndStack) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Encode from inner most cause outwards
|
||||
Collections.reverse(throwables);
|
||||
|
||||
for (Throwable current : throwables) {
|
||||
dos.writeUTF(current.getClass().getName());
|
||||
dos.writeUTF(emptyIfNull(current.getMessage()));
|
||||
StackTraceElement[] stackTrace = withCauseAndStack ? current.getStackTrace() : null;
|
||||
if (stackTrace == null) {
|
||||
stackTrace = new StackTraceElement[0];
|
||||
}
|
||||
dos.writeInt(stackTrace.length);
|
||||
for (int i = 0; i < stackTrace.length; i++) {
|
||||
StackTraceElement frame = stackTrace[i];
|
||||
if (frame != null) {
|
||||
dos.writeUTF(emptyIfNull(frame.getClassLoaderName()));
|
||||
dos.writeUTF(emptyIfNull(frame.getModuleName()));
|
||||
dos.writeUTF(emptyIfNull(frame.getModuleVersion()));
|
||||
dos.writeUTF(emptyIfNull(frame.getClassName()));
|
||||
dos.writeUTF(emptyIfNull(frame.getMethodName()));
|
||||
dos.writeUTF(emptyIfNull(frame.getFileName()));
|
||||
dos.writeInt(frame.getLineNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stack of the current thread as of the first native method. The chopped
|
||||
* frames are for the VM call to {@link VMSupport#decodeAndThrowThrowable}.
|
||||
*/
|
||||
private static StackTraceElement[] getMyStackTrace() {
|
||||
Exception ex = new Exception();
|
||||
StackTraceElement[] stack = ex.getStackTrace();
|
||||
for (int i = 0; i < stack.length; i++) {
|
||||
StackTraceElement e = stack[i];
|
||||
if (e.isNativeMethod()) {
|
||||
return Arrays.copyOfRange(stack, i, stack.length);
|
||||
}
|
||||
}
|
||||
// This should never happen but since this is exception handling
|
||||
// code, be defensive instead raising a nested exception.
|
||||
return new StackTraceElement[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes {@code encodedThrowable} into a {@link TranslatedException}.
|
||||
*
|
||||
* @param encodedThrowable an encoded exception in the format specified by
|
||||
* {@link #encodeThrowable}
|
||||
*/
|
||||
static Throwable decodeThrowable(byte[] encodedThrowable) {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(encodedThrowable);
|
||||
try (DataInputStream dis = new DataInputStream(new GZIPInputStream(bais))) {
|
||||
Throwable cause = null;
|
||||
Throwable throwable = null;
|
||||
StackTraceElement[] myStack = getMyStackTrace();
|
||||
while (dis.available() != 0) {
|
||||
String exceptionClassName = dis.readUTF();
|
||||
String exceptionMessage = emptyAsNull(dis.readUTF());
|
||||
throwable = create(exceptionClassName, exceptionMessage, cause);
|
||||
int stackTraceDepth = dis.readInt();
|
||||
StackTraceElement[] stackTrace = new StackTraceElement[stackTraceDepth + myStack.length];
|
||||
int stackTraceIndex = 0;
|
||||
int myStackIndex = 0;
|
||||
for (int j = 0; j < stackTraceDepth; j++) {
|
||||
String classLoaderName = emptyAsNull(dis.readUTF());
|
||||
String moduleName = emptyAsNull(dis.readUTF());
|
||||
String moduleVersion = emptyAsNull(dis.readUTF());
|
||||
String className = emptyAsNull(dis.readUTF());
|
||||
String methodName = emptyAsNull(dis.readUTF());
|
||||
String fileName = emptyAsNull(dis.readUTF());
|
||||
int lineNumber = dis.readInt();
|
||||
StackTraceElement ste = new StackTraceElement(classLoaderName,
|
||||
moduleName,
|
||||
moduleVersion,
|
||||
className,
|
||||
methodName,
|
||||
fileName,
|
||||
lineNumber);
|
||||
|
||||
if (ste.isNativeMethod()) {
|
||||
// Best effort attempt to weave stack traces from two heaps into
|
||||
// a single stack trace using native method frames as stitching points.
|
||||
// This is not 100% reliable as there's no guarantee that native method
|
||||
// frames only exist for calls between HotSpot and libjvmci.
|
||||
while (myStackIndex < myStack.length) {
|
||||
StackTraceElement suffixSTE = myStack[myStackIndex++];
|
||||
if (suffixSTE.isNativeMethod()) {
|
||||
break;
|
||||
}
|
||||
stackTrace[stackTraceIndex++] = suffixSTE;
|
||||
}
|
||||
}
|
||||
stackTrace[stackTraceIndex++] = ste;
|
||||
}
|
||||
while (myStackIndex < myStack.length) {
|
||||
stackTrace[stackTraceIndex++] = myStack[myStackIndex++];
|
||||
}
|
||||
if (stackTraceIndex != stackTrace.length) {
|
||||
// Remove null entries at end of stackTrace
|
||||
stackTrace = Arrays.copyOf(stackTrace, stackTraceIndex);
|
||||
}
|
||||
throwable.setStackTrace(stackTrace);
|
||||
cause = throwable;
|
||||
}
|
||||
return throwable;
|
||||
} catch (Throwable translationFailure) {
|
||||
debugPrintStackTrace(translationFailure);
|
||||
return new TranslatedException("Error decoding exception: " + encodedThrowable,
|
||||
translationFailure.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
|
@ -32,12 +32,17 @@ import java.util.jar.JarFile;
|
|||
import java.util.jar.Manifest;
|
||||
import java.util.jar.Attributes;
|
||||
|
||||
import jdk.internal.misc.VM;
|
||||
import jdk.internal.misc.Unsafe;
|
||||
|
||||
/*
|
||||
* Support class used by JVMTI and VM attach mechanism.
|
||||
* Support class used by JVMCI, JVMTI and VM attach mechanism.
|
||||
*/
|
||||
public class VMSupport {
|
||||
|
||||
private static final Unsafe U = Unsafe.getUnsafe();
|
||||
private static Properties agentProps = null;
|
||||
|
||||
/**
|
||||
* Returns the agent properties.
|
||||
*/
|
||||
|
@ -51,13 +56,20 @@ public class VMSupport {
|
|||
private static native Properties initAgentProperties(Properties props);
|
||||
|
||||
/**
|
||||
* Write the given properties list to a byte array and return it. Properties with
|
||||
* a key or value that is not a String is filtered out. The stream written to the byte
|
||||
* array is ISO 8859-1 encoded.
|
||||
* Writes the given properties list to a byte array and return it. The stream written
|
||||
* to the byte array is ISO 8859-1 encoded.
|
||||
*/
|
||||
private static byte[] serializePropertiesToByteArray(Properties p) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
|
||||
p.store(out, null);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns a Properties object containing only the entries in {@code p}
|
||||
* whose key and value are both Strings
|
||||
*/
|
||||
private static Properties onlyStrings(Properties p) {
|
||||
Properties props = new Properties();
|
||||
|
||||
// stringPropertyNames() returns a snapshot of the property keys
|
||||
|
@ -66,17 +78,28 @@ public class VMSupport {
|
|||
String value = p.getProperty(key);
|
||||
props.put(key, value);
|
||||
}
|
||||
|
||||
props.store(out, null);
|
||||
return out.toByteArray();
|
||||
return props;
|
||||
}
|
||||
|
||||
public static byte[] serializePropertiesToByteArray() throws IOException {
|
||||
return serializePropertiesToByteArray(System.getProperties());
|
||||
return serializePropertiesToByteArray(onlyStrings(System.getProperties()));
|
||||
}
|
||||
|
||||
public static byte[] serializeAgentPropertiesToByteArray() throws IOException {
|
||||
return serializePropertiesToByteArray(getAgentProperties());
|
||||
return serializePropertiesToByteArray(onlyStrings(getAgentProperties()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes {@link VM#getSavedProperties()} to a byte array.
|
||||
*
|
||||
* Used by JVMCI to copy properties into libjvmci.
|
||||
*/
|
||||
public static byte[] serializeSavedPropertiesToByteArray() throws IOException {
|
||||
Properties props = new Properties();
|
||||
for (var e : VM.getSavedProperties().entrySet()) {
|
||||
props.put(e.getKey(), e.getValue());
|
||||
}
|
||||
return serializePropertiesToByteArray(props);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -88,4 +111,40 @@ public class VMSupport {
|
|||
* variables such as java.io.tmpdir.
|
||||
*/
|
||||
public static native String getVMTemporaryDirectory();
|
||||
|
||||
/**
|
||||
* Decodes the exception encoded in {@code buffer} and throws it.
|
||||
*
|
||||
* @param buffer a native byte buffer containing an exception encoded by
|
||||
* {@link #encodeThrowable}
|
||||
*/
|
||||
public static void decodeAndThrowThrowable(long buffer) throws Throwable {
|
||||
int encodingLength = U.getInt(buffer);
|
||||
byte[] encoding = new byte[encodingLength];
|
||||
U.copyMemory(null, buffer + 4, encoding, Unsafe.ARRAY_BYTE_BASE_OFFSET, encodingLength);
|
||||
throw TranslatedException.decodeThrowable(encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@code bufferSize} is large enough, encodes {@code throwable} into a byte array and writes
|
||||
* it to {@code buffer}. The encoding in {@code buffer} can be decoded by
|
||||
* {@link #decodeAndThrowThrowable}.
|
||||
*
|
||||
* @param throwable the exception to encode
|
||||
* @param buffer a native byte buffer
|
||||
* @param bufferSize the size of {@code buffer} in bytes
|
||||
* @return the number of bytes written into {@code buffer} if {@code bufferSize} is large
|
||||
* enough, otherwise {@code -N} where {@code N} is the value {@code bufferSize} needs to
|
||||
* be to fit the encoding
|
||||
*/
|
||||
public static int encodeThrowable(Throwable throwable, long buffer, int bufferSize) {
|
||||
byte[] encoding = TranslatedException.encodeThrowable(throwable);
|
||||
int requiredSize = 4 + encoding.length;
|
||||
if (bufferSize < requiredSize) {
|
||||
return -requiredSize;
|
||||
}
|
||||
U.putInt(buffer, encoding.length);
|
||||
U.copyMemory(encoding, Unsafe.ARRAY_BYTE_BASE_OFFSET, null, buffer + 4, encoding.length);
|
||||
return requiredSize;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue