Android invoke JVMTI from java side via JNI failed, pointer is NULL - java

I had wrote a agent to use the JVMTI on android 9. Code like this, I create an AgentFunction object to monitor the VM. It work's fine.
AgentFunction *agent = 0;
JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
agent = new AgentFunction();
agent->Init(vm);
agent->ParseOptions(options);
agent->AddCapability();
agent->ShowCapabilities();
agent->RegisterEvent();
return JNI_OK;
}
Then i want export some interface to java, than user can invoke the JVMTI function directly.
private native boolean applyChangeNative(List<ClassInfo> classes);
The JNI fumction in agent.so
extern "C"
JNIEXPORT jboolean JNICALL Java_com_cc_jvmtiagent_JVMTIFunction_applyChangeNative
(JNIEnv *jniEnv, jobject, jlong jvmti, jobject classInfo) {
...
jvmtiClassDefinition *def = new jvmtiClassDefinition[total_classes];
agent->RedefineClasses(total_classes, def);
}
But when invoke the native method applyChangeNative from JAVA, the agent->RedefineClasses crash caused by agent is null. After my test, i found i can't access the object create in JVMTI from JNI.
I had read the JDK souce code , I found it have an InvocationAdapter.cc, When Agent_OnAttach it create the JPLISAgent, then create java.lang.instrument.Instrumentation and save the JPLISAgent in it. Each function from Java will take the JPLISAgent point.
But i want to known , why access the JVMTI object is NULL directly from JNI?
Resolved:
If you want invoke the agent method via JNI, you should use System.Load(agentPath) instead of System.LoadLibrary(libName). It need use the same so file.
It work's on Android 9 and 10, But on Android 8.x, Can't access the agent, i do not know why.

Related

Does callback copy jnienv, jinstance inside a JNI function?

The lambda that I pass to builder is populated into className object, and called at regular intervals (every hour) of time to refresh the other members. It gets called the first time successfully. I'm not sure if the lambda retains env, instance to legally call the reverse JNI function?
JNIEXPORT jint JNICALL
Java_com_company_app_ClassName_JniInit(JNIEnv *env, jobject instance){
int data = 0;
auto builder = new Builder(data,
[env, instance]() -> std::string {
std::string stringObj = populateData(env, instance); // This function makes a reverse JNI call to get data from a java function in the class
return stringObj;
}
);
std::shared_ptr<className> = builder->build();
return 1;
}
I seem to be getting a SIGNAL 11 error, SIGSEGV. Is this kind of segmentation fault catchable in any way, so the app doesn't crash?
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x228 in tid 21785 (ClassName), pid 21573 (.company.app)
It seems to be crashing at this line inside populateData-
jstring data = (jstring)(env)->CallObjectMethod(instance, javaFunctionName);
Is there a way to check if this function will fail before calling it? I checked if env (JNIEnv* argument in populateData) is NULL, but its not, and has a valid address along with instance (jinstance argument in populateData).
You'll have problems with jobject instance if this function does something asynchronously. The reason is that before this function is started, Java marks the object as having an extra reference. It removes that when it returns. So after it returns, the object can be cleaned up by the garbage collector if there's no other instances in the Java code.
This can be fixed by calling NewGlobalRef(JNIEnv *env, jobject obj) before starting the async function on the main thread, and calling DeleteGlobalRef at the end of the callback when jobject is no longer needed.
To answer this question, I have found a slightly different kind of hack.
Don't copy the JNIEnv, and object or create references to them. They get deleted as soon as your JNI function goes out of scope. I'm not sure why copying doesn't work (if someone could answer this, that would be great). Alternatively, I've used JavaVM* to maintain a local reference of the jvm, you can do this in your JNI_OnLoad.
Use the function below, and it would work fine.
JavaVM* jvm; // Initialise this in OnLoad
JNIEnv *getJNIEnv()
{
JNIEnv *env = nullptr;
jint ret = jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (ret == JNI_EDETACHED || !env)
{
env = nullptr;
}
return env;
}
If you need the class instance, you'll need to initialise it via JNIEnv, but I use a static method for my class, so I just fetched the class using env->FindClass and then you can do env->GetStaticMethodID along with env->CallStaticObjectMethod.

Call Java method from C++ on android ndk

I create Androidd NDK OpenGL ES application so I want to call Java method from NativeActivity, in main.cpp file I have JNIEnv* which I pass it to another classes where I want to call the Java method:
JNIEnv* env= manager.env;//I pass this pointer from main.cpp
jclass clazz = env->FindClass("com/game/JavaMethods");//Here I get the exception
jstring jstr1 =env->NewStringUTF(imgURL.c_str());
jmethodID mid = env->GetStaticMethodID(clazz, "Share", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(clazz, mid, jstr1);
I get SIGABRT crash if any env method called.
I am not expert in JNI so I am confused where is the wrong.

What causes the JNI error "use of deleted local reference"?

I have an Android app where the following C method is called when the app starts (in Activity.onCreate).
extern "C"
JNIEXPORT jstring JNICALL
Java_com_google_oboe_test_oboetest_MainActivity_stringFromJNI(
JNIEnv *env,
jobject instance) {
jclass sysclazz = env->FindClass("java/lang/System");
jmethodID getPropertyMethod = env->GetStaticMethodID(sysclazz, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
jstring result = (jstring) env->CallStaticObjectMethod(sysclazz, getPropertyMethod, "os.name");
return result;
}
When this method is called the app crashes and I get the error:
JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0xd280e8d5
Step debugging shows that this line causes the crash:
jstring result = (jstring) env->CallStaticObjectMethod(sysclazz, getPropertyMethod, "os.name");
What causes this error? And how can I call System.getProperty("os.name") using JNI without getting this error?
The issue is that env->CallStaticObjectMethod is expecting a jstring as its 3rd argument and is instead being supplied with a string literal.
Creating a jstring first
jstring arg = env->NewStringUTF("os.name");
jstring result = (jstring) env->CallStaticObjectMethod(sysclazz, getPropertyMethod, arg);
fixed the problem.
In my case, I was using a local reference that was created in a function and was used in another function.
For example:
void funA(){
jclass localClass = env->FindClass("MyClass");
}
The above statement returns a local reference of jclass. Suppose we store this in a global variable and use it in another function funB after funA is completed, then this local reference will not be considered as valid and will return "use of deleted local reference error".
To resolve this we need to do this:
jclass localClass = env->FindClass("MyClass");
jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));
First, get the local reference and then get the global reference from local reference. globalClass can be used globally (in different functions).
Read Local and global references

Setting up JNI in Android Studio 3

I am using JNI in an Android Studio project I am working on. Currently, I have a C++ library that looks similar to this.
#include <jni.h>
...
extern "C" {
JNIEXPORT jobject JNICALL Java_com_cerbyarms_cerbyarms_esra_camera_CameraActivity_FindFeatures(JNIEnv* env, jobject, jlong maskMat)
{
...
jclass rectClass = env->FindClass("org/opencv/core/Rect");
jmethodID rectID = env->GetMethodID(rectClass, "<init>", "(IIII)V");
return env->NewObject(rectClass, rectID, x, y, width, height);
}
}
This works. However, it is inefficient. Every time this is run, rectClass has to refind the class and other variables that remain constant in the program have to be recalculated and redefined every time function FindFeatures is called.
I came across this answer on Stack Overflow (It is not related to this question apart from the fact that it shows an example of what I am trying to do), that shows a different layout for a native file when using JNI.
It looked like this
static jclass java_util_ArrayList;
static jmethodID java_util_ArrayList_;
jmethodID java_util_ArrayList_size;
jmethodID java_util_ArrayList_get;
jmethodID java_util_ArrayList_add;
static thread_local JNIEnv* env;
void init() {
java_util_ArrayList = static_cast<jclass>(env->NewGlobalRef(env->FindClass("java/util/ArrayList")));
java_util_ArrayList_ = env->GetMethodID(java_util_ArrayList, "<init>", "(I)V");
java_util_ArrayList_size = env->GetMethodID (java_util_ArrayList, "size", "()I");
java_util_ArrayList_get = env->GetMethodID(java_util_ArrayList, "get", "(I)Ljava/lang/Object;");
java_util_ArrayList_add = env->GetMethodID(java_util_ArrayList, "add", "(Ljava/lang/Object;)Z");
}
std::vector<std::string> java2cpp(jobject arrayList) {
jint len = env->CallIntMethod(arrayList, java_util_ArrayList_size);
std::vector<std::string> result;
result.reserve(len);
for (jint i = 0; i < len; i++) {
jstring element = static_cast<jstring>(env->CallObjectMethod(arrayList, java_util_ArrayList_get, i));
const char* pchars = env->GetStringUTFChars(element, nullptr);
result.emplace_back(pchars);
env->ReleaseStringUTFChars(element, pchars);
env->DeleteLocalRef(element);
}
}
This shows a native file that has expensive and constant variables that appear to only be declared and calculated once.
How can I achieve a similar thing using only the Android Studio IDE? I don't mind having to set up external tools in the Android Studio IDE settings, but I don't want to have keep switching between Android Studio and something like CMD every time I compile my code.
Ideally, this could all be handled correctly when Make Project is hit. Is this possible in Android Studio 3?
You are 100% right, some JNI values beg to be cached and reused. Class references and method IDs are good examples. Please remember that FindClass() returns a local reference, so you need NewGlobalRef() for each class you keep in cache.
Android Studio does not help us with this setup, and I am not aware of reliable tools that can do such refactoring for us. You can learn good practices from open source code, e.g. from WebRTC JNI wrapper or from Spotify JNI helpers.
Android Studio can only keep track of the native methods, not of the cached objects, conversions, etc.

Why isn't JNIOnLoad running?

I have an Android project with a Java base Activity, Java JNI interface class, and native code. I have the proper
System.loadlibrary(_libraryname_);
In my Java interface class and from the logcat output:
11-29 15:11:20.737: D/dalvikvm(8940): No JNI_OnLoad found in /data/data/com.example.testjni/lib/libTestJNI.so 0x406ef030, skipping init
In my cpp file header:
extern "C" {
JNIEXPORT jint JNICALL JNIOnLoad(JavaVM *, void *);
}
In the cpp file:
JNIEXPORT jint JNICALL JNIOnLoad(JavaVM *vm, void *reserved)
{
LOGI("JNIOnLoad");
jvm = vm;
return JNI_VERSION_1_6; /* the required JNI version */
}
But still the OnLoad function never is called. I've tried uninstalling the app and re-installing it but it never runs.
Function signature is JNI_OnLoad. There is an underscore / _ between JNI and OnLoad.

Categories

Resources