中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android so的熱升級嘗試

發布時間:2020-09-08 12:22:44 來源:腳本之家 閱讀:162 作者:瘋中之瘋 欄目:移動開發

一、So的熱升級嘗試

在Android代碼中,加載so庫是通過調用System.loadLibrary函數實現的。但和Android的許多特性一樣,只提供了加載,而沒有卸載和更換等功能。為了研究能否實現卸載和升級等功能,首先要了解清楚JNI so加載的流程。

Android so的熱升級嘗試 

在以上流程中,使用dlopen加載so之后,會繼續調用JNI_Onload函數,通過系統提供的RegisterNatives函數完成一些列初始化,向虛擬機注冊so庫提供的JNI函數。So庫也可以不實現JNI_Onload函數,而是采用自動查找的方式。

Android虛擬機會在首次調用JNI函數時按照JNI規范的命名規則自動查找。通過分析Android代碼,這種方法最終也會調用到上圖中的dvmSetNativeFunc等函數,將函數地址保存到虛擬機中供下次調用。

二、卸載及重新加載

如果想要提供熱升級的能力,首先要做的是關閉已打開的so文件。但Android虛擬機沒有提供unloadLibrary這樣的接口,因此需要我們自己自己實現。

根據上一節的分析,loadLibrary在native層加載文件使用的是dlopen,與之對應的系統接口是dlclose。而接下來的RegisterNatives由于沒有對應的unRegister,我們暫且先放一放,看看卸載的效果再來處理。

卸載so

提供卸載能力的接口需要完成以下幾項任務:

1、找到要卸載so的句柄;

2、調用JNI_OnUnload;

3、調用dlclose卸載。

如下便是我們寫出的卸載函數:

void JNICALL Java_com_example_Unloader_unload(JNIEnv* env, jobject obj)
{
void* handle = dlopen(“/data/data/com.example.unloader/lib/libtest.so”, RTLD_GLOBAL);
if(!handle) return;
LOGD(“unload so: 0x%x\n”, (unsigned int)handle);
void* symbol = dlsym(handle, “JNI_OnUnload”);
if(symbol)
{
OnLoadFunc func = (OnLoadFunc)symbol;
JavaVM* jvm = 0;
(*env)->GetJavaVM(env, &jvm);
if(jvm)
(*func)(jvm, 0);
}
int result = dlclose(handle);
LOGD(“unload result %d\n”, result);
result = dlclose(handle);
result = dlclose(handle);
LOGD(“unload result %d\n”, result);
}

其中dlclose調用了2次,因為函數內的dlopen會增加handle的引用計數。

卸載之后如果我們先嘗試調用原來的JNI函數,會發生什么事呢?顯而易見會出現crash。

Android so的熱升級嘗試 

究其原因,是由于so在加載或使用時已經在虛擬機中注冊了JNI函數的地址,卸載后原地址變為非法地址,導致crash。那我們再重新加載so會發生什么呢?

重新加載so

分析代碼可得知,由于so已經使用System.loadLibrary加載過,我們之前在卸載時也沒有觸及到JNI層,因此重復調用loadLibrary并不會重新加載so。我們可以按照dvmLoadNativeCode的流程,在native層用dlopen重新加載so。

按照之前的分析,很容易就能寫出加載函數:

void JNICALL Java_com_example_Unloader_load(JNIEnv* env, jobject obj)
{
void* handle = dlopen(“/data/data/com.example.Unloader/lib/libtest.so”, RTLD_GLOBAL);
if(!handle) return;
LOGD(“load so: 0x%x\n”, (unsigned int)handle);
void* symbol = dlsym(handle, “JNI_OnLoad”);
if(symbol)
{
OnLoadFunc func = (OnLoadFunc)symbol;
JavaVM* jvm = 0;
(*env)->GetJavaVM(env, &jvm);
if(jvm)
(*func)(jvm, 0);
}
}

三、問題及解決

重新加載so后,再次調用原來的JNI函數。發現有時候會成功,但有時候也會crash。經過追蹤后注意到,報錯的函數地址和卸載前一樣,但so加載的地址變化了。

Android so的熱升級嘗試 

由于dlopen加載so時,并不能保證每次都加載在同一地址上。即使能夠加載到同一地址,如果升級造成so文件變化,那函數地址也是不準確的。所以要使新的so工作,那我們也必須要設法更新虛擬機已經保存的函數指針,將其指向新加載so的正確地址。

這時候就需要我們之前忽略的RegisterNatives登場了,這個函數可以用來手動注冊JNI函數地址。讓我們重復與第一節文字相似但含義不同的這段話:

在以上流程中,so庫在使用dlopen加載后,還需要調用JNI_Onload函數,通過系統提供的RegisterNatives函數完成一些列初始化,向虛擬機注冊新的JNI函數地址。

static JNINativeMethod gMethods[] = {
{ “foo”, “()V”, (void*)Java_com_tencent_example_foo },
};
// Register several native methods for one class.
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}

使用RegisterNatives注冊后,即使so的地址發生變化,也能夠更新虛擬機中記錄的函數地址。

Android so的熱升級嘗試 

本篇小結

如果想要在運行時更新so,則新的so文件必須要實現JNI_Onload函數,并且在JNI_Onload中調用系統提供的RegisterNatives注冊所有的JNI函數,不能使用自動查找JNI函數名的方式。

四、其他問題

以上方案主要解決了so的卸載,重加載和JNI函數調用問題。但除了這些問題之外,so代碼的細節上還有許多要注意的地方。

CRASH

卸載so后,除了JNI函數的指針,其它指向so地址的指針也都會失效,包括指向靜態變量,常量,native函數的指針等。所有引用到該so地址的指針都需要更新。

內存和資源泄漏

native代碼中可能存在各種分配內存和資源的行為,使用以上方法更新so前,如果沒有仔細處理這些資源,就會丟失原指針,造成內存泄漏。

1、malloc/mmap/shmem等方式分配的內存。

2、socket, pipe, mutex, thread等各種系統資源。

3、使用NewGlobalRef分配并持有Java對象,丟失指針后會造成虛擬機的Java內存泄漏。

綜上所述,對于所有可能丟失,造成泄露的資源,必須在卸載so前設法保存或刪除。這些工作可以在卸載時調用的JNI_OnUnload中完成。

版權所屬,禁止轉載

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

图木舒克市| 阿拉善左旗| 伊川县| 砚山县| 准格尔旗| 恩施市| 新密市| 云和县| 湖口县| 沭阳县| 嘉义市| 麻城市| 搜索| 郑州市| 林周县| 仙居县| 沧源| 柳林县| 微山县| 三河市| 溧水县| 日喀则市| 于都县| 卫辉市| 鄂温| 堆龙德庆县| 襄垣县| 镶黄旗| 苏尼特右旗| 湘阴县| 嵊泗县| 承德县| 彰化县| 镇安县| 得荣县| 福泉市| 临海市| 富民县| 湖州市| 登封市| 巧家县|