I am trying to call a Java method from the code. C code listens to either Escape, Shift, Ctrl key press, then it calls the Java method telling which key was pressed. Following are the snippets that play a role in this.
C Snippet:
mid = (*env)->GetMethodID(env,cls,"callBack","(Ljava/lang/String;)V");
Env = env;
if(called)
switch(param) {
case VK_CONTROL:
printf("Control pressed !\n");
(*Env)->CallVoidMethodA(Env,Obj,mid,"11"); // calling the java method
break;
case VK_SHIFT:
printf("Shift pressed !\n");
(*Env)->CallVoidMethodA(Env,Obj,mid,"10"); // calling the java method
break;
case VK_ESCAPE:
printf("Escape pressed !\n");
(*Env)->CallVoidMethodA(Env,Obj,mid,"1B"); // calling the java method
break;
default:
printf("The default case\n");
break;
}
Java Snippet:
public void callBack(String key) {
String x = KeyEvent.getKeyText(Integer.parseInt(key, 16));
System.out.println(x);
}
When I run the program and press the Escape key I get this on the console:
Escape pressed !
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x5c8b809a, pid=7588, tid=8088
#
# JRE version: 7.0
# Java VM: Java HotSpot(TM) Client VM (20.0-b01 mixed mode, sharing windows-x86 )
# Problematic frame:
# V [jvm.dll+0x19809a]
#
# An error report file with more information is saved as:
# W:\UnderTest\NetbeansCurrent\KeyLoggerTester\build\classes\hs_err_pid7588.log
#
# If you would like to submit a bug report, please visit:
# http://java.sun.com/webapps/bugreport/crash.jsp
#
I know I am calling the Java function the wrong way, but I don't know where I am wrong. As from the output, it satisfies the case when I press the Escape key and then an unexpected error occurs.
Link to the LOG FILE
EDIT:
After the answer by mavroprovato I still get the same errors.
I edited this way:
(*Env)->CallVoidMethodA(Env,Obj,mid,(*Env)->NewStringUTF(Env,"1B"));
EDIT:
COMPLETE CODE version 1
COMPLETE CODE version 2
The JVM is crashing because the JNIEnv that is used is not a valid one. There are other issues with the code as well.
The Sun JNI documentation is providing very good information regarding threads.
Here comes some parts that are obvious:
Create a JNI_OnLoad function in your code. It will be called when the library is loaded. Then cache the JavaVM pointer because that is valid across threads. An alternative is to call (*env)->GetJavaVM in the initializeJNIVars function but I prefer the first one.
In your initializeJNIVars you can save the obj reference by calling Obj = (*env)->NewGlobalRef(obj).
In the LowLevelKeyboardProc you will have to get the env pointer:
AttachCurrentThread(JavaVM *jvm, JNIEnv &env, NULL);
Edit
OK, here are the code that you should add to get it working, I have tried it myself and it works. NB: I have not analyzed what your code is actually doing so I just did some fixes to get it working.
Add these variables among your other global variables:
static JavaVM *javaVM = NULL;
static jmethodID callbackMethod = NULL;
static jobject callbackObject = NULL;
You can remove your cls, mid, Env and Obj variables and use mine instead.
Create the JNI_OnLoad method where you cache the JavaVM pointer:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
JNIEnv *env = 0;
if ((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_4)) {
return JNI_ERR;
}
javaVM = jvm;
return JNI_VERSION_1_4;
}
Alter your initializeJNIVars to look like the following:
void Java_keylogger_TestKeys_initializeJNIVars(JNIEnv *env, jobject obj) {
jclass cls = (*env)->GetObjectClass(env,obj);
callbackMethod = (*env)->GetMethodID(env, cls, "callBack", "(Ljava/lang/String;)V");
callbackObject = (*env)->NewGlobalRef(env, obj);
if(cls == NULL || callbackMethod == NULL) {
printf("One of them is null \n");
}
called = TRUE;
}
And finally in your LowLoevelKeyboardProc code you will have to add the following:
...
WPARAM param = kbhook->vkCode;
JNIEnv *env;
jint rs = (*javaVM)->AttachCurrentThread(javaVM, (void**)&env, NULL);
if (rs != JNI_OK) {
return NULL; // Or something appropriate...
}
...
case VK_ESCAPE:
printf("Escape pressed !\n");
jstring message = (*env)->NewStringUTF(env, "1B");
(*env)->CallVoidMethod(env, callbackObject, callbackMethod, message);
break;
...
In your unregisterWinHook you should delete the global reference so that objects can be GC'd.
...
(*env)->DeleteGlobalRef(env, callbackObject);
And that's it.
I believe you cannot call a java method that takes a String parameter and pass it a char*. You should call NewStringUTF first.
I think it is due to the UAC feature enabled on your Operating System. This was a bug for Java 6. Read this for further reference.
The reason I say this is because the event to the escape key is fired correctly and the problem only begins as soon as the call to the java method is done.
Related
I'm new to JNI and I want to create an ArrayList<Boolean> from the C++ side.
I have the following situation:
import java.util.ArrayList;
public class createArrayJNI {
static {
System.loadLibrary("libnative");
}
public static void main(String[] args) {
createArrayJNI jni = new createArrayJNI();
ArrayList<Boolean> array = jni.creatArray();
System.err.println(array);
}
public native ArrayList<Boolean> creatArray();
}
and my cpp file is:
...
JNIEXPORT jobject JNICALL Java_createArrayJNI_creatArray
(JNIEnv \*env, jobject thisObject) {
jclass java_util_class = env->FindClass("java/util/ArrayList");
jmethodID java_util_method_constructor = env->GetMethodID(java_util_class, "<init>", "()V");
jmethodID java_add_method = env->GetMethodID(java_util_class, "add", "(Ljava/lang/Object;)Z");
jobject java_util_object = env->NewObject(java_util_class, java_util_method_constructor, "");
jboolean a = true;
env->CallBooleanMethod(java_util_object, java_add_method, a);
return java_util_object;
}
It shows me:
#
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0x00000001099110ac, pid=24207, tid=4355
#
# JRE version: OpenJDK Runtime Environment Homebrew (19.0.1) (build 19.0.1)
# Java VM: OpenJDK 64-Bit Server VM Homebrew (19.0.1, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, bsd-aarch64)
# Problematic frame:
# V \[libjvm.dylib+0x1f10ac\] AccessInternal::PostRuntimeDispatch\<G1BarrierSet::AccessBarrier\<598116ull, G1BarrierSet\>, (AccessInternal::BarrierType)2, 598116ull\>::oop_access_barrier(void\*)+0xc
#
# No core dump will be written. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /Users/taki/Desktop/Preparation doctorat ERM/Projects/Julia4C/hs_err_pid24207.log
#
# If you would like to submit a bug report, please visit:
# https://github.com/Homebrew/homebrew-core/issues
#
zsh: abort /usr/bin/env --enable-preview -XX:+ShowCodeDetailsInExceptionMessages -cp
I'm pretty sure that the error comes from env->CallBooleanMethod(java_util_object, java_add_method, a); because the add method requires an object, and a is jboolean.
I tried to cast the a variable but it didn't work as well.
ArrayList<Boolean> is the same as just ArrayList. At runtime, the <Boolean> doesn't exist. This is called "type erasure".
The next thing to notice is that you did not try to add a Boolean (a kind of Object) but a primitive boolean. This probably gets interpreted as an object pointer and since it's not null it's the address of an Object - but true (the number 1) isn't a valid address of anything, and when the JVM dereferences it it crashes.
You need to get the static field Boolean.TRUE and add that to the ArrayList.
I found a way to instantiate a java Boolean Object from c++ using
Boolean.valueOf() function.
I first get staticMethodId and then use it to create jobject and use the same object in the add method as follow:
JNIEXPORT jobject JNICALL Java_createArrayJNI_creatArray
(JNIEnv *env, jobject thisObject) {
std::vector<bool> cpp_vec;
cpp_vec.push_back(true);
cpp_vec.push_back(false);
cpp_vec.push_back(true);
jclass java_util_class = env->FindClass("java/util/ArrayList");
jmethodID java_util_method_constructor = env->GetMethodID(java_util_class, "<init>", "()V");
jmethodID java_add_method = env->GetMethodID(java_util_class, "add", "(Ljava/lang/Object;)Z");
jobject java_util_object = env->NewObject(java_util_class, java_util_method_constructor, "");
jclass bool_class = env->FindClass("java/lang/Boolean");
jmethodID bool_init = env->GetStaticMethodID(bool_class, "valueOf", "(Z)Ljava/lang/Boolean;");
for (int i=0; i< cpp_vec.size(); i++){
jboolean curr_element = cpp_vec[i];
jobject bool_object = env->CallStaticObjectMethod(bool_class, bool_init, (jboolean) cpp_vec[i]);
if (bool_object == NULL){
std::cout << "Cannot instantiate the Boolean object";
return NULL;
}
env->CallBooleanMethod(java_util_object, java_add_method, bool_object);
}
return java_util_object;
}
I have a C (navive) program and a jar file with the main() method. From my native program I am initializing the JVM, and calling the main() method. I have no problems with this, everything is completely fine. But then I wanted to call back a C function from my java code.
The C function is defined in the native code in the same module as the one, that have created the JVM. The header is auto-generated, and the body is as simple as this:
JNIEXPORT void JNICALL Java_eu_raman_chakhouski_NativeUpdaterBus_connect0(JNIEnv* env, jclass clazz)
{
return;
}
So, from the java code I'm calling NativeUpdaterBus.connect0(), continuosly getting an UnsatisfiedLinkError. I have no System.loadLibrary() calls in my java code, because I thought, that there will be no problems calling the native code back from the java code if the target module is (possibly?) already loaded.
Well, maybe my approach is completely incorrect, but I can't see any obvious defects, maybe you could help?
What possibly could help (but I didn't tried any of these approaches, because I'm still not quite sure)
Use a kind of a "trampoline" dynamic library with these JNI methods, load it from the java code, then marshal native calls through it.
Define a java.lang.Runnable's anonymous inheritor, created with jni_env->DefineClass() but this involves some bytecode trickery.
Use an another, less invasive approach, like sockets, named pipes, etc. But in my case I'm using only one native process, so this might be an overkill.
I'm using OpenJDK 11.0.3 and Windows 10. My C program is compiled with the Microsoft cl.exe 19.16.27031.1 for x64 (Visual Studio 2017).
One possibility, as others have already mentioned, is to create a shared library (.dll) and call it from the native code and from Java to exchange data.
However, if you want to callback to a C function defined in the native code in the same module as the one the JVM originally created, you can use RegisterNatives.
Simple Example
C program creates JVM
it calls a Main of a class
the Java Main calls back a C function named connect0 in the calling C code
to have a test case the native C function constructs a Java string and returns it
the Java side prints the result
Java
package com.software7.test;
public class Main {
private native String connect0() ;
public static void main(String[] args) {
Main m = new Main();
m.makeTest(args);
}
private void makeTest(String[] args) {
System.out.println("Java: main called");
for (String arg : args) {
System.out.println(" -> Java: argument: '" + arg + "'");
}
String res = connect0(); //callback into native code
System.out.println("Java: result of connect0() is '" + res + "'"); //process returned String
}
}
C Program
One can create the Java VM in C as shown here
(works not only with cygwin but still with VS 2019) and then register with RegisterNatives native C callbacks. So using the function invoke_class from the link above it could look like this:
#include <stdio.h>
#include <windows.h>
#include <jni.h>
#include <stdlib.h>
#include <stdbool.h>
...
void invoke_class(JNIEnv* env) {
jclass helloWorldClass;
jmethodID mainMethod;
jobjectArray applicationArgs;
jstring applicationArg0;
helloWorldClass = (*env)->FindClass(env, "com/software7/test/Main");
mainMethod = (*env)->GetStaticMethodID(env, helloWorldClass, "main", "([Ljava/lang/String;)V");
applicationArgs = (*env)->NewObjectArray(env, 1, (*env)->FindClass(env, "java/lang/String"), NULL);
applicationArg0 = (*env)->NewStringUTF(env, "one argument");
(*env)->SetObjectArrayElement(env, applicationArgs, 0, applicationArg0);
(*env)->CallStaticVoidMethod(env, helloWorldClass, mainMethod, applicationArgs);
}
jstring connect0(JNIEnv* env, jobject thiz);
static JNINativeMethod native_methods[] = {
{ "connect0", "()Ljava/lang/String;", (void*)connect0 },
};
jstring connect0(JNIEnv* env, jobject thiz) {
printf("C: connect0 called\n");
return (*env)->NewStringUTF(env, "Some Result!!");
}
static bool register_native_methods(JNIEnv* env) {
jclass clazz = (*env)->FindClass(env, "com/software7/test/Main");
if (clazz == NULL) {
return false;
}
int num_methods = sizeof(native_methods) / sizeof(native_methods[0]);
if ((*env)->RegisterNatives(env, clazz, native_methods, num_methods) < 0) {
return false;
}
return true;
}
int main() {
printf("C: Program starts, creating VM...\n");
JNIEnv* env = create_vm();
if (env == NULL) {
printf("C: creating JVM failed\n");
return 1;
}
if (!register_native_methods(env)) {
printf("C: registering native methods failed\n");
return 1;
}
invoke_class(env);
destroy_vm();
getchar();
return 0;
}
Result
Links
Creating a JVM from a C Program: http://www.inonit.com/cygwin/jni/invocationApi/c.html
Registering Native Methods: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/functions.html#registering-native-methods
System.loadLibrary() is essential for the jni lookup to work. You also have a more flexible System.load() alternative.
Make sure that the native method implementation is declared with extern "C" and is not hidden by linker.
My JNI library works flawlessly on Windows, however, on Linux I always get a strange segmentation fault.
siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x0000000000000000
The stack crace from the crash file is this:
C [libfmodjavaL.so+0xfb8c] JNIEnv_::GetStaticObjectField(_jclass*, _jfieldID*)+0x18
C [libfmodjavaL.so+0xf72b] Logger::sendToSystemOut(bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)+0x75
C [libfmodjavaL.so+0xf7c2] Logger::log(char const*)+0x4c
C [libfmodjavaL.so+0xd70d] fmodDebugCallback(unsigned int, char const*, int, char const*, char const*)+0x127
So it appears that it crashed when calling GetStaticObject field in the Logger class. This is that method:
void Logger::sendToSystemOut(bool error, std::string message) {
JNIEnv* jni = FMODWrapper::utils->getJNI();
jobject printStream;
if (error) {
printStream = jni->GetStaticObjectField(this->systemClass, this->errFieldID);
} else {
printStream = jni->GetStaticObjectField(this->systemClass, this->outFieldID);
}
jobject messageString = jni->NewStringUTF(message.c_str());
jni->CallObjectMethod(printStream, this->printlnMethodID, messageString);
}
So I'm guessing something's not right about storing the class and field IDs of these fields. But the weird thing is, I get logging output when my library starts up, even from FMOD, which the fmodDebugCallback gets called by.
Logger::Logger(const char* name) {
this->name = name;
JNIEnv* jni = FMODWrapper::utils->getJNI();
this->systemClass = FMODWrapper::utils->findClass("java/lang/System");
this->outFieldID = jni->GetStaticFieldID(this->systemClass, "out", "Ljava/io/PrintStream;");
this->errFieldID = jni->GetStaticFieldID(this->systemClass, "err", "Ljava/io/PrintStream;");
jclass printStreamClass = FMODWrapper::utils->findClass("java/io/PrintStream");
this->printlnMethodID = jni->GetMethodID(printStreamClass, "println", "(Ljava/lang/String;)V");
}
So, logging works flawlessly on Windows, but after some time crashes on Linux. Compiled with g++ on Fedora 29 64-bit.
Update: my method for getting a JNIEnv*
JNIEnv* Utils::getJNI() {
JNIEnv* jni;
int getEnvResult = FMODWrapper::jvm->GetEnv((void**) &jni, JNI_VERSION_1_6);
if (getEnvResult == JNI_EDETACHED) {
FMODWrapper::jvm->AttachCurrentThread(ANDROID_VOIDPP_CAST &jni, nullptr);
}
return jni;
}
Update 2: the code itself works up to a certain point since I'm getting log messages. Might be something to do with threads?
https://hastebin.com/kuzefuwawu.txt
systemClass, errFieldId, and outFieldID are all obtained from a different JNIEnv.
The JNIEnv cannot be cached:
Keeping a global reference to the JNIEnv environment
Just as it cannot be cached, you cannot store ids that were obtained from the other JNIEnv that you should no longer be using, nor should you be using anything that came from it. You need to get them all from the current valid JNIEnv.
The problem is not with thread affinity of class references or field IDs. The problem is with using a local class reference out of its scope. This is an implementation detail of some JVMs, that local references do not actually expire.
The fix would be to use
Logger::Logger(const char* name) {
this->name = name;
JNIEnv* jni = FMODWrapper::utils->getJNI();
this->systemClass = jni->NewGlobalRef(jni->findClass("java/lang/System"));
…
I cache a reference to a Java object in my native code, just like this:
// java global reference deleter
// _JAVA_ENV is an instance of JNIEnv that is cached globally and just
// valid in current thread scope
static void g_java_ref_deleter(jobject ptr) {
_JAVA_ENV->DeleteGlobalRef(ptr);
}
// native class caches a java object reference
class NativeA {
private:
shared_ptr<_jobject> M_java_object;
public:
setJavaObject(jobject obj) {
M_java_object = shared_ptr<_jobject>(_JAVA_ENV->NewGlobalRef(obj), g_java_ref_deleter);
}
shared_ptr<_jobject> getJavaObject() const {
return M_java_object;
}
}
and I access it in another native class:
class NativeB {
public:
void doSomething(NativeA& a) {
// here I got an error: accessed stale weak global reference
// make_record do something on java object (set float field actually)
make_record(a.getJavaObject().get());
}
}
This code run onto Android 4.3. Why do I get this error and how can I fix it?
OK, I've solved this problem! Actually I cached _JAVA_ENV and use it mistakenly. From this blog I found :
Although any given JNIEnv* is only valid for use on one thread, because Android never had any per-thread state in a JNIEnv*, it used to be possible to get away with using a JNIEnv* on the wrong thread. Now there’s a per-thread local reference table, it’s vital that you only use a JNIEnv* on the right thread.
So I thought there is no problem that I cache the JNIEnv and use it in one thread, but actually the JNIEnv was stale when the program get into java environment and returned to native environment. (eh... forgive my poor English)
And from the documentation, I found :
If a piece of code has no other way to get its JNIEnv, you should share the JavaVM, and use GetEnv to discover the thread's JNIEnv.
So, you should cache the JavaVM, and use it to get JNIEnv, the code would be like this :
JavaVM* g_jvm;
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
g_jvm = vm;
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
// Get jclass with env->FindClass.
// Register methods with env->RegisterNatives.
return JNI_VERSION_1_6;
}
JNIEnv* getJNIEnv() {
JNIEnv* env;
g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6 /*version*/);
return env;
}
Hope can help someone! (please forgive my poor English again..)
To support a static-analysis tool I want to instrument or monitor a Java program in such a way that I can determine for every reflective call (like Method.invoke(..)):
1.) which class C this method is invoked on, and
2.) which classloader loaded this class C.
Ideally I am looking for a solution that does not require me to statically modify the Java Runtime Library, i.e. I am looking for a load-time solution. However, the solution should be able to capture all reflective calls, even such calls that occur within the Java Runtime Library itself. (I played around with ClassFileTransformer but this seems to be applied only to classes that ClassFileTransformer itself does not depend on. In particular, a ClassFileTransfomer is not applied to the class "Class".)
Thanks!
Are you looking for something that can run in production? Or is it sufficient to instrument the application running in a test environment? If it's the latter, might want to consider running the application under a profiling tool. I've personally used and would recommend JProfiler, which lets you do call tracing and set up triggers to perform actions like logging when specific methods are invoked. It does not require any modifications to the hosted program and works just fine on the Java runtime library. There are open source tools, too, but I have not had as much success getting those to work.
If you need something that will run in production, you might want to investigate implementing your own custom Classloader or byte code manipulation via Javassist or CGLib, perhaps using AspectJ (AOP). That's obviously a more complication solution and I'm not sure it'll work without compile-time support, so hopefully the profiling tool is feasible for your situation.
The API that you are probably after is JVMTI. JVMTI allows you to register callbacks for the majority of events that occur within the JVM, including MethodEntry, MethodExit. You listen for those events and pull out the Method.invoke events. There are API calls to get the classloader for a specific class. However you will have to write tool in C or C++.
Here is an example that will get the filter out the java.lang.reflect.Method.invoke call and print it out. To get details regarding the object that has been called you will probably need to look at the stack frame.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <jvmti.h>
static jvmtiEnv *jvmti = NULL;
static jvmtiCapabilities capa;
static jint check_jvmti_error(jvmtiEnv *jvmti,
jvmtiError errnum,
const char *str) {
if (errnum != JVMTI_ERROR_NONE) {
char *errnum_str;
errnum_str = NULL;
(void) (*jvmti)->GetErrorName(jvmti, errnum, &errnum_str);
printf("ERROR: JVMTI: %d(%s): %s\n",
errnum,
(errnum_str == NULL ? "Unknown" : errnum_str),
(str == NULL ? "" : str));
return JNI_ERR;
}
return JNI_OK;
}
void JNICALL callbackMethodEntry(jvmtiEnv *jvmti_env,
JNIEnv* jni_env,
jthread thread,
jmethodID method) {
char* method_name;
char* method_signature;
char* generic_ptr_method;
char* generic_ptr_class;
char* class_name;
jvmtiError error;
jclass clazz;
error = (*jvmti_env)->GetMethodName(jvmti_env,
method,
&method_name,
&method_signature,
&generic_ptr_method);
if (check_jvmti_error(jvmti_env, error, "Failed to get method name")) {
return;
}
if (strcmp("invoke", method_name) == 0) {
error
= (*jvmti_env)->GetMethodDeclaringClass(jvmti_env, method,
&clazz);
if (check_jvmti_error(jvmti_env, error,
"Failed to get class for method")) {
(*jvmti_env)->Deallocate(jvmti_env, method_name);
(*jvmti_env)->Deallocate(jvmti_env, method_signature);
(*jvmti_env)->Deallocate(jvmti_env, generic_ptr_method);
return;
}
error = (*jvmti_env)->GetClassSignature(jvmti_env, clazz, &class_name,
&generic_ptr_class);
if (check_jvmti_error(jvmti_env, error, "Failed to get class signature")) {
(*jvmti_env)->Deallocate(jvmti_env, method_name);
(*jvmti_env)->Deallocate(jvmti_env, method_signature);
(*jvmti_env)->Deallocate(jvmti_env, generic_ptr_method);
return;
}
if (strcmp("Ljava/lang/reflect/Method;", class_name) == 0) {
printf("Method entered: %s.%s.%s\n", class_name, method_name,
method_signature);
}
(*jvmti_env)->Deallocate(jvmti_env, class_name);
(*jvmti_env)->Deallocate(jvmti_env, generic_ptr_class);
}
(*jvmti_env)->Deallocate(jvmti_env, method_name);
(*jvmti_env)->Deallocate(jvmti_env, method_signature);
(*jvmti_env)->Deallocate(jvmti_env, generic_ptr_method);
}
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
jint result;
jvmtiError error;
jvmtiEventCallbacks callbacks;
result = (*jvm)->GetEnv(jvm, (void**) &jvmti, JVMTI_VERSION_1_0);
if (result != JNI_OK || jvmti == NULL) {
printf("error\n");
return JNI_ERR;
} else {
printf("loaded agent\n");
}
(void) memset(&capa, 0, sizeof(jvmtiCapabilities));
capa.can_generate_method_entry_events = 1;
error = (*jvmti)->AddCapabilities(jvmti, &capa);
if (check_jvmti_error(jvmti, error, "Unable to set capabilities") != JNI_OK) {
return JNI_ERR;
}
(void) memset(&callbacks, 0, sizeof(callbacks));
callbacks.MethodEntry = &callbackMethodEntry;
error = (*jvmti)->SetEventCallbacks(jvmti,
&callbacks,
(jint) sizeof(callbacks));
if (check_jvmti_error(jvmti, error, "Unable to set callbacks") != JNI_OK) {
return JNI_ERR;
}
error = (*jvmti)->SetEventNotificationMode(jvmti,
JVMTI_ENABLE,
JVMTI_EVENT_METHOD_ENTRY,
(jthread) NULL);
if (check_jvmti_error(jvmti, error,
"Unable to set method entry notifications") != JNI_OK) {
return JNI_ERR;
}
return JNI_OK;
}