Android调用预编译.so库打开串口时JNI方法找不到的问题
我在StackOverflow上搜索相关问题但未找到直接答案。我需要在特定Android设备的应用中打开串口,该设备原有带串口功能的APK,我逆向分析后获取了system/lib64下的liboldnativelibrary.so文件,已知信息如下:
- 串口采用Uart通信;
- 该.so库功能正常;
- 已掌握其基础函数名;
- 设备运行Android 10,处理器为arm64-v8a;
- 已将该.so添加至jniLibs并成功加载。
我没有原JNI的.c和.h文件,也无设备及串口文档,难以重新构建原生库。逆向得到的.so方法为:
jobject Java_com_the_old_serialportApp_SerialPort_open(JNIEnv *penv,undefined8 param_2,jstring inputString,speed_t baudRate,uint hiddenParam)
我编写了Kotlin代码:
package com.myself.serialporttesting companion object { init { try { Log.i("Library", "Loading library") System.loadLibrary("oldnativelibrary") Log.i("Library", "Library loaded successfully") } catch (e: UnsatisfiedLinkError) { Log.i("Library", "Library not loaded") } } private external fun open( fileName: String, baudRate: Int, hiddenValue: Int ): FileDescriptor }
运行时报错:
No implementation found for java.io.FileDescriptor com.hfad.serialporttesting.SerialPort$Companion.open(java.lang.String, int, int)
我怀疑是包名不同导致的JNI命名问题,请问是否如此?能否找到解决办法?
你的怀疑完全正确——这就是JNI方法命名不匹配导致的问题。JNI通过全限定类名+方法名的规则绑定Java/Kotlin方法和原生函数,逆向得到的原生函数名Java_com_the_old_serialportApp_SerialPort_open对应原APK中的类路径是com.the.old.serialportApp.SerialPort,而你当前的Kotlin类路径是com.myself.serialporttesting.SerialPort$Companion(因为用了伴生对象),两者完全不匹配,所以系统找不到对应的实现。
以下是几种可行的解决办法,按优先级排序:
1. 完全匹配原类路径(最简单直接)
直接把你的Kotlin类的包名和类名改成和原APK一致:
- 包名改为
com.the.old.serialportApp - 类名改为
SerialPort,且将external方法放到类实例中,不要用伴生对象(原函数的第二个参数对应类实例jobject,而非伴生对象的jclass)
修改后的Kotlin代码示例:
package com.the.old.serialportApp class SerialPort { init { try { Log.i("Library", "Loading library") System.loadLibrary("oldnativelibrary") Log.i("Library", "Library loaded successfully") } catch (e: UnsatisfiedLinkError) { Log.i("Library", "Library not loaded") } } private external fun open( fileName: String, baudRate: Int, hiddenValue: Int ): FileDescriptor }
这样JNI就能通过标准命名规则找到对应的原生函数。
2. 使用JNI动态注册(无需修改包名)
如果不想调整自己的包名,可以通过JNI动态注册绑定方法,步骤如下:
- 创建新的JNI层文件(比如
serialport_bridge.c),实现动态注册逻辑 - 编译该文件为新的.so库,让它作为桥梁连接你的Kotlin方法和原有的
liboldnativelibrary.so函数
动态注册核心代码示例:
#include <jni.h> #include <android/log.h> // 声明原有的原生函数 jobject Java_com_the_old_serialportApp_SerialPort_open(JNIEnv *penv, jobject thiz, jstring inputString, speed_t baudRate, uint hiddenParam); // 方法映射:Kotlin方法名 -> 签名 -> 原生函数指针 static const JNINativeMethod methods[] = { { "open", "(Ljava/lang/String;II)Ljava/io/FileDescriptor;", (void *)Java_com_the_old_serialportApp_SerialPort_open } }; // 库加载时执行注册逻辑 jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } // 替换为你当前Kotlin类的全路径(注意用/分隔,伴生对象需加$Companion) jclass clazz = (*env)->FindClass(env, "com/myself/serialporttesting/SerialPort$Companion"); if (clazz == NULL) { __android_log_print(ANDROID_LOG_ERROR, "SerialPortBridge", "Failed to find target class"); return JNI_ERR; } if ((*env)->RegisterNatives(env, clazz, methods, sizeof(methods)/sizeof(methods[0])) < 0) { __android_log_print(ANDROID_LOG_ERROR, "SerialPortBridge", "Failed to register methods"); return JNI_ERR; } return JNI_VERSION_1_6; }
编译生成libserialport_bridge.so后,在Kotlin代码中先加载这个桥接库,再加载原库即可完成绑定。
3. 修改原.so文件的函数名(需逆向技能)
如果有逆向修改ELF文件的能力,可以用IDA Pro或Ghidra等工具,把原函数名Java_com_the_old_serialportApp_SerialPort_open修改为对应你当前类路径的名字(比如Java_com_myself_serialporttesting_SerialPort_00024Companion_open,Kotlin伴生对象会生成类似后缀)。但这种方法风险较高,容易破坏.so的结构,仅推荐有逆向经验的开发者尝试。
额外注意:
- 原函数的第二个参数
undefined8 param_2对应JNI中的jobject thiz(类实例),如果用伴生对象,该参数会变成jclass,可能引发参数类型不匹配问题,建议优先使用类实例方法而非伴生对象。 - 原函数的
speed_t和uint类型在arm64-v8a架构下均为32位整数,与Kotlin的Int类型匹配,参数类型无需调整。
内容的提问来源于stack exchange,提问作者jekth

