Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.oracle.svm.agent.BreakpointInterceptor Maven / Gradle / Ivy
/*
* Copyright (c) 2019, 2019, 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 com.oracle.svm.agent;
import static com.oracle.svm.agent.Support.check;
import static com.oracle.svm.agent.Support.checkJni;
import static com.oracle.svm.agent.Support.checkNoException;
import static com.oracle.svm.agent.Support.clearException;
import static com.oracle.svm.agent.Support.fromCString;
import static com.oracle.svm.agent.Support.fromJniString;
import static com.oracle.svm.agent.Support.getCallerClass;
import static com.oracle.svm.agent.Support.getCallerMethod;
import static com.oracle.svm.agent.Support.getClassNameOr;
import static com.oracle.svm.agent.Support.getClassNameOrNull;
import static com.oracle.svm.agent.Support.getMethodDeclaringClass;
import static com.oracle.svm.agent.Support.getObjectArgument;
import static com.oracle.svm.agent.Support.handles;
import static com.oracle.svm.agent.Support.jniFunctions;
import static com.oracle.svm.agent.Support.jvmtiEnv;
import static com.oracle.svm.agent.Support.jvmtiFunctions;
import static com.oracle.svm.agent.Support.testException;
import static com.oracle.svm.agent.Support.toCString;
import static com.oracle.svm.agent.jvmti.JvmtiEvent.JVMTI_EVENT_BREAKPOINT;
import static com.oracle.svm.agent.jvmti.JvmtiEvent.JVMTI_EVENT_NATIVE_METHOD_BIND;
import static com.oracle.svm.core.util.VMError.guarantee;
import static com.oracle.svm.jni.JNIObjectHandles.nullHandle;
import static org.graalvm.word.WordFactory.nullPointer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.UnmanagedMemory;
import org.graalvm.nativeimage.c.function.CEntryPoint;
import org.graalvm.nativeimage.c.function.CEntryPointLiteral;
import org.graalvm.nativeimage.c.function.CFunctionPointer;
import org.graalvm.nativeimage.c.function.CodePointer;
import org.graalvm.nativeimage.c.function.InvokeCFunctionPointer;
import org.graalvm.nativeimage.c.struct.SizeOf;
import org.graalvm.nativeimage.c.type.CCharPointerPointer;
import org.graalvm.nativeimage.c.type.CIntPointer;
import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder;
import org.graalvm.nativeimage.c.type.WordPointer;
import org.graalvm.word.WordFactory;
import com.oracle.svm.agent.Support.WordSupplier;
import com.oracle.svm.agent.jvmti.JvmtiCapabilities;
import com.oracle.svm.agent.jvmti.JvmtiEnv;
import com.oracle.svm.agent.jvmti.JvmtiError;
import com.oracle.svm.agent.jvmti.JvmtiEventCallbacks;
import com.oracle.svm.agent.jvmti.JvmtiEventMode;
import com.oracle.svm.agent.restrict.ProxyAccessVerifier;
import com.oracle.svm.agent.restrict.ReflectAccessVerifier;
import com.oracle.svm.agent.restrict.ResourceAccessVerifier;
import com.oracle.svm.configure.config.ConfigurationMethod;
import com.oracle.svm.core.c.function.CEntryPointOptions;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.jni.nativeapi.JNIEnvironment;
import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.CallBooleanMethod0FunctionPointer;
import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.CallLongMethod1FunctionPointer;
import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.CallLongMethod2FunctionPointer;
import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.CallObjectMethod0FunctionPointer;
import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.CallObjectMethod1FunctionPointer;
import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.CallObjectMethod2FunctionPointer;
import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes.CallObjectMethod3FunctionPointer;
import com.oracle.svm.jni.nativeapi.JNIMethodId;
import com.oracle.svm.jni.nativeapi.JNINativeMethod;
import com.oracle.svm.jni.nativeapi.JNIObjectHandle;
import jdk.vm.ci.meta.MetaUtil;
/*
* NOTE: With most of our breakpoints, we recursively call the intercepted method ourselves to
* inspect its return value and determine whether it provides a valid result. This permits us to
* identify probing.
*
* Many of the methods are caller-sensitive, so when we call them from a breakpoint, they
* observe a different caller class and therefore can behave differently. Short of using
* bytecode instrumentation to read the return value, there seems to be no strictly better
* approach (and instrumenting java.lang.Class and friends might be tricky, too). It would be
* possible to set breakpoints at return bytecodes instead, but then there is no way to retrieve
* the return value from the operand stack.
*/
final class BreakpointInterceptor {
private static TraceWriter traceWriter;
private static ReflectAccessVerifier accessVerifier;
private static ProxyAccessVerifier proxyVerifier;
private static ResourceAccessVerifier resourceVerifier;
private static Map installedBreakpoints;
/**
* A map from {@link JNIMethodId} to entry point addresses for bound Java {@code native}
* methods, NOT considering our intercepting functions, i.e., these are the original entry
* points for a native method from symbol resolution or {@code registerNatives}.
*/
private static Map boundNativeMethods = Collections.synchronizedMap(new HashMap<>());
/**
* Map from {@link JNIMethodId} to breakpoints in {@code native} methods. Not all may be
* installed if the native methods haven't been {@linkplain #bindNativeBreakpoint bound}.
*/
private static Map nativeBreakpoints;
private static final ThreadLocal recursive = ThreadLocal.withInitial(() -> Boolean.FALSE);
private static void traceBreakpoint(JNIEnvironment env, JNIObjectHandle clazz, JNIObjectHandle declaringClass, JNIObjectHandle callerClass, String function, Object result, Object... args) {
if (traceWriter != null) {
traceWriter.traceCall("reflect",
function,
getClassNameOr(env, clazz, null, TraceWriter.UNKNOWN_VALUE),
getClassNameOr(env, declaringClass, null, TraceWriter.UNKNOWN_VALUE),
getClassNameOr(env, callerClass, null, TraceWriter.UNKNOWN_VALUE),
result,
args);
guarantee(!testException(env));
}
}
private static boolean forName(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
JNIObjectHandle name = getObjectArgument(0);
String className = fromJniString(jni, name);
boolean allowed = (accessVerifier == null || accessVerifier.verifyForName(jni, callerClass, className));
Object result = false;
if (allowed) {
boolean initializeValid = true;
boolean classLoaderValid = true;
CIntPointer initializePtr = StackValue.get(CIntPointer.class);
WordPointer classLoaderPtr = StackValue.get(WordPointer.class);
if (bp.method == handles().javaLangClassForName3) {
initializeValid = (jvmtiFunctions().GetLocalInt().invoke(jvmtiEnv(), nullHandle(), 0, 1, initializePtr) == JvmtiError.JVMTI_ERROR_NONE);
classLoaderValid = (jvmtiFunctions().GetLocalObject().invoke(jvmtiEnv(), nullHandle(), 0, 2, classLoaderPtr) == JvmtiError.JVMTI_ERROR_NONE);
} else {
initializePtr.write(1);
classLoaderPtr.write(nullHandle());
if (callerClass.notEqual(nullHandle())) {
/*
* NOTE: we use our direct caller class, but this class might be skipped over by
* Class.forName(nameOnly) in its security stackwalk for @CallerSensitive,
* leading to different behavior of our call and the original call.
*/
classLoaderValid = (jvmtiFunctions().GetClassLoader().invoke(jvmtiEnv(), callerClass, classLoaderPtr) == JvmtiError.JVMTI_ERROR_NONE);
}
}
result = TraceWriter.UNKNOWN_VALUE;
if (initializeValid && classLoaderValid) {
result = nullHandle().notEqual(jniFunctions(). getCallStaticObjectMethod().invoke(
jni, bp.clazz, handles().javaLangClassForName3, name, WordFactory.signed(initializePtr.read()), classLoaderPtr.read()));
if (clearException(jni)) {
result = false;
}
}
}
traceBreakpoint(jni, bp.clazz, nullHandle(), callerClass, bp.specification.methodName, result, className);
if (!allowed) {
try (CCharPointerHolder message = toCString(Agent.MESSAGE_PREFIX + "configuration does not permit access to class: " + className)) {
jniFunctions().getThrowNew().invoke(jni, handles().javaLangClassNotFoundException, message.get());
}
}
return allowed;
}
private static boolean getFields(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
return handleGetFields(jni, callerClass, bp, false);
}
private static boolean getDeclaredFields(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
return handleGetFields(jni, callerClass, bp, true);
}
private static boolean handleGetFields(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp, boolean declaredOnly) {
JNIObjectHandle self = getObjectArgument(0);
JNIObjectHandle returnResult = nullHandle();
if (accessVerifier != null) {
returnResult = jniFunctions(). getCallObjectMethod().invoke(jni, self, bp.method);
if (clearException(jni)) {
returnResult = nullHandle();
}
if (returnResult.notEqual(nullHandle())) {
returnResult = accessVerifier.filterGetFields(jni, self, returnResult, declaredOnly, callerClass);
}
}
traceBreakpoint(jni, self, nullHandle(), callerClass, bp.specification.methodName, null);
if (accessVerifier != null && returnResult.notEqual(nullHandle())) {
jvmtiFunctions().ForceEarlyReturnObject().invoke(jvmtiEnv(), nullHandle(), returnResult);
}
return true;
}
private static boolean getMethods(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
return handleGetMethods(jni, callerClass, bp, false, () -> handles().getJavaLangReflectMethod(jni));
}
private static boolean getDeclaredMethods(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
return handleGetMethods(jni, callerClass, bp, true, () -> handles().getJavaLangReflectMethod(jni));
}
private static boolean getConstructors(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
return handleGetMethods(jni, callerClass, bp, true, () -> handles().getJavaLangReflectConstructor(jni));
}
private static boolean getDeclaredConstructors(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
return handleGetMethods(jni, callerClass, bp, true, () -> handles().getJavaLangReflectConstructor(jni));
}
private static boolean handleGetMethods(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp, boolean declaredOnly, WordSupplier elementClass) {
JNIObjectHandle self = getObjectArgument(0);
JNIObjectHandle returnResult = nullHandle();
if (accessVerifier != null) {
returnResult = jniFunctions(). getCallObjectMethod().invoke(jni, self, bp.method);
if (clearException(jni)) {
returnResult = nullHandle();
}
if (returnResult.notEqual(nullHandle())) {
returnResult = accessVerifier.filterGetMethods(jni, self, returnResult, elementClass, declaredOnly, callerClass);
}
}
traceBreakpoint(jni, self, nullHandle(), callerClass, bp.specification.methodName, null);
if (accessVerifier != null && returnResult.notEqual(nullHandle())) {
jvmtiFunctions().ForceEarlyReturnObject().invoke(jvmtiEnv(), nullHandle(), returnResult);
}
return true;
}
private static boolean getField(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
return handleGetField(jni, callerClass, bp, false);
}
private static boolean getDeclaredField(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
return handleGetField(jni, callerClass, bp, true);
}
private static boolean handleGetField(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp, boolean declaredOnly) {
JNIObjectHandle self = getObjectArgument(0);
JNIObjectHandle name = getObjectArgument(1);
JNIObjectHandle result = jniFunctions(). getCallObjectMethod().invoke(jni, self, bp.method, name);
if (clearException(jni)) {
result = nullHandle();
}
JNIObjectHandle declaring = nullHandle();
if (!declaredOnly && result.notEqual(nullHandle())) {
declaring = jniFunctions(). getCallObjectMethod().invoke(jni, result, handles().javaLangReflectMemberGetDeclaringClass);
if (clearException(jni)) {
declaring = nullHandle();
}
}
boolean allowed = result.equal(nullHandle()) || accessVerifier == null || accessVerifier.verifyGetField(jni, self, name, result, (declaredOnly ? self : declaring), callerClass);
traceBreakpoint(jni, self, declaring, callerClass, bp.specification.methodName, allowed && result.notEqual(nullHandle()), fromJniString(jni, name));
if (!allowed) {
try (CCharPointerHolder message = toCString(Agent.MESSAGE_PREFIX + "configuration does not permit access to field: " +
getClassNameOr(jni, self, "(null)", "(?)") + "." + fromJniString(jni, name))) {
jniFunctions().getThrowNew().invoke(jni, handles().javaLangNoSuchFieldException, message.get());
}
}
return allowed;
}
private static final CEntryPointLiteral nativeObjectFieldOffsetLiteral = CEntryPointLiteral.create(
BreakpointInterceptor.class, "nativeObjectFieldOffset", JNIEnvironment.class, JNIObjectHandle.class, JNIObjectHandle.class);
private interface ObjectFieldOffsetFunctionPointer extends CFunctionPointer {
@InvokeCFunctionPointer
long invoke(JNIEnvironment jni, JNIObjectHandle self, JNIObjectHandle field);
}
/** Native breakpoint for the JDK 8 {@code sun.misc.Unsafe.objectFieldOffset} native method. */
@CEntryPoint
@CEntryPointOptions(prologue = AgentIsolate.Prologue.class, epilogue = AgentIsolate.Epilogue.class)
static long nativeObjectFieldOffset(JNIEnvironment jni, JNIObjectHandle self, JNIObjectHandle field) {
JNIMethodId currentMethod = getCallerMethod(0);
JNIObjectHandle callerClass = getCallerClass(1);
ObjectFieldOffsetFunctionPointer original = WordFactory.pointer(boundNativeMethods.get(currentMethod.rawValue()));
long result = original.invoke(jni, self, field);
boolean validResult = !clearException(jni);
JNIObjectHandle name = nullHandle();
JNIObjectHandle declaring = nullHandle();
if (field.notEqual(nullHandle())) {
name = jniFunctions(). getCallObjectMethod().invoke(jni, field, handles().javaLangReflectMemberGetName);
if (clearException(jni)) {
name = nullHandle();
}
declaring = jniFunctions(). getCallObjectMethod().invoke(jni, field, handles().javaLangReflectMemberGetDeclaringClass);
if (clearException(jni)) {
declaring = nullHandle();
}
}
if (!verifyAndTraceObjectFieldOffset(jni, validResult, name, declaring, currentMethod, callerClass)) {
return Long.MIN_VALUE; // (pending exception)
}
if (!validResult) { // invoke again for exception
return original.invoke(jni, self, field);
}
return result;
}
private static boolean objectFieldOffset(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
JNIObjectHandle self = getObjectArgument(0);
JNIObjectHandle field = getObjectArgument(1);
jniFunctions(). getCallLongMethod().invoke(jni, self, bp.method, field);
boolean validResult = !clearException(jni);
JNIObjectHandle name = nullHandle();
JNIObjectHandle declaring = nullHandle();
if (field.notEqual(nullHandle())) {
name = jniFunctions(). getCallObjectMethod().invoke(jni, field, handles().javaLangReflectMemberGetName);
if (clearException(jni)) {
name = nullHandle();
}
declaring = jniFunctions(). getCallObjectMethod().invoke(jni, field, handles().javaLangReflectMemberGetDeclaringClass);
if (clearException(jni)) {
declaring = nullHandle();
}
}
return verifyAndTraceObjectFieldOffset(jni, validResult, name, declaring, bp.method, callerClass);
}
private static boolean verifyAndTraceObjectFieldOffset(JNIEnvironment jni, boolean validResult, JNIObjectHandle name,
JNIObjectHandle declaring, JNIMethodId currentMethod, JNIObjectHandle callerClass) {
JNIObjectHandle clazz = getMethodDeclaringClass(currentMethod);
boolean allowed = !validResult || accessVerifier == null || accessVerifier.verifyObjectFieldOffset(jni, name, declaring, callerClass);
traceBreakpoint(jni, clazz, declaring, callerClass, "objectFieldOffset", allowed && validResult, fromJniString(jni, name));
if (!allowed) {
try (CCharPointerHolder message = toCString(Agent.MESSAGE_PREFIX + "configuration does not permit unsafe access to field: " +
getClassNameOr(jni, declaring, "(null)", "(?)") + "." + fromJniString(jni, name))) {
jniFunctions().getThrowNew().invoke(jni, handles().javaLangRuntimeException, message.get());
}
return false;
}
return true;
}
private static boolean objectFieldOffsetByName(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
JNIObjectHandle self = getObjectArgument(0);
JNIObjectHandle declaring = getObjectArgument(1);
JNIObjectHandle name = getObjectArgument(2);
jniFunctions(). getCallLongMethod().invoke(jni, self, bp.method, declaring, name);
boolean validResult = !clearException(jni);
return verifyAndTraceObjectFieldOffset(jni, validResult, name, declaring, bp.method, callerClass);
}
private static boolean getConstructor(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
JNIObjectHandle self = getObjectArgument(0);
JNIObjectHandle paramTypesHandle = getObjectArgument(1);
JNIObjectHandle result = jniFunctions(). getCallObjectMethod().invoke(jni, self, bp.method, paramTypesHandle);
if (clearException(jni)) {
result = nullHandle();
}
Object paramTypes = getClassArrayNames(jni, paramTypesHandle);
Supplier signature = () -> asInternalSignature(paramTypes);
boolean allowed = result.equal(nullHandle()) || accessVerifier == null || accessVerifier.verifyGetConstructor(jni, self, signature, result, callerClass);
traceBreakpoint(jni, self, nullHandle(), callerClass, bp.specification.methodName, allowed && nullHandle().notEqual(result), paramTypes);
if (!allowed) {
throwNoSuchMethodException(jni, self, ConfigurationMethod.CONSTRUCTOR_NAME, signature.get());
}
return allowed;
}
private static boolean getMethod(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
return handleGetMethod(jni, callerClass, bp, false);
}
private static boolean getDeclaredMethod(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
return handleGetMethod(jni, callerClass, bp, true);
}
private static boolean handleGetMethod(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp, boolean declaredOnly) {
JNIObjectHandle self = getObjectArgument(0);
JNIObjectHandle nameHandle = getObjectArgument(1);
JNIObjectHandle paramTypesHandle = getObjectArgument(2);
JNIObjectHandle result = jniFunctions(). getCallObjectMethod().invoke(jni, self, bp.method, nameHandle, paramTypesHandle);
if (clearException(jni)) {
result = nullHandle();
}
JNIObjectHandle declaring = nullHandle();
if (!declaredOnly && result.notEqual(nullHandle())) {
declaring = jniFunctions(). getCallObjectMethod().invoke(jni, result, handles().javaLangReflectMemberGetDeclaringClass);
if (clearException(jni)) {
declaring = nullHandle();
}
}
String name = fromJniString(jni, nameHandle);
Object paramTypes = getClassArrayNames(jni, paramTypesHandle);
Supplier signature = () -> asInternalSignature(paramTypes);
boolean allowed = result.equal(nullHandle()) || accessVerifier == null || accessVerifier.verifyGetMethod(jni, self, name, signature, result, (declaredOnly ? self : declaring), callerClass);
traceBreakpoint(jni, self, declaring, callerClass, bp.specification.methodName, allowed && result.notEqual(nullHandle()), name, paramTypes);
if (!allowed) {
throwNoSuchMethodException(jni, self, name, signature.get());
}
return allowed;
}
private static boolean getEnclosingMethod(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
JNIObjectHandle self = getObjectArgument(0);
Object result = TraceWriter.EXPLICIT_NULL;
JNIObjectHandle enclosing = jniFunctions(). getCallObjectMethod().invoke(jni, self, bp.method);
JNIObjectHandle holder = nullHandle();
String name = null;
String signature = null;
if (!clearException(jni) && enclosing.notEqual(nullHandle())) {
result = TraceWriter.UNKNOWN_VALUE;
JNIMethodId enclosingID = jniFunctions().getFromReflectedMethod().invoke(jni, enclosing);
if (!clearException(jni) && enclosingID.isNonNull()) {
WordPointer holderPtr = StackValue.get(WordPointer.class);
if (jvmtiFunctions().GetMethodDeclaringClass().invoke(jvmtiEnv(), enclosingID, holderPtr) == JvmtiError.JVMTI_ERROR_NONE) {
holder = holderPtr.read();
String holderName = getClassNameOrNull(jni, holderPtr.read());
if (holderName != null) {
CCharPointerPointer namePtr = StackValue.get(CCharPointerPointer.class);
CCharPointerPointer signaturePtr = StackValue.get(CCharPointerPointer.class);
if (jvmtiFunctions().GetMethodName().invoke(jvmtiEnv(), enclosingID, namePtr, signaturePtr, nullPointer()) == JvmtiError.JVMTI_ERROR_NONE) {
name = fromCString(namePtr.read());
signature = fromCString(signaturePtr.read());
result = holderName + "." + name + signature;
jvmtiFunctions().Deallocate().invoke(jvmtiEnv(), namePtr.read());
jvmtiFunctions().Deallocate().invoke(jvmtiEnv(), signaturePtr.read());
}
}
}
}
}
boolean allowed = enclosing.equal(nullHandle()) || accessVerifier == null || accessVerifier.verifyGetEnclosingMethod(jni, holder, name, signature, enclosing, callerClass);
traceBreakpoint(jni, nullHandle(), nullHandle(), callerClass, bp.specification.methodName, allowed ? result : false);
if (!allowed) {
jvmtiFunctions().ForceEarlyReturnObject().invoke(jvmtiEnv(), nullHandle(), nullHandle());
}
return allowed;
}
private static boolean newInstance(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
JNIObjectHandle self = getObjectArgument(0);
JNIMethodId result;
String name = "";
String signature = "()V";
try (CCharPointerHolder ctorName = toCString(name); CCharPointerHolder ctorSignature = toCString(signature)) {
result = jniFunctions().getGetMethodID().invoke(jni, self, ctorName.get(), ctorSignature.get());
}
if (clearException(jni)) {
result = nullHandle();
}
boolean allowed = result.equal(nullHandle()) || accessVerifier == null || accessVerifier.verifyNewInstance(jni, self, name, signature, result, callerClass);
traceBreakpoint(jni, self, nullHandle(), callerClass, bp.specification.methodName, allowed && result.notEqual(nullHandle()));
if (!allowed) {
throwNoSuchMethodException(jni, self, name, signature);
}
return allowed;
}
private static void throwNoSuchMethodException(JNIEnvironment jni, JNIObjectHandle clazz, String name, String signature) {
try (CCharPointerHolder message = toCString(Agent.MESSAGE_PREFIX + "configuration does not permit access to method: " +
getClassNameOr(jni, clazz, "(null)", "(?)") + "." + name + signature)) {
jniFunctions().getThrowNew().invoke(jni, handles().javaLangNoSuchMethodException, message.get());
}
}
private static boolean getResource(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
return handleGetResources(jni, callerClass, bp, false);
}
private static boolean getResources(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
return handleGetResources(jni, callerClass, bp, true);
}
private static boolean handleGetResources(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp, boolean returnsEnumeration) {
JNIObjectHandle self = getObjectArgument(0);
JNIObjectHandle name = getObjectArgument(1);
boolean result = false;
boolean allowed = (resourceVerifier == null || resourceVerifier.verifyGetResources(jni, name, callerClass));
if (allowed) {
JNIObjectHandle returnValue = jniFunctions(). getCallObjectMethod().invoke(jni, self, bp.method, name);
result = returnValue.notEqual(nullHandle());
if (clearException(jni)) {
result = false;
}
if (result && returnsEnumeration) {
result = hasEnumerationElements(jni, returnValue);
}
}
JNIObjectHandle selfClazz = nullHandle(); // self is java.lang.ClassLoader, get its class
if (self.notEqual(nullHandle())) {
selfClazz = jniFunctions().getGetObjectClass().invoke(jni, self);
if (clearException(jni)) {
selfClazz = nullHandle();
}
}
traceBreakpoint(jni, selfClazz, nullHandle(), callerClass, bp.specification.methodName, result, fromJniString(jni, name));
if (!allowed) {
forceGetResourceReturn(jni, returnsEnumeration);
}
return allowed;
}
private static boolean hasEnumerationElements(JNIEnvironment jni, JNIObjectHandle obj) {
boolean hasElements = jniFunctions(). getCallBooleanMethod().invoke(jni, obj, handles().javaUtilEnumerationHasMoreElements);
if (clearException(jni)) {
hasElements = false;
}
return hasElements;
}
private static void forceGetResourceReturn(JNIEnvironment env, boolean returnsEnumeration) {
JNIObjectHandle newResult = nullHandle();
if (returnsEnumeration) {
JNIObjectHandle javaUtilCollections = handles().getJavaUtilCollections(env);
JNIMethodId emptyEnumeration = handles().getJavaUtilCollectionsEmptyEnumeration(env);
if (javaUtilCollections.notEqual(nullHandle()) && emptyEnumeration.isNonNull()) {
newResult = jniFunctions(). getCallObjectMethod().invoke(env, javaUtilCollections, emptyEnumeration);
if (clearException(env)) {
newResult = nullHandle();
}
}
}
jvmtiFunctions().ForceEarlyReturnObject().invoke(jvmtiEnv(), nullHandle(), newResult);
}
private static boolean getSystemResource(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
return handleGetSystemResources(jni, callerClass, bp, false);
}
private static boolean getSystemResources(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
return handleGetSystemResources(jni, callerClass, bp, true);
}
private static boolean handleGetSystemResources(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp, boolean returnsEnumeration) {
JNIObjectHandle name = getObjectArgument(0);
boolean allowed = (resourceVerifier == null || resourceVerifier.verifyGetSystemResources(jni, name, callerClass));
boolean result = false;
if (allowed) {
JNIObjectHandle returnValue = jniFunctions(). getCallStaticObjectMethod().invoke(jni, bp.clazz, bp.method, name);
result = returnValue.notEqual(nullHandle());
if (clearException(jni)) {
result = false;
}
if (result && returnsEnumeration) {
result = hasEnumerationElements(jni, returnValue);
}
}
traceBreakpoint(jni, nullHandle(), nullHandle(), callerClass, bp.specification.methodName, result, fromJniString(jni, name));
if (!allowed) {
forceGetResourceReturn(jni, returnsEnumeration);
}
return allowed;
}
private static boolean newProxyInstance(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
JNIObjectHandle classLoader = getObjectArgument(0);
JNIObjectHandle ifaces = getObjectArgument(1);
Object ifaceNames = getClassArrayNames(jni, ifaces);
boolean allowed = proxyVerifier == null || proxyVerifier.verifyNewProxyInstance(jni, ifaceNames, callerClass);
boolean result = false;
if (allowed) {
JNIObjectHandle invokeHandler = getObjectArgument(2);
result = nullHandle().notEqual(jniFunctions(). getCallStaticObjectMethod()
.invoke(jni, bp.clazz, bp.method, classLoader, ifaces, invokeHandler));
if (clearException(jni)) {
result = false;
}
}
traceBreakpoint(jni, nullHandle(), nullHandle(), callerClass, bp.specification.methodName, result, TraceWriter.UNKNOWN_VALUE, ifaceNames, TraceWriter.UNKNOWN_VALUE);
if (!allowed) {
throwDeniedProxyException(jni, ifaceNames);
}
return allowed;
}
private static boolean getProxyClass(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp) {
JNIObjectHandle classLoader = getObjectArgument(0);
JNIObjectHandle ifaces = getObjectArgument(1);
Object ifaceNames = getClassArrayNames(jni, ifaces);
boolean allowed = proxyVerifier == null || proxyVerifier.verifyGetProxyClass(jni, ifaceNames, callerClass);
boolean result = false;
if (allowed) {
result = nullHandle().notEqual(jniFunctions(). getCallStaticObjectMethod()
.invoke(jni, bp.clazz, bp.method, classLoader, ifaces));
if (clearException(jni)) {
result = false;
}
}
traceBreakpoint(jni, nullHandle(), nullHandle(), callerClass, bp.specification.methodName, result, TraceWriter.UNKNOWN_VALUE, ifaceNames);
if (!allowed) {
throwDeniedProxyException(jni, ifaceNames);
}
return allowed;
}
private static void throwDeniedProxyException(JNIEnvironment jni, Object ifaceNames) {
String interfaceString = "(unknown)";
if (ifaceNames instanceof Object[]) {
interfaceString = Arrays.toString((Object[]) ifaceNames);
}
try (CCharPointerHolder message = toCString(Agent.MESSAGE_PREFIX + "configuration does not permit proxy class for interfaces: " + interfaceString)) {
jniFunctions().getThrowNew().invoke(jni, handles().javaLangSecurityException, message.get());
}
}
private static Object getClassArrayNames(JNIEnvironment jni, JNIObjectHandle classArray) {
Object classNames = TraceWriter.EXPLICIT_NULL;
if (classArray.notEqual(nullHandle())) {
classNames = TraceWriter.UNKNOWN_VALUE;
int length = jniFunctions().getGetArrayLength().invoke(jni, classArray);
if (!clearException(jni) && length >= 0) {
List list = new ArrayList<>();
for (int i = 0; i < length; i++) {
JNIObjectHandle clazz = jniFunctions().getGetObjectArrayElement().invoke(jni, classArray, i);
if (!clearException(jni)) {
list.add(getClassNameOr(jni, clazz, TraceWriter.EXPLICIT_NULL, TraceWriter.UNKNOWN_VALUE));
} else {
list.add(TraceWriter.UNKNOWN_VALUE);
}
}
classNames = list.toArray(new String[0]);
}
}
return classNames;
}
private static String asInternalSignature(Object paramTypesArray) {
if (paramTypesArray instanceof Object[]) {
StringBuilder sb = new StringBuilder("(");
for (Object paramType : (Object[]) paramTypesArray) {
sb.append(MetaUtil.toInternalName(paramType.toString()));
}
return sb.append(')').toString();
}
return null;
}
@CEntryPoint
@CEntryPointOptions(prologue = AgentIsolate.Prologue.class, epilogue = AgentIsolate.Epilogue.class)
private static void onBreakpoint(@SuppressWarnings("unused") JvmtiEnv jvmti, JNIEnvironment jni,
@SuppressWarnings("unused") JNIObjectHandle thread, JNIMethodId method, @SuppressWarnings("unused") long location) {
if (recursive.get()) {
return;
}
recursive.set(true);
try {
JNIObjectHandle callerClass = getCallerClass(1);
Breakpoint bp = installedBreakpoints.get(method.rawValue());
if (bp.specification.handler.dispatch(jni, callerClass, bp)) {
guarantee(!testException(jni));
}
} catch (Throwable t) {
VMError.shouldNotReachHere(t);
} finally {
recursive.set(false);
}
}
@CEntryPoint
@CEntryPointOptions(prologue = AgentIsolate.Prologue.class, epilogue = AgentIsolate.Epilogue.class)
private static void onNativeMethodBind(@SuppressWarnings("unused") JvmtiEnv jvmti, JNIEnvironment jni,
@SuppressWarnings("unused") JNIObjectHandle thread, JNIMethodId method, CodePointer address, WordPointer newAddressPtr) {
if (recursive.get()) {
return;
}
if (nativeBreakpoints != null) {
NativeBreakpoint bp = nativeBreakpoints.get(method.rawValue());
if (bp != null) {
bindNativeBreakpoint(jni, bp, newAddressPtr);
boundNativeMethods.put(method.rawValue(), address.rawValue());
}
} else { // breakpoints are not yet initialized, remember and install breakpoint later
boundNativeMethods.put(method.rawValue(), address.rawValue());
}
}
private static final CEntryPointLiteral onBreakpointLiteral = CEntryPointLiteral.create(BreakpointInterceptor.class, "onBreakpoint",
JvmtiEnv.class, JNIEnvironment.class, JNIObjectHandle.class, JNIMethodId.class, long.class);
private static final CEntryPointLiteral onNativeMethodBindLiteral = CEntryPointLiteral.create(BreakpointInterceptor.class, "onNativeMethodBind",
JvmtiEnv.class, JNIEnvironment.class, JNIObjectHandle.class, JNIMethodId.class, CodePointer.class, WordPointer.class);
public static void onLoad(JvmtiEnv jvmti, JvmtiEventCallbacks callbacks, TraceWriter writer, ReflectAccessVerifier verifier,
ProxyAccessVerifier prverifier, ResourceAccessVerifier resverifier) {
JvmtiCapabilities capabilities = UnmanagedMemory.calloc(SizeOf.get(JvmtiCapabilities.class));
check(jvmti.getFunctions().GetCapabilities().invoke(jvmti, capabilities));
capabilities.setCanGenerateBreakpointEvents(1);
capabilities.setCanAccessLocalVariables(1);
capabilities.setCanForceEarlyReturn(1);
capabilities.setCanGenerateNativeMethodBindEvents(1);
check(jvmti.getFunctions().AddCapabilities().invoke(jvmti, capabilities));
UnmanagedMemory.free(capabilities);
callbacks.setBreakpoint(onBreakpointLiteral.getFunctionPointer());
callbacks.setNativeMethodBind(onNativeMethodBindLiteral.getFunctionPointer());
BreakpointInterceptor.traceWriter = writer;
BreakpointInterceptor.accessVerifier = verifier;
BreakpointInterceptor.proxyVerifier = prverifier;
BreakpointInterceptor.resourceVerifier = resverifier;
Support.check(jvmti.getFunctions().SetEventNotificationMode().invoke(jvmti, JvmtiEventMode.JVMTI_ENABLE, JVMTI_EVENT_NATIVE_METHOD_BIND, nullHandle()));
}
public static void onVMInit(JvmtiEnv jvmti, JNIEnvironment jni) {
Map breakpoints = new HashMap<>(BREAKPOINT_SPECIFICATIONS.length);
Map nativeBrkpts = new HashMap<>(NATIVE_BREAKPOINT_SPECIFICATIONS.length);
JNIObjectHandle lastClass = nullHandle();
String lastClassName = null;
for (BreakpointSpecification br : BREAKPOINT_SPECIFICATIONS) {
JNIObjectHandle clazz;
if (lastClassName != null && lastClassName.equals(br.className)) {
clazz = lastClass;
} else {
clazz = resolveBreakpointClass(jni, br.className, br.optional);
lastClass = clazz;
lastClassName = br.className;
}
JNIMethodId method = resolveBreakpointMethod(jni, clazz, br.methodName, br.signature, br.optional);
JvmtiError result = jvmtiFunctions().SetBreakpoint().invoke(jvmti, method, 0L);
if (result == JvmtiError.JVMTI_ERROR_NONE) {
breakpoints.put(method.rawValue(), new Breakpoint(br, clazz, method));
} else {
guarantee(br.optional, "Setting breakpoint failed");
}
}
for (NativeBreakpointSpecification br : NATIVE_BREAKPOINT_SPECIFICATIONS) {
JNIObjectHandle clazz;
if (lastClassName != null && lastClassName.equals(br.className)) {
clazz = lastClass;
} else {
clazz = resolveBreakpointClass(jni, br.className, br.optional);
lastClass = clazz;
lastClassName = br.className;
}
JNIMethodId method = resolveBreakpointMethod(jni, clazz, br.methodName, br.signature, br.optional);
if (method.isNonNull()) {
NativeBreakpoint bp = new NativeBreakpoint(br, clazz, method);
nativeBrkpts.put(method.rawValue(), bp);
if (boundNativeMethods.containsKey(method.rawValue())) {
bindNativeBreakpoint(jni, bp, nullPointer());
}
}
}
boundNativeMethods.keySet().retainAll(nativeBrkpts.keySet()); // others no longer needed
installedBreakpoints = breakpoints;
nativeBreakpoints = nativeBrkpts;
Support.check(jvmti.getFunctions().SetEventNotificationMode().invoke(jvmti, JvmtiEventMode.JVMTI_ENABLE, JVMTI_EVENT_BREAKPOINT, nullHandle()));
}
private static JNIObjectHandle resolveBreakpointClass(JNIEnvironment jni, String className, boolean optional) {
JNIObjectHandle clazz;
try (CCharPointerHolder cname = toCString(className)) {
clazz = jniFunctions().getFindClass().invoke(jni, cname.get());
if (optional && (clearException(jni) || clazz.equal(nullHandle()))) {
return nullHandle();
}
checkNoException(jni);
}
clazz = jniFunctions().getNewGlobalRef().invoke(jni, clazz);
checkNoException(jni);
return clazz;
}
private static JNIMethodId resolveBreakpointMethod(JNIEnvironment jni, JNIObjectHandle clazz, String methodName, String signature, boolean optional) {
if (optional && clazz.equal(nullHandle())) {
return nullPointer();
}
guarantee(clazz.notEqual(nullHandle()));
JNIMethodId method;
try (CCharPointerHolder cname = toCString(methodName); CCharPointerHolder csignature = toCString(signature)) {
method = jniFunctions().getGetMethodID().invoke(jni, clazz, cname.get(), csignature.get());
if (method.isNull()) {
clearException(jni);
method = jniFunctions().getGetStaticMethodID().invoke(jni, clazz, cname.get(), csignature.get());
}
}
if (optional && (clearException(jni) || method.isNull())) {
return nullPointer();
}
guarantee(!testException(jni) && method.isNonNull());
return method;
}
private static void bindNativeBreakpoint(JNIEnvironment jni, NativeBreakpoint bp, WordPointer newAddressPtr) {
assert !recursive.get();
CFunctionPointer breakpointMethod = bp.specification.handlerLiteral.getFunctionPointer();
if (newAddressPtr.isNonNull()) {
newAddressPtr.write(breakpointMethod);
} else {
recursive.set(true);
try (CCharPointerHolder cname = toCString(bp.specification.methodName);
CCharPointerHolder csignature = toCString(bp.specification.signature)) {
JNINativeMethod nativeMethod = StackValue.get(JNINativeMethod.class);
nativeMethod.setName(cname.get());
nativeMethod.setSignature(csignature.get());
nativeMethod.setFnPtr(breakpointMethod);
checkJni(jni.getFunctions().getRegisterNatives().invoke(jni, bp.clazz, nativeMethod, 1));
} finally {
recursive.set(false);
}
}
}
public static void onUnload(JNIEnvironment env) {
installedBreakpoints.values().stream().map(bp -> bp.clazz.rawValue()).distinct().forEach(
ref -> jniFunctions().getDeleteGlobalRef().invoke(env, WordFactory.pointer(ref)));
installedBreakpoints = null;
accessVerifier = null;
proxyVerifier = null;
resourceVerifier = null;
traceWriter = null;
}
private interface BreakpointHandler {
boolean dispatch(JNIEnvironment jni, JNIObjectHandle callerClass, Breakpoint bp);
}
private static final BreakpointSpecification[] BREAKPOINT_SPECIFICATIONS = {
brk("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", BreakpointInterceptor::forName),
brk("java/lang/Class", "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;", BreakpointInterceptor::forName),
brk("java/lang/Class", "getFields", "()[Ljava/lang/reflect/Field;", BreakpointInterceptor::getFields),
brk("java/lang/Class", "getMethods", "()[Ljava/lang/reflect/Method;", BreakpointInterceptor::getMethods),
brk("java/lang/Class", "getConstructors", "()[Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructors),
brk("java/lang/Class", "getDeclaredFields", "()[Ljava/lang/reflect/Field;", BreakpointInterceptor::getDeclaredFields),
brk("java/lang/Class", "getDeclaredMethods", "()[Ljava/lang/reflect/Method;", BreakpointInterceptor::getDeclaredMethods),
brk("java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getDeclaredConstructors),
brk("java/lang/Class", "getField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;", BreakpointInterceptor::getField),
brk("java/lang/Class", "getDeclaredField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;", BreakpointInterceptor::getDeclaredField),
brk("java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", BreakpointInterceptor::getMethod),
brk("java/lang/Class", "getConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructor),
brk("java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", BreakpointInterceptor::getDeclaredMethod),
brk("java/lang/Class", "getDeclaredConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructor),
brk("java/lang/Class", "getEnclosingMethod", "()Ljava/lang/reflect/Method;", BreakpointInterceptor::getEnclosingMethod),
brk("java/lang/Class", "getEnclosingConstructor", "()Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getEnclosingMethod),
brk("java/lang/Class", "newInstance", "()Ljava/lang/Object;", BreakpointInterceptor::newInstance),
brk("java/lang/ClassLoader", "getResource", "(Ljava/lang/String;)Ljava/net/URL;", BreakpointInterceptor::getResource),
brk("java/lang/ClassLoader", "getResources", "(Ljava/lang/String;)Ljava/util/Enumeration;", BreakpointInterceptor::getResources),
brk("java/lang/ClassLoader", "getSystemResource", "(Ljava/lang/String;)Ljava/net/URL;", BreakpointInterceptor::getSystemResource),
brk("java/lang/ClassLoader", "getSystemResources", "(Ljava/lang/String;)Ljava/util/Enumeration;", BreakpointInterceptor::getSystemResources),
/*
* NOTE: get(System)ResourceAsStream() generallys call get(System)Resource(), no
* additional breakpoints necessary
*/
brk("java/lang/reflect/Proxy", "getProxyClass", "(Ljava/lang/ClassLoader;[Ljava/lang/Class;)Ljava/lang/Class;", BreakpointInterceptor::getProxyClass),
brk("java/lang/reflect/Proxy", "newProxyInstance",
"(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;", BreakpointInterceptor::newProxyInstance),
// In Java 9+, these are Java methods that call private methods
optionalBrk("sun/misc/Unsafe", "objectFieldOffset", "(Ljava/lang/reflect/Field;)J", BreakpointInterceptor::objectFieldOffset),
optionalBrk("jdk/internal/misc/Unsafe", "objectFieldOffset", "(Ljava/lang/reflect/Field;)J", BreakpointInterceptor::objectFieldOffset),
optionalBrk("jdk/internal/misc/Unsafe", "objectFieldOffset", "(Ljava/lang/Class;Ljava/lang/String;)J", BreakpointInterceptor::objectFieldOffsetByName),
};
private static final NativeBreakpointSpecification[] NATIVE_BREAKPOINT_SPECIFICATIONS = {
optionalNativeBrk("sun/misc/Unsafe", "objectFieldOffset", "(Ljava/lang/reflect/Field;)J", nativeObjectFieldOffsetLiteral),
};
private static BreakpointSpecification brk(String className, String methodName, String signature, BreakpointHandler handler) {
return new BreakpointSpecification(className, methodName, signature, handler, false);
}
private static BreakpointSpecification optionalBrk(String className, String methodName, String signature, BreakpointHandler handler) {
return new BreakpointSpecification(className, methodName, signature, handler, true);
}
private static NativeBreakpointSpecification optionalNativeBrk(String className, String methodName, String signature, CEntryPointLiteral> handlerLiteral) {
return new NativeBreakpointSpecification(className, methodName, signature, handlerLiteral, true);
}
private abstract static class AbstractBreakpointSpecification {
final String className;
final String methodName;
final String signature;
final boolean optional;
AbstractBreakpointSpecification(String className, String methodName, String signature, boolean optional) {
this.className = className;
this.methodName = methodName;
this.signature = signature;
this.optional = optional;
}
}
private static class BreakpointSpecification extends AbstractBreakpointSpecification {
final BreakpointHandler handler;
BreakpointSpecification(String className, String methodName, String signature, BreakpointHandler handler, boolean optional) {
super(className, methodName, signature, optional);
this.handler = handler;
}
}
private static class NativeBreakpointSpecification extends AbstractBreakpointSpecification {
final CEntryPointLiteral> handlerLiteral;
NativeBreakpointSpecification(String className, String methodName, String signature, CEntryPointLiteral> handlerLiteral, boolean optional) {
super(className, methodName, signature, optional);
this.handlerLiteral = handlerLiteral;
}
}
private abstract static class AbstractBreakpoint {
final T specification;
final JNIObjectHandle clazz;
final JNIMethodId method;
AbstractBreakpoint(T specification, JNIObjectHandle clazz, JNIMethodId method) {
this.specification = specification;
this.clazz = clazz;
this.method = method;
}
}
private static final class Breakpoint extends AbstractBreakpoint {
Breakpoint(BreakpointSpecification specification, JNIObjectHandle clazz, JNIMethodId method) {
super(specification, clazz, method);
}
}
private static final class NativeBreakpoint extends AbstractBreakpoint {
NativeBreakpoint(NativeBreakpointSpecification specification, JNIObjectHandle clazz, JNIMethodId method) {
super(specification, clazz, method);
}
}
private BreakpointInterceptor() {
}
}