- 靜態(tài)注冊(cè)
一般我們寫(xiě)的java native方法在c層都有對(duì)應(yīng)的方法,例如
java代碼如下
package com.example.administrator.ndkstudydemo.demo;
public class CounterNative {
private native void init();
}
聲明了一個(gè)init本地方法,對(duì)應(yīng)的如果靜態(tài)注冊(cè),會(huì)生成一個(gè)這樣的方法在c代碼里面,c代碼如下
extern "C"
JNIEXPORT void JNICALL
Java_com_example_administrator_ndkstudydemo_demo_CounterNative_init(JNIEnv *env,jobject thiz) {}
這就是典型的靜態(tài)注冊(cè),通過(guò)Java_包名_方法名的規(guī)則生成
正常情況下它沒(méi)什么問(wèn)題,但是它第一相對(duì)不安全,為啥這么說(shuō)?
相對(duì)不安全
-
相對(duì)不安全是因?yàn)槭紫萰ava代碼反編譯者(統(tǒng)稱(chēng)hacker)很容易反編譯,如果你在so寫(xiě)了核心邏輯,hacker就會(huì)通過(guò)java里面的名字按圖索驥的找到c里面的代碼,這樣就有利于hacker分析我們的邏輯。 - 另外為什么我們把
相對(duì)引了起來(lái),是因?yàn)槿绻私?code>JNI加載邏輯的的都知道,在System.loadLibrary("so名稱(chēng)");的時(shí)候會(huì)先調(diào)用JNI_OnLoad方法,我們做動(dòng)態(tài)注冊(cè)也會(huì)在這里進(jìn)行(后面會(huì)講到),那么其實(shí)hacker只要在找不到靜態(tài)注冊(cè)的方法,就可以分析這個(gè)函數(shù)里的邏輯,同樣可以找到方法。
- 動(dòng)態(tài)注冊(cè)
首先java代碼不變,我們改變c++代碼
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
jvm = vm;
if (jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
//動(dòng)態(tài)注冊(cè)函數(shù)
if (registerNatives(env) != JNI_TRUE) {
return -1;
}
return JNI_VERSION_1_6;
}
首先重寫(xiě)JNI_OnLoad方法,核心邏輯是注釋部分代碼if (registerNatives(env) != JNI_TRUE) { return -1; },接下來(lái)我們看一下這個(gè)registerNatives函數(shù)實(shí)現(xiàn)
//java類(lèi)全路徑
static const char *classPathName = "com/example/administrator/ndkstudydemo/demo/CounterNative";
//方法數(shù)組
static JNINativeMethod methods[] = {
{"init", "()V", (void *) Java_com_example_administrator_ndkstudydemo_demo_CounterNative_nativeSetup}
};
static int registerNatives(JNIEnv *env) {
registerNativeMethods(env, classPathName, methods, sizeof(methods) / sizeof(methods[0]));
return JNI_TRUE;
}
static int registerNativeMethods(JNIEnv *env, const char *className,
JNINativeMethod *gMethods, int numMethods) {
jclass clazz = env->FindClass(className);
//調(diào)用env注冊(cè)本地方法的函數(shù),傳入需要修改的方法數(shù)組,和需要修改的方法數(shù)
env->RegisterNatives(clazz, gMethods, numMethods);
return JNI_TRUE;
}
其實(shí)就是調(diào)用env的一個(gè)注冊(cè)本地方法的函數(shù)env->RegisterNatives(clazz, gMethods, numMethods),它需要三個(gè)參數(shù),第一個(gè)是方法所在的class對(duì)象,還有對(duì)應(yīng)的java方法數(shù)組,第三個(gè)參數(shù)就是方法數(shù)組數(shù)目,這樣就完成了動(dòng)態(tài)注冊(cè)本地方法
- 總結(jié)
java的本地方法名init只是本地方法nativeSetup的一個(gè)別名,在vm方法表里存儲(chǔ)了他們的對(duì)應(yīng)關(guān)系,才使得java方法能正確訪問(wèn)到本地方法