I have written a shared library which is accessed via my linux-system and via a JNI-call from java.
That library should have a global ringbuffer which must be available in the JNI-method and in the other native methods.
I thought this won't be a problem, because when I access the SL from different programs, the global variables are always as they should.
But now, in my JNI-method, the global variables seem to be not initialized (they should as the program-flow forces it).
Here is my example:
ringbuf_t ringbuffer;
void internalMethod() {
// this method is first called from system-program
ringbuffer = ringbuf_new(5000);
}
JNIEXPORT jint JNICALL Java_example_read(JNIEnv *env, jobject This) {
// this method is later called via JNI
if (!ringbuffer) {
LOGI("uhhh, why is that buffer not set?!");
}
}
What do I have to do to make the ringbuffer-variable really global so every instance/call to the shared-library access one and the same instance of that variable?
From the comments it seems you want to allocate your memory in a process and use it in another.
For this purpose it might be a good idea to look at shared memory: 'man shmget' should be a good start.
Note that this is not specifically related to JNI and is a restriction from the OS.
EDIT:
I would suggest you:
read up on shared memory - you should understand the concepts of how this works.
first try sharing memory between 2 simple applications
only then implement in your JNI application
Related
We can write methods to get the key as below both Method 1 and Method 2 for the Android app
Are both Methods have the same security strength? or is there any security weakness in one of the below Methods while decompile/reverse engineering or other app cracking procedures?
Method 1
declare/assign key inside the method in the lib.cpp file
lib.cpp file
extern "C"
JNIEXPORT jstring JNICALL
Java_com_app_keytest_KeyHelper_getKey(JNIEnv *env, jobject) {
std::string API_KEY = "YOUR_API_KEY";
return env->NewStringUTF(API_KEY.c_str());
}
Method 2
declare/assign keys in separate keys.h file in the same directory with lib.cpp and import to lib.cpp file
keys.h file
std::string API_KEY = "YOUR_API_KEY";
lib.cpp file
#include "keys.h"
extern "C"
JNIEXPORT jstring JNICALL
Java_com_app_keytest_KeyHelper_getKey(JNIEnv *env, jobject) {
return env->NewStringUTF(API_KEY.c_str());
}
Your two snippets compile to nearly the exact same code. In both cases the API key is present in plain text in the compiled library. Even if you took pains to obfuscate the native code, an attacker can just attach a debugger and catch the return value of getKey.
You need to rethink your approach and decide if it is actually worth the effort on your part.
You can achieve a good result in another way:
you have to obfuscate JNI function name because "getKey()" is too much self-explained and easy to understand during reverse engineering
add unused parameters to "getKey()" to make it more complex when scrolling while viewing reversed code
getKey() haven't to return the Key but have to call a Java method or set a specific Java variable
About #3: I'm using a dedicated Thread on JNI to receive a "command" and a "callback" for its results:
Java calls JNI's "getKey(fakeArg1, fakeArg2, fn_callback, fakeArg3)"
getKey() sends a request to JNI dedicated Thread and pass even "fn_callback" as part of that request
the dedicated Thread process the request and then call Java Callback
In this way event during normal debugging it's very difficult to follow the Flow because debugging won't go inside dedicated Thread automatically using StepInto/Over key/button.
Update: callback is the first approach but create a link between caller and the result. To avoid this you could call a completly separated Java method from JNI to pass the variable to.
Have been using java.nio.ByteBuffers on the NDK side for a while now - noticed this article about Android relationship with JNI, GC and future of ICS. Article here http://android-developers.blogspot.com/2011/11/jni-local-reference-changes-in-ics.html
So... here is the concern:
Since the "pointer" that JNI provides seems to actually be a reference that is managed by the JNI internaly - it could be "moved" or deleted by GC at some point if it is not marked as NewGlobalReference() in JNI method before being passed to c++ classes?
In my JNI methods I take the Direct Buffer address and pass it on to classes that use it, without any
env->NewGlobalRef(jobject);
env->NewLocalRef(jobject);
env->DeleteGlobalRef(jobject);
management.
For now it all works - but is it correct?
Thoughts?
P.S - I do use free(ByteBuffer) on exit/destructor in c++
A local reference is only valid for the duration of the JNI method that it is passed to or created in. After that method returns to the JVM, the reference is no longer valid. If you're not breaking that rule, you're OK.
It's a bit unclear what you're asking, so let me try to clarify a few points.
Any jobject type you get in JNI, whether returned from a JNI call like FindClass or passed in as an argument (jobject, jclass, jbyteArray, etc), is a local reference. It has a very short lifespan. If you pass it to NewGlobalRef, you get a global reference in return; this will last until you delete it.
Any JNI function that takes or returns a pointer type is giving you a pointer that's good until something invalidates it. For example, if you call GetStringUTFChars, you get a const char* that's valid until you call ReleaseStringUTFChars.
References are not pointers, and pointers are not references. You can't pass a char* to NewGlobalRef, and you can't dereference a global reference (where "can't" is usually an error or a native crash).
What I assume you're doing is calling GetDirectByteBufferAddress on a ByteBuffer object, which returns a void* that points to the start of the direct byte buffer. This pointer is valid until the storage is freed. How that happens depends upon how you allocated it:
If you allocated the direct byte buffer with ByteBuffer.allocateDirect(), then Dalvik owns the storage. It will be freed when the ByteBuffer becomes unreachable and is garbage collected.
If you allocated the storage yourself, and associated it with a ByteBuffer with the JNI NewDirectByteBuffer call, then it's valid until you free it.
For the allocateDirect() case, it's very important that your native code stops using the pointer before your managed code discards the ByteBuffer. One way to do this would be to retain a global reference to the ByteBuffer in your native code, and invalidate your buffer pointer at the same time you delete the global reference. Depending on how your code is structured that may not be necessary.
See also the JNI Tips page.
I use regular std::map to map jobject's to c++ objects. The problem with this approach is that it may fail for other types of references, e.g. global references are actually different pointer than regular local references even if they reference the same object. The correct way to compare if two references reference the same object is:
env->IsSameObject(jobj1, jobj2);
So, my question is: what's the correct way to map jobject's to c++ objects? The obvious reply to wrap jobject into some c++ class that overloads operator== and calls IsSameObject isn't the reply that I'm looking for. I'd like to know if there is way to do it without going back and forth between JVM and native c/c++ for every compare operation.
EDIT: global reference is a jni global reference, and has nothing to do with c++ references.
EDIT2: I'd like to clarify what the problem is with this code:
std::map<jobject, void *> jobjs;
jobject obj1, obj2;
... some code that sets these obj1 and obj2 to some Java objects.
jobjs[obj1] = new CppPeer;
CppPeer * = jobjs[obj1]; //OK...
if(objs.find(obj2) == objs.end()){
assert(obj2 != obj1);
//Here's the problem: here a new c++ CppPeer
//created for obj2, but the problem is that
//even if obj1 != ob2 it doesn't mean that
//they actually reference different java objects
//On the next line error might happen
jobjs[obj2] = new CppPeer; //maybe not OK here...
}
The other problem with IsSameObject is that it makes things pretty nasty and messy. Not only now I'd need to keep a pointer to JVM, but also whenever I need to compare jobjects I'd need to attach thread etc to get pointer to JNIEnv to be able to check jobject
EDIT3: Be aware, that according to android docs you cannot even assume that two references reference the same object if they are equal. I don't know how it's possible, but there's the part from Android's JNI tips page:
One consequence of this is that you must not assume object references
are constant or unique in native code. The 32-bit value representing
an object may be different from one invocation of a method to the
next, and it's possible that two different objects could have the same
32-bit value on consecutive calls. Do not use jobject values as keys.
DISCLAIMER: it smells.
On Android, pointer and jint are of the same size. Consider adding an int field to your Java class to store a pointer to the native conterpart. When in a JNI method, typecast it back to a pointer.
To be on a slightly safer side, do a static assert on datatype matching:
{char a[sizeof(void*) == sizeof(jint) ? 1 : -1]};
If it's compiled on a system where it's not, you'll get a compilation error.
For slightly less smell, use a static/global map from int to your objects, use an ID generator, and instead of object pointer store relatively unique (within process lifetime) objects IDs within the Java object.
I am writing the C++/Native side of a text/VoIP program that utilizes JNI. Currently there are three functions on the C++ side that will likely be called by the Java code regularly. The frequency at which the functions are called will depend on how often the user sends/receives text messages and how active their friends are in changing their presence/status. Each of the three functions follow the pseudo code below and I am not sure if it would be wise (or even possible) to "globalize" anything within the functions.
JNIEnv *env;
if (jvm_->GetEnv((void**) &env, JNI_VERSION_1_6) < 0)
{
[print error and return]
}
jclass stringclass = env->FindClass("java/lang/String"); // Same for all 3
jstring passinfo = env->NewStringUTF([str-info-to-pass]); // Different for all 3
jclass cls = env->FindClass([directory to calling Java function class]); // Same for all 3
[check if cls found, print error if not found and return]
jmethodID methID = env->GetStatisMethodID([arguments for the function]); // Different for all 3
[check if methID found, print error if not found and return]
jobjectArray args = env->NewObjectArray([arguments to build argument structure being passed to function]);
[call Java function, passing right arguments]
I was thinking that I should/would be able to move the jclass stringclass, jclass cls and JNIEnv *env out of the functions and make them globals that were set during the JNI_OnLoad function call. Is this possible/advisable? Will these values be somehow different from the OnLoad function to these functions? Should I just instantiate them as globals and still set them each time in the function call? And, will doing any of these really increase performance in any noticeable amount?
Possible, yes. Wise, it depends. Define for yourself what is a "noticeable amount". What cadence of JNI calls you expect? I think you could see some measurable impact of caching starting at 10+ calls per second. Is that a case? Anyway, in my personal experience, nicely done caching cleans up the code, makes it shorter and more readable. So i will give some advice anyway.
Caching results of FindClass, yes absolutely. Generally anything reusable and "static", what you are looking up by a symbolic name - which introduces a penalty of symbolic lookup, contrary to simply using an already cached pointer. Liang JNI book talks also about field and method ids, so your GetStaticMethodID applies. jmethodID is simpler in the sense that the result of Get(Static)MethodID can be directly cached: it is valid for any further calls. Unlike FindClass which returns a local reference, so for valid caching (and to make jmethodID really persistent) you must create NewGlobalRef from it - and delete it when appropriate. A nice comprehensive example is found in the JNI_OnLoad tutorial. On the other hand, there is no point in caching JNIEnv, you get it again with every JNI call. Cached JNIEnv would be also invalid if you happen to call the JNI methods from another thread than the one from which you cached.
You should not worry about performance.
Currently I develop a voip client where i used native codec. This means that the JNI is called for every rtp packet (two times in every 20 msec because both encoding and decoding) also passing the JNIEnv with each call. This variable is just a pointer. Consumed CPU usage for JNI calls are below 1% on a low end device for me in these circumstances so you should not worry at all if your only need is texting.
When I run the code, I get an error "failed adding to JNI local ref table has 512 entries"
This is my code:
jstring pJNIData = pJNIEnv->NewStringUTF ( variables[0].GetStringValue() );
pJNIEnv->CallStaticVoidMethod ( pJNIActivityClass, pJNIMethodIDStartTime, pJNIData ) ;
pJNIEnv->DeleteLocalRef(pJNIData);
I have read several suggestions, but none of them work! In spite of the DeleteLocalRef, it fails to works. The function is used in a profiler that literally calls all the functions...
I have seen this when a JNI method called Java code (in my case, the method was not static). As I understand, unused local references are not automatically deleted when a Java method is called from JNI (I mean, until the top-level JNI function returns).
IIRC either there already was information about memory objects in the log, or I could add some logging; from that information I identified garbage items that I did not mention before. They were two arrays and a class, created in subsequent calls but not garbage-collected.
// in a function that calls a Java method from JNI
jbyteArray srcArray = env->NewByteArray(len);
jclass cls = env->FindClass("com/something/MyClass");
jmethodID mid = env->GetMethodID(cls, "mymethod", "([BI)[B");
jbyteArray resArray = (jbyteArray)env->CallObjectMethod(obj, mid, srcArray, XXXX);
...
env->DeleteLocalRef(cls);
env->DeleteLocalRef(resArray);
env->DeleteLocalRef(srcArray);
// no need to do anything with mid
Note that although these three local references were obtained differently, all of them were hanging around.
Useful link:
http://www.netmite.com/android/mydroid/dalvik/docs/jni-tips.html#local_vs_global_references
(or find the Dalvik VM docs dalvik/docs/jni-tips.html and locate the section "Local vs. Global References")
Every object that JNI returns is a "local reference". This means that it's valid for the duration of the current native method in the current thread. Even if the object itself continues to live on after the native method returns, the reference is not valid. This applies to all sub-classes of jobject, including jclass and jarray. [...] Note: method and field IDs are just 32-bit identifiers, not object references, and should not be passed to NewGlobalRef. The raw data pointers returned by functions like GetStringUTFChars and GetByteArrayElements are also not objects.
I thought I would chip in just in case anyone else runs into this issue. This is a weird case that kept me confused for hours!
Ok so I have an NDK app and the Java code being called is inside an apk that is loaded at runtime. I have no idea if the runtime loading effects this in any way but I thought I should mention it.
Now in a c++ method I use find class and getmethodid to get the constuctor to a HashMap and call it to get a new HashMap instance. I then populate the HashMap from the c++ side using jni calls. So far so good.
I then pass the HashMap to java code and, again, all is working as expected. Once the java code has returned I call DeleteLocalRef on the HashMap. No errors are thrown but the reference is not deleted.
This only came up when I finally ran over 512 local references (from multiple calls to this function) and the error dump showed that the last 10 items in the localref store were nearly all HashMaps. I would understand that the GC would not collect these references at the end of the method as I am make a multithreaded ndk app. However the DeleteLocalRef should have worked.
The Fix:
In the end I found that creating the HashMap from a jni call to a java method I wrote was fine, and the reference was then free'able. It seems crazy to have a java function that literally just returns a new HashMap but it worked so for now I am living with it :)