Hot upgrade attempt for Android so
1、 Hot upgrade attempt of so
In Android code, loading so library is realized by calling system.loadlibrary function. But like many features of Android, it only provides loading, not unloading and replacement. In order to study whether the functions such as unloading and upgrading can be realized, we must first understand the JNI so loading process.
In the above process, after loading so with dlopen, JNI will continue to be called_ The onload function initializes some columns through the registernatives function provided by the system, and registers the JNI function provided by the so library to the virtual machine. The so library may not implement JNI_ Onload function, but adopts the method of automatic search.
Android Virtual Opportunities are automatically searched according to the naming rules of JNI specification when calling JNI functions for the first time. By analyzing the Android code, this method will eventually call functions such as dvmsetnaturefunc in the figure above, and save the function address to the virtual machine for next call.
2、 Unloading and reloading
If you want to provide the ability of hot upgrade, the first thing to do is to close the open so file. However, the Android virtual machine does not provide an interface such as unloadlibrary, so we need to implement it ourselves.
According to the analysis in the previous section, dlopen is used by loadlibrary to load files in the native layer, and the corresponding system interface is dlclose. Since there is no corresponding unregister for the next registernatives, let's put it aside and see the effect of unloading before processing.
Uninstall so
The interface providing unloading capability needs to complete the following tasks:
1. Find the handle to unload so;
2. Call JNI_ OnUnload;
3. Call dlclose to unload.
The unloading function we wrote is as follows:
Dlclose is called twice, because dlopen in the function will increase the reference count of handle.
After uninstallation, what happens if we try to call the original JNI function first? Obviously, there will be a crash.
The reason is that so has registered the address of JNI function in the virtual machine when loading or using, and the original address becomes illegal after unloading, resulting in crash. What happens when we reload so again?
Reload so
By analyzing the code, we can see that since so has been loaded using system.loadlibrary, we have not touched the JNI layer before unloading, so calling loadlibrary repeatedly will not reload so. We can reload so with dlopen in the native layer according to the process of dvmloadnativecode.
According to the previous analysis, it is easy to write the loading function:
3、 Problems and Solutions
After reloading so, call the original JNI function again. Find that sometimes you will succeed, but sometimes you will crash. After tracing, it is noted that the address of the error function is the same as that before unloading, but the address loaded by so has changed.
When dlopen loads so, it is not guaranteed that it will be loaded at the same address every time. Even if it can be loaded to the same address, if the so file changes due to the upgrade, the function address is not accurate. Therefore, to make the new so work, we must also try to update the function pointer saved by the virtual machine and point it to the correct address of the newly loaded so.
At this time, we need the registernatives we ignored earlier. This function can be used to manually register the JNI function address. Let's repeat the words similar to those in the first section but with different meanings:
In the above process, the so library needs to call JNI after loading with dlopen_ Onload function, initialize some columns through the registernatives function provided by the system, and register a new JNI function address to the virtual machine.
After registering with registernatives, the function address recorded in the virtual machine can be updated even if the address of so changes.
Summary of this paper
If you want to update so at run time, the new so file must implement JNI_ Onload function, and in JNI_ In Onload, the RegisterNatives supplied by the system calls all JNI functions, and can not use the way of automatically finding the JNI function name.
4、 Other issues
The above scheme mainly solves the problems of so unloading, reloading and JNI function call. But in addition to these problems, there are many things to pay attention to in the details of so code.
CRASH
After unloading so, except for the JNI function pointer, other pointers to the so address will also fail, including pointers to static variables, constants, native functions, etc. All pointers that refer to this so address need to be updated.
Memory and resource leaks
There may be various behaviors of allocating memory and resources in native code. Before updating so with the above methods, if these resources are not handled carefully, the original pointer will be lost, resulting in memory leakage.
1. Malloc / MMAP / shmem, etc.
2. Socket, pipe, mutex, thread and other system resources.
3. Using newglobalref to allocate and hold Java objects will cause JAVA memory leakage of the virtual machine after losing pointers.
To sum up, all resources that may be lost or leaked must be saved or deleted before uninstalling so. These jobs can be called by JNI at uninstall time_ Completed in onunload.
Copyright, no reprint