jmethodID in android ART - java

I'm working on an app.
Inside JNI_OnLoad():
jclass cls = (*env)->FindClass(env, "dummy/dummy/dummy/Dummy");
if(cls == NULL) {
LOG_ERROR("unable to class");
return;
}
cached_dummy.cls = (*env)->NewGlobalRef(env, cls);
if(cached_dummy.cls == NULL) {
LOG_ERROR("unable to create a global reference of class");
return;
}
cached_dummy.method = (*env)->GetStaticMethodID(env, cls, "testMethod", "()V");
and when i used this cached result in java callback using (*env)->CallStaticVoidMethod(env, cached_dummy.cls, cached_dummy.method)
Result: "JNI DETECTED ERROR IN APPLICATION: jmethodID was NULL"
Android 5.0.2
Query:
Incompatibility?
Solution? (Do i need to make a global reference of jmethodID?)
Note: https://stackoverflow.com/a/2093300/1500988

This has to do with recursive loading of shared library.
when class is loaded, its static block is executed.
Inside the static block, shared library loader is placed on top and then rest of init code.
class(Foo) load => its static block executed => jni library loader => JNI_OnLoad => cache static method Foo.Bar() => class(Foo) load => WARN(recusive-load-warning) and execute the rest static block remaining code that call jni code and it uses Foo.Bar() and BOOM!

Related

JNIEnv::NewObject() throwing java.lang.InstantiantionException

I am attempting to call JNIEnv::NewObject() in some JNI code when a C function returns a non-zero error code.
The order of events looks like:
Call C function.
If return code is non-zero, call a helper function which throws a custom excpetion.
The class I am trying to construct so that I can throw it is:
public final class HseException extends Exception {
private static final long serialVersionUID = 8995408998818557762L;
private final int errno;
private final Context ctx;
/* Only called from C */
HseException(final String message, final int errno, final Context ctx) {
super(message);
this.errno = errno;
this.ctx = ctx;
}
public Context getContext() {
return this.ctx;
}
public int getErrno() {
return this.errno;
}
public static enum Context {
NONE
}
}
In my code I am caching the jclass and jmethodID for the class and the constructor in a global struct, but the code looks like:
globals.com.micron.hse.HseException.class =
(*env)->FindClass(env, "com/micron/hse/HseException");
globals.com.micron.hse.HseException.init = (*env)->GetMethodID(
env,
globals.com.micron.hse.HseException.class,
"<init>",
"(Ljava/lang/String;ILcom/micron/hse/HseException$Context;)V");
globals.com.micron.hse.HseException.Context.class =
(*env)->FindClass(env, "com/micron/hse/HseException$Context");
globals.com.micron.hse.HseException.Context.NONE = (*env)->GetStaticFieldID(
env,
globals.com.micron.hse.HseException.Context.class,
"NONE",
"Lcom/micron/hse/HseException$Context;");
Note that the above code is located in the JNI_OnLoad() function of my library. This function completes without error, so this tells me that at least my classes and methods are being loaded correctly.
Lastly here is my helper function where I throw my custom exception type:
/* hse_err_t is a scalar type.
* hse_strerror() creates a string out of that scalar.
* hse_err_to_ctx() gets the enum context value embedded within the scalar.
* hse_err_to_errno() gets the errno value embedded within the scalar.
*/
jint
throw_new_hse_exception(JNIEnv *env, hse_err_t err)
{
assert(env);
assert(err);
const size_t needed_sz = hse_strerror(err, NULL, 0);
char *buf = malloc(needed_sz + 1);
if (!buf)
return (*env)->ThrowNew(
env,
globals.java.lang.OutOfMemoryError.class,
"Failed to allocate memory for error buffer");
hse_strerror(err, buf, needed_sz + 1);
const jstring message = (*env)->NewStringUTF(env, buf);
free(buf);
if ((*env)->ExceptionCheck(env))
return JNI_ERR;
const int rc = hse_err_to_errno(err);
const enum hse_err_ctx ctx = hse_err_to_ctx(err);
jfieldID err_ctx_field = NULL;
switch (ctx) {
case HSE_ERR_CTX_NONE:
err_ctx_field = globals.com.micron.hse.HseException.Context.NONE;
break;
}
assert(err_ctx_field);
const jobject err_ctx_obj = (*env)->GetStaticObjectField(
env, globals.com.micron.hse.HseException.Context.class, err_ctx_field);
if ((*env)->ExceptionCheck(env))
return JNI_ERR;
const jobject hse_exception_obj = (*env)->NewObject(
env,
globals.com.micron.hse.HseException.class,
globals.com.micron.hse.HseException.init,
message,
rc,
err_ctx_obj);
if ((*env)->ExceptionCheck(env))
return JNI_ERR;
return (*env)->Throw(env, (jthrowable)hse_exception_obj);
}
I know for a fact that the (*env)->NewObject() call is what is raising the exception because an exception check before and after will tell me so. The (*env)->NewStringUTF() call is successful and contains the string it should contain. The context field is also retrieved successfully.
What I am not understanding is why I am getting an InstantiationException. The Throws section of the JNIEnv::NewObject() is marked as the following:
THROWS:
InstantiationException: if the class is an interface or an abstract class.
OutOfMemoryError: if the system runs out of memory.
Any exceptions thrown by the constructor.
My class is not an interface nor is it an abstract class, so where could this exception be generated from? The weird thing is that I swear this worked before, but since I am writing these Java bindings from scratch, I have just been overwriting commits and force pushing to my branch.
Any help is appreciated. Unfortunately getMessage() on the exception returns null which just isn't helpful at all. There is no message from the JVM telling me potentially what I have done wrong either.
One detail that could be helpful is that when I try to call JNIEnv::ThrowNew() (after putting a (Ljava/lang/String;)V constructor in the same HseException class, jni_ThrowNew() segfaults, and I cannot understand why. The class is valid when I stash the jclass, and I know for a fact that the memory it is stashed in isn't overwritten in any way, since I have checked the pointer.
The repo where all this code lives is: https://github.com/hse-project/hse-java. Unfinished product, but at least it is buildable and tests can be ran. In the event that someone decides to clone the repo and build it, I will repeat the directions here:
meson build
ninja -C build
meson test -C build -t 0 KvsTest # I am using this test to exercise the code path
My goal tomorrow will be to try to reproduce the issue in a smaller manner. I may also try to peer into the OpenJDK code assuming that is where the JNI interfaces live. Figure if I look hard enough, I might find the line of code which generates the exception.
Edit: I did a test where in my current code, I added a main function and a native function whose only purpose is to throw an exception from C. The code looks something like:
private static native void throwException();
public static void main(String[] args) {
System.load("/path/to/.so");
throwException();
}
The implementation of the native function is:
void Java_com_micron_hse_Hse_throwException
(JNIEnv *env, jclass hse_cls)
{
(void)hse_cls;
/* Generate error */
hse_err_t err = hse_kvdb_txn_begin(NULL, NULL);
throw_new_hse_exception(env, err);
}
This printed the following after executing java -jar path/to/jar:
Exception in thread "main" com.micron.hse.HseException: lib/binding/kvdb_interface.c:1046: Invalid argument (22)
at com.micron.hse.Hse.throwException(Native Method)
at com.micron.hse.Hse.main(Hse.java:28)
That is exactly what I expect to be printed, so now I would say I am even more lost than when I started. For some reason in the context of my tests, the InstantiationException is raised. Not sure if an application using the JAR would hit the same issue or if it is just a test context thing.
Edit 2:
Changed the main method from the previous edit to the following which is pretty much exactly what my test does:
public static void main(String[] args) throws HseException {
try {
loadLibrary(Paths.get("/home/tpartin/Projects/hse-java/build/src/main/c/libhsejni-2.so"));
init();
final Kvdb kvdb = Kvdb.open(Paths.get("/media/hse-tests"));
final Kvs kvs = kvdb.kvsOpen("kvs");
kvs.delete((byte[])null);
kvs.close();
kvdb.close();
} finally {
// fini();
}
}
And was able throw the exception from C appropriately. This must mean that something is wrong with my test environment somehow.
Edit 3: Another clue. On one test, this issue generates the InstantiationException. On another test, this issue segfaults in jni_NewObject.
My issue was that I was holding onto jclass et al. references for too long.
Prior question: Why I should not reuse a jclass and/or jmethodID in JNI?
Java docs: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#global_and_local_references
All Java objects returned by JNI functions are local references.
Thanks to Andrew Henle for pointing this out in the comments of the question. I have highlighted his comment in this answer, and will mark it is as the answer.

Java Invocation API: Call the C function back from the java code

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.

Passing a Java object to C++ via JNI and then back to Java, via void*

I have an Android app that employs both React Native and JNI. C++ (via a fork of the JUCE library) is used to generate one of the views.
React Native requires that a new instance of a view be returned from the overridden method createViewInstance(context). This seems to get called each time a React Native component containing this view is updated.
Here is my (simplified) implementation:
protected JuceViewHolder createViewInstance(ThemedReactContext themedReactContext) {
JuceBridge juceBridge = JuceBridge.getInstance();
juceViewHolder = new JuceViewHolder(themedReactContext);
// JNI method: this will trigger JuceBridge.createNewView
MainActivity.createJuceWindow(juceViewHolder);
return juceViewHolder;
}
For reference, createJuceWindow is defined as:
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, createJuceWindow, jobject, (JNIEnv* env, jclass, jobject view))
{
JuceView::getInstance().createJuceWindow(view);
}
which calls:
void createJuceWindow(jobject view)
{
viewToAttachTo = GlobalRef(view); // [GlobalRef][1] manages NewGlobalRef
myComponent = std::unique_ptr<MyComponent> (new MyComponent);
myComponent->setVisible (true);
myComponent->setOpaque(true);
if (viewToAttachTo.get() == nullptr)
DBG ("createJuceWindow: viewToAttachTo null!!!");
myComponent->setBounds(Desktop::getInstance().getDisplays().getMainDisplay().userArea);
myComponent->addToDesktop (0, viewToAttachTo.get()); // This passes in the `jobject` held by GlobalRef
}
I have separated the JuceViewHolder derived from ViewGroup, and I am passing it to a C++ function via JNI in order to attach the (Java)ComponentPeerView generated from the (C++) AndroidComponentPeer constructor
AndroidComponentPeer (Component& comp, const int windowStyleFlags, void* viewToAttachTo)
: ComponentPeer (comp, windowStyleFlags),
usingAndroidGraphics (false),
fullScreen (false),
sizeAllocated (0),
scale ((float) Desktop::getInstance().getDisplays().getMainDisplay().scale)
{
// NB: must not put this in the initialiser list, as it invokes a callback,
// which will fail if the peer is only half-constructed.
if (viewToAttachTo == nullptr) DBG ("viewToAttachTo null");
view = GlobalRef (android.bridge.callObjectMethod (JuceBridge.createNewView,
(jboolean) component.isOpaque(),
(jlong) this,
(jobject) viewToAttachTo));
if (view.get() == nullptr)
DBG ("view null!");
else
DBG ("got view.");
if (isFocused())
handleFocusGain();
}
via the createNewView method: (simplified here)
public final ComponentPeerView createNewView (boolean opaque, long host, ViewGroup viewToAttachTo)
{
ComponentPeerView v = new ComponentPeerView (viewToAttachTo.getContext(), opaque, host);
viewToAttachTo.addView(v);
return v;
}
host is a pointer to the C++ AndroidComponentPeer instance.
This works initially, however it seems that if I switch to another View or Activity and/or switch back to the C++ View, I get a crash similar to the following:
JNI ERROR (app bug): accessed deleted global reference 0x100566
art/runtime/java_vm_ext.cc:410] JNI DETECTED ERROR IN APPLICATION: use of deleted global reference 0x100566
art/runtime/java_vm_ext.cc:410] from void com.juce.JuceBridge$ComponentPeerView.focusChanged(long, boolean)
My current assumption is that the error is due to the void* pointer becoming obsolete at some point due to the garbage collector moving the underlying object, as described in this article. What I got from the article is that jobject should be used in place of a pointer.
However the method signature of the Component::addToDesktop method that I have employed uses void*:
virtual void addToDesktop (int windowStyleFlags,
void* nativeWindowToAttachTo = nullptr);
(This is the method used in iOS to pass a native UIView to attach a Component to)
Is my assumption valid? If so, is it possible to safely cast a void* pointer to a Java object, store it as a jobject (via NewGlobalRef) and then pass it back to Java?

Android NDK crash in CallObjectMethod calling static method with ART

In an Android app I have some JNI code that calls a java static method.
jbyteArray response = (jbyteArray)pEnv->CallObjectMethod(handlerClass, mid, jstrServiceUrl, jstrRequest);
Executing it in Android 5 in an ART environment, I get a check jni error:
JNI DETECTED ERROR IN APPLICATION: calling static method byte[] x.y.z(java.lang.String, java.lang.String) with CallObjectMethodV in call to CallObjectMethodV...
I don't get this error in Android 4 with a Dalvik environment.
The java method is this one:
public static byte[] z(String serviceURL, String request)
and is previously binded like this:
jclass handlerClass = pEnv->FindClass("x/y/z");
if (handlerClass == NULL) {
return -1;
}
mid = pEnv->GetStaticMethodID(handlerClass, "z", "(Ljava/lang/String;Ljava/lang/String;)[B");
if (mid == NULL) {
return -2;
}
// Construct Strings
jstring jstrServiceUrl = pEnv->NewStringUTF(szServiceURL);
jstring jstrRequest = pEnv->NewStringUTF(szRequest);
I don't know why your code worked with Dalvik, but the method id given to Call<type>Method must be obtained with GetMethodID. If you have a method id obtained with GetStaticMethodID you should use CallStatic<type>Method.
See the descriptions of Call<type>Method and CallStatic<type>Method in the JNI functions documentation.

Find names of all classes that a Java program loads using reflection

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

Categories

Resources