8298099: [JVMCI] decouple libgraal from JVMCI module at runtime

Reviewed-by: never
This commit is contained in:
Doug Simon 2022-12-07 22:11:11 +00:00
parent 8a9911ef17
commit 8b69a2e434
18 changed files with 381 additions and 467 deletions

View file

@ -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());
}
}
}

View file

@ -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;
}
}