Jni函数查找失败问题分析
1、 问题点描述:
系统jni函数查找报错java.lang.UnsatisfiedLinkError: Native method not found。通常的原因是函数书写错误,但是在肯定函数没有问题的情况下,偶尔还会出现报错的问题。
2、 问题点原因分析:
1 经过log分析问题点发生时,软件在调用mcu的指令发送函数McuDaemon.write0,系统查找不到此函数在Jni层内的具体实现,故抛出函数查找不到的错误。
E/AndroidRuntime( 2471): java.lang.UnsatisfiedLinkError: Native method not found: com.soling.uart.McuDaemon.write0:([B)V
2 此函数在Jni层有正常实现,除了概率性报错外,其他场景下都能够正常运行。故不是软件代码编写的问题,而是系统函数查找检索出现问题。
3 继续分析log,在问题出现时,在另外一个线程有其他的库方法被调用到。推测为两个库的方法同时调用有冲突。
I/sysdata jni( 2471): sysdata_opera --->func:int private_store_write(const char*, const char*) write 340=128
I/sysdatajni(2471):sysdata_opera--->func:jint com_soling_emc_test_FileSystemDataSave_native_setdata(JNIEnv*, jobject, jstring, jstring) [private_data_write] :Write private data fail
4 分析源码,抛出函数查找不到的错误在android/dalvik/vm/Native.cpp第129行:
/* now scan any DLLs we have loaded for JNI signatures */
void* func = lookupSharedLibMethod(method);
if (func != NULL) {
/* found it, point it at the JNI bridge and then call it */
dvmUseJNIBridge((Method*) method, func);
(*method->nativeFunc)(args, pResult, method, self);
return;
}
IF_ALOGW() {
char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
ALOGW("No implementation found for native %s.%s:%s",
clazz->descriptor, method->name, desc);
free(desc);
}
dvmThrowUnsatisfiedLinkError("Native method not found", method);
由上述代码可以看到,lookupSharedLibMethod时获取到的函数为空。
5 系统查找检索的方法分析
函数如下:
/*
* See if the requested method lives in any of the currently-loaded
* shared libraries. We do this by checking each of them for the expected
* method signature.
*/
static void* lookupSharedLibMethod(const Method* method)
{
if (gDvm.nativeLibs == NULL) {
ALOGE("Unexpected init state: nativeLibs not ready");
dvmAbort();
}
return (void*) dvmHashForeach(gDvm.nativeLibs, findMethodInLib,
(void*) method);
}
6 dvmHashForeach 是HashTable中轮询函数的方法,findMethodInLib为从一个so库中查找函数的方法。可以看出系统加载每个程序的so库时,会保存在对应的一个HashTable中,java层调用native方法时,会遍历HashTable中保存的so库信息,来查找对应的jni函数。分析此流程可以看出,当问题出现时,无法从HashTable的so库中查找到对应的函数,但是此时可以看出对应的so库已经加载,但是检索函数没有查找对应的so库。故继续分析dvmHashForeach函数。
7 dvmHashForeach函数为:
/*
* Scan every entry in the hash table and evaluate it with the specified
* indirect function call. If the function returns 1, remove the entry from
* the table.
*
* Does NOT invoke the "free" function on the item.
*
* Returning values other than 0 or 1 will abort the routine.
*/
int dvmHashForeachRemove(HashTable* pHashTable, HashForeachRemoveFunc func)
{
int i, val, tableSize;
tableSize = pHashTable->tableSize;
for (i = 0; i < tableSize; i++) {
HashEntry* pEnt = &pHashTable->pEntries[i];
if (pEnt->data != NULL && pEnt->data != HASH_TOMBSTONE) {
val = (*func)(pEnt->data);
if (val == 1) {
pEnt->data = HASH_TOMBSTONE;
pHashTable->numEntries--;
pHashTable->numDeadEntries++;
}
else if (val != 0) {
return val;
}
}
}
return 0;
}
结合日志可以看到HashTable在轮询过程中,HashTable被外部线程改变,长度发生变化,导致so库没有查找完全。
3、 问题原因总结:
1、系统加载每个程序的so库时,会保存在对应的一个HashTable中。每次程序调用loadLibrary 时,hashTable会插入so库的信息,长度会根据情况发生变化.
2、java层调用native方法时,会遍历HashTable中保存的so库信息,来查找对应的jni函数。
3、当查找jni函数时,如果HashTable发生长度变化,so库在table中的信息可能发生位置偏移,但系统中dalvik使用的遍历HashTable的方法并不会更新遍历的长度,可能导致无法查找到对应库的jni函数。
4、 问题对策:
为了避免查找jni函数和加载so库两种动作冲突导致的jni函数查找失败,故程序开始运行时统一加载所有的so库。
5、结论:
软件设计过程中要多思考程序执行存在的不确定性,虽然有些时序发生的概率非常之小,也要从细节上提升软件的可靠性、准确性、稳定性。之所以会出现此次报错,归根结底是android系统本身的问题,但是在不能完全更换系统的情况下,必须针对不同场景,使用特定的方法来适配系统。
另:通过搜索此问题,找到google的dalvik虚拟机针对此查询特别将遍历hashtable的方法由动态长度改为静态长度,可能是针对某问题而改动,但由此引发了报错的隐患,故不进行源码修改,而从策略上来做规避。