dalvik淺析二:jni、so

  android大多使用java來開發,java中有個概念叫jni。固然說到jni,必然是少不了native code。在android中就是so庫。咱們來分析下jni在android dalvik的使用,如下篇幅是我對Dalvik虛擬機JNI方法的註冊過程分析文章的學習和註解。在這以前先說幾個概念:html

  JavaVM:虛擬機實例,也能夠經過全局變量gDvm所描述的一個DvmGlobals結構體的成員變量vmList來描述的;java

  JNIEnv:用來描述當前線程的Java環境,利用此結構能夠調用在Zygote中註冊(看Zygote的啓動過程)到dalvik裏的jni方法android

  jobject:來描述當前正在執行JNI方法的Java對象app

  下圖取自老羅的博客(下文就是圍繞此圖展開)函數

  

  咱們在java函數在load so庫:學習

System.loadLibrary("nanosleep");    

  so庫的編寫:atom

static jint shy_luo_jni_ClassWithJni_nanosleep(JNIEnv* env, jobject clazz, jlong seconds, jlong nanoseconds) 
{
    struct timespec req;
    req.tv_sec  = seconds;
    req.tv_nsec = nanoseconds;
    
    return nanosleep(&req, NULL);
}

static const JNINativeMethod method_table[] = {
    {"nanosleep", "(JJ)I", (void*)shy_luo_jni_ClassWithJni_nanosleep},
};

extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) 
{
      JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return result;
    }

    jniRegisterNativeMethods(env, "shy/luo/jni/ClassWithJni", method_table, NELEM(method_table));
    
    return JNI_VERSION_1_4;
}

 

  java層在loadLibrary so庫時,系統其實作了這麼幾件事(上圖step 4):spa

  1 調用dlopen在進程加載so庫;看我 android so加載.net

  2 調用dlsym得到so庫中名稱爲「JNI_OnLoad」的函數的地址並保存在保存在函數指針func中:func= dlsym(handle, "JNI_OnLoad");線程

  3 執行so庫中JNI_OnLoad函數: version = (*func)(gDvm.vmList, NULL);  

  這個時候咱們的視線轉移到C++層:JNI_OnLoad(在這裏註冊jni方法)。看代碼,實際是調用jniRegisterNativeMethods函數。可是看上圖咱們知道實際以前幾個函數沒有實質突破,仍是靠dvmRegisterJNIMethod來執行:

static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,
    const char* signature, void* fnPtr)
{
  // 解釋下參數:
  // clazz:類名"shy/luo/jni/ClassWithJni";
  // methodName:須要註冊的jni方法名 nanosleep;
  // signature:方法的簽名 實質是方法的參數和返回值,區別不一樣參數的函數
  // fnPtr: jni方法函數地址 即
shy_luo_jni_ClassWithJni_nanosleep函數;dalvik執行的就是這個函數,很重要哎
    Method* method;
    ......
    method = dvmFindDirectMethodByDescriptor(clazz, methodName, signature);
    ......
    dvmUseJNIBridge(method, fnPtr);
    ......
}

  在這裏我要補充的是注意dvmFindDirectMethodByDescriptor函數。jni方法在java層對應的函數就是個空的,而jni註冊就是要把jni方法綁定到對應的java層函數體中。那咱們怎麼找到讓他們對應起來呢?dvmFindDirectMethodByDescriptor利用methodName和signature參數來達到上述目的。在dvmFindDirectMethodByDescriptor中,獲得class類的函數列表methods;循壞比較methods[index]的args、returnType和signature是否相等,若相等則爲jni方法找到了在java層的函數(jni:我在上層也是有人滴^_^)。ok,找到method了,趕快綁定啊也可別讓她逃走了啊。

void dvmUseJNIBridge(Method* method, void* func)  
{  
    DalvikBridgeFunc bridge = shouldTrace(method)  
        ? dvmTraceCallJNIMethod  
        : dvmSelectJNIBridge(method);  
    dvmSetNativeFunc(method, bridge, func);  
}  
 這裏有個bridge的東東,咱們這裏先不看後面會說起(詳情看老羅的文章吧)。直接看dvmSetNativeFunc
void dvmSetNativeFunc(Method* method, DalvikBridgeFunc func,  
    const u2* insns)  
{  
    ......  
    // 參數func  = bridge
   // 參數 insns = func(
dvmSetNativeFunc(method, bridge, func)); 即func = (void*)shy_luo_jni_ClassWithJni_nanosleep
if (insns != NULL) {  
        /* update both, ensuring that "insns" is observed first */  
        method->insns = insns;  
        android_atomic_release_store((int32_t) func,  
            (void*) &method->nativeFunc);  
    } else {  
        /* only update nativeFunc */  
        method->nativeFunc = func;  
    }  
  
    ......  
}
 在dvmSetNativeFunc函數,既然是把bridge賦值給method->nativeFunc,shy_luo_jni_ClassWithJni_nanosleep賦值給method->insns,那何時纔會執行到shy_luo_jni_ClassWithJni_nanosleep啊(在dalvik中,若method爲native則會執行method->nativeFunc)!帶着這個疑問,咱們回頭看dvmSelectJNIBridge:
/* 
 * Returns the appropriate JNI bridge for 'method', also taking into account 
 * the -Xcheck:jni setting. 
 */  
static DalvikBridgeFunc dvmSelectJNIBridge(const Method* method)  
{  
    enum {  
        kJNIGeneral = 0,  
        kJNISync = 1,  
        kJNIVirtualNoRef = 2,  
        kJNIStaticNoRef = 3,  
    } kind;  
    static const DalvikBridgeFunc stdFunc[] = {  
        dvmCallJNIMethod_general,  
        dvmCallJNIMethod_synchronized,  
        dvmCallJNIMethod_virtualNoRef,  
        dvmCallJNIMethod_staticNoRef  
    };  
    static const DalvikBridgeFunc checkFunc[] = {  
        dvmCheckCallJNIMethod_general,  
        dvmCheckCallJNIMethod_synchronized,  
        dvmCheckCallJNIMethod_virtualNoRef,  
        dvmCheckCallJNIMethod_staticNoRef  
    };  
  
    bool hasRefArg = false;  
  
    if (dvmIsSynchronizedMethod(method)) {  
        /* use version with synchronization; calls into general handler */  
        kind = kJNISync;  
   .....if (hasRefArg) {  
            /* use general handler to slurp up reference args */  
            kind = kJNIGeneral;  
        } else {  
            /* virtual methods have a ref in args[0] (not in signature) */  
            if (dvmIsStaticMethod(method))  
                kind = kJNIStaticNoRef;  
            else  
                kind = kJNIVirtualNoRef;  
        }  
    }  
  
    return dvmIsCheckJNIEnabled() ? checkFunc[kind] : stdFunc[kind];  
}  
直接看最後返回dvmIsCheckJNIEnabled() ? checkFunc[kind] : stdFunc[kind];假設返回stdFunc[kind]。看上面stdFunc定義,可知bridge實際上是函數。咱們再假定是最普通dvmCallJNIMethod_general,那麼在dvmSetNativeFunc裏method->nativeFunc = dvmCallJNIMethod_general。ok,那咱們就看看dvmCallJNIMethod_general是在哪裏執行咱們的shy_luo_jni_ClassWithJni_nanosleep。
void dvmCallJNIMethod_general(const u4* args, JValue* pResult,
    const Method* method, Thread* self)
    ......
    dvmPlatformInvoke(env, staticMethodClass,
        method->jniArgInfo, method->insSize, modArgs, method->shorty,
        (void*)method->insns, pResult);
    ......
}

  接着看dvmPlatformInvoke:  

void dvmPlatformInvoke(void* pEnv, ClassObject* clazz, int argInfo, int argc,
    const u4* argv, const char* shorty, void* func, JValue* pReturn)
{
    ......
    ffi_call(&cif, FFI_FN(func), pReturn, values);
}

  wow,看到沒有最終仍是調用了method->insns(在java函數中,dalvik中的method->insns存的是函數體的dex代碼)即shy_luo_jni_ClassWithJni_nanosleep。

  ok,上圖中的步驟已所有走完。發現jni註冊實質就是把native函數體綁定到對應的java層函數體,讓dalvik發現函數是native時有native代碼能夠執行。

 

  思考:

  1 method是native時,dalvik纔會調用method->nativeFunc來執行;那這個native標誌是在何時被設置呢?dex被載入dalvik時?

    在dex文件裏的class—>method的accessflag屬性:定義在/external/emma/core/java12/com/vladium/jcd/cls/IAccessFlags.java

  2 so庫加載過程時dlopen載入,而後執行調用其JNI_OnLoad函數。那具體的執行流程是?so庫的加固是否在這裏作文章呢?

    看後面elf格式、so加載文章

 

  參考資料:

  1 老羅的android之旅

相關文章
相關標籤/搜索