AndroidHook機制——ART

0x00 前言

之前一直都是在Dalvik 虛擬機上在折騰,從Android 4.4開始開始引入ART,到5.0已經成爲默認選擇。而且最近看到阿里開源的 Dexposed 框架,已經提供了對於android art 模式下的 hook 支持,所以對照着android art 部分的源碼和之前 liang 大牛放出了hook代碼研究了一下ART模式下的hook原理,做個簡單的整理。關於android ART 更詳盡的部分 可以閱讀csdn的博客專欄《老羅的android之旅》。 

Android運行時ART執行類方法的過程分析

Android運行時ART加載類和方法的過程分析

Android運行時ART加載OAT文件的過程分析

 

0x01 ART

ART是Android平臺上的新一代運行時,用來代替dalvik。它主要採用了AOT(Ahead Of Time)的方法,在apk安裝的時候將dalvikbytecode一次性編譯成arm本地指令(但是這種AOT與c語言等還是有本質不同的,還是需要虛擬機的環境支持),這樣在運行的時候就無需進行任何解釋或編譯便可直接執行。因爲Dalvik執行的是Dex字節碼,通過解釋器執行。雖然Dalvik也會對頻繁執行的代碼進行jIT生成本地機器指令來執行,但畢竟在應用程序運行過程中將Dex字節碼翻譯成本地機器指令也會影響到應用程序本身的執行。因此ART節省了運行時間,提高了效率,但是在一定程度上使得應用安裝的時間變長,空間佔用變大。

下圖是ART 的源碼目錄結構:

                          

中間有幾個目錄比較關鍵,

首先是dex2oat,負責將dex文件給轉換爲oat文件,具體的翻譯工作需要由compiler來完成,最後編譯爲dex2oat;

其次是runtime目錄,內容比較多,主要就是運行時,編譯爲libart.so用來替換libdvm.so,dalvik是一個外殼,其中還是在調用ART runtime;

oatdump也是一個比較重要的工具,編譯爲oatdump程序,主要用來對oat文件進行分析並格式化顯示出文件的組成結構;

jdwpspy是java的調試支持部分,即JDWP服務端的實現。

ART也是由zygote所啓動的,與dalvik的啓動過程完全一樣,保證了由dalvik到ART的無縫銜接。

整個啓動過程是從app_process(/framework/base/cmds/app_process/app_main.cpp)開始的,開始的時候,創建了一個對象AppRuntime runtime,這是個單例,整個系統運行時只有一個,隨着zygote 的fork過程,每個子進程只是在不斷的複製指向這個對象的指針個數。然後開始執行runtime.start方法(/frameworks/base/core/jni/AndrroidRuntime.cpp)。在start方法中會對系統的屬性進行判斷,選擇libdvm.so 或者是libart.so進行鏈接。

複製代碼

/* start the virtual machine */
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
if (startVm(&mJavaVM, &env) != 0) {
     return;
}

複製代碼

可以在JniInvocation.Init函數中看到初始化過程

複製代碼

bool JniInvocation::Init(const char* library) {
#ifdef HAVE_ANDROID_OS
  char default_library[PROPERTY_VALUE_MAX];
  property_get("persist.sys.dalvik.vm.lib", default_library, "libdvm.so");
#else
  const char* default_library = "libdvm.so";
#endif
  if (library == NULL) {
    library = default_library;
  }

  handle_ = dlopen(library, RTLD_NOW);
  if (handle_ == NULL) {
    ALOGE("Failed to dlopen %s: %s", library, dlerror());
    return false;
  }
  if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetDefaultJavaVMInitArgs_),
                  "JNI_GetDefaultJavaVMInitArgs")) {
    return false;
  }
  if (!FindSymbol(reinterpret_cast<void**>(&JNI_CreateJavaVM_),
                  "JNI_CreateJavaVM")) {
    return false;
  }
  if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetCreatedJavaVMs_),
                  "JNI_GetCreatedJavaVMs")) {
    return false;
  }
  return true;
}

複製代碼

而對於libdvm.so或者libart.so都需要提供幾個公用的接口,以達到從Dalvik到ART的無縫銜接。而接下的來調用的JNI_CreateJavaVM()實際上是JniInvocation中的JNI_CreateJavaVM()函數

jint JniInvocation::JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
  return JNI_CreateJavaVM_(p_vm, p_env, vm_args);
}

在之前的JniInvocation::init中函數指針已經選擇保存了libdvm.so或者libart.so中的函數地址,在這裏正式開始劃分Dalvik和ART啓動流程。Android系統通過將ART運行時抽象成一個Java虛擬機,以及通過系統屬性persist.sys.dalvik.vm.lib和一個適配層JniInvocation,就可以無縫地將Dalvik虛擬機替換爲ART運行時。

而hook代碼中對於android運行模式判斷也是如此,和JniInvocation::init函數中一樣,都是判斷系統屬性值。

static bool isArt(){
    char value[PROPERTY_VALUE_MAX];
    property_get("persist.sys.dalvik.vm.lib", value, "");
    LOGI("[+] persist.sys.dalvik.vm.lib = %s", value);
    return strncmp(value, "libart.so", strlen("libart.so")) == 0;
}

 

0x02  ART 中方法的調用

還是通過源碼,在ART啓動過程中:

複製代碼

/* frameworks/base/core/jni/AndroidRuntime.cpp */
void AndroidRuntime::start(const char* className, const char* options)
{
    ......
    char* slashClassName = toSlashClassName(className);
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray); 
            ......
        }
    }
    ......
}

複製代碼

跟入CallStaicVoidMethod() 函數

複製代碼

/* art/runtime/jni_internal.cc */
static void CallStaticVoidMethod(JNIEnv* env, jclass, jmethodID mid, ...) {
    va_list ap;
    va_start(ap, mid);
    CHECK_NON_NULL_ARGUMENT(CallStaticVoidMethod, mid);
    ScopedObjectAccess soa(env);
    InvokeWithVarArgs(soa, NULL, mid, ap);
    va_end(ap);}

複製代碼

JNI類的成員函數CallStaticVoidMethod實際上又是通過全局函數InvokeWithVarArgs來調用參數mid指定的方法。

複製代碼

/* art/runtime/jni_internal.cc */
static JValue InvokeWithVarArgs(const ScopedObjectAccess& soa, jobject obj,
                                jmethodID mid, va_list args)
    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
  ArtMethod* method = soa.DecodeMethod(mid);
  Object* receiver = method->IsStatic() ? NULL : soa.Decode<Object*>(obj);
  MethodHelper mh(method);
  JValue result;
  ArgArray arg_array(mh.GetShorty(), mh.GetShortyLength());
  arg_array.BuildArgArray(soa, receiver, args);
  InvokeWithArgArray(soa, method, &arg_array, &result, mh.GetShorty()[0]);
  return result;
}

複製代碼

函數InvokeWithVarArgs將調用參數封裝在一個數組中,然後再調用另外一個函數InvokeWithArgArray來參數mid指定的方法。

複製代碼

/* art/runtime/jni_internal.cc */
void InvokeWithArgArray(const ScopedObjectAccess& soa, ArtMethod* method,
                        ArgArray* arg_array, JValue* result, char result_type)
    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
  uint32_t* args = arg_array->GetArray();
  if (UNLIKELY(soa.Env()->check_jni)) {
    CheckMethodArguments(method, args);
  }
  method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, result_type);
}

複製代碼

可以看到參數mid實際上是一個ArtMethod對象指針,因此,將它轉換爲一個ArtMethod指針(dalvik也是如此),於是就可以得到被調用類方法的相關信息了。

函數InvokeWithArgArray通過ArtMethod類的成員函數Invoke來調用參數method指定的類方法。ArtMethod類的成員函數Invoke的實現如下所示:

複製代碼

/* art/runtime/mirror/art_method.cc*/
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
                       char result_type) {
  ......

  // Push a transition back into managed code onto the linked list in thread.
  ManagedStack fragment;
  self->PushManagedStackFragment(&fragment);

  Runtime* runtime = Runtime::Current();
  // Call the invoke stub, passing everything as arguments.
  if (UNLIKELY(!runtime->IsStarted())) {
    ......
    if (result != NULL) {
      result->SetJ(0);
    }
  } else {
    const bool kLogInvocationStartAndReturn = false;
    if (GetEntryPointFromCompiledCode() != NULL) {
      ......
#ifdef ART_USE_PORTABLE_COMPILER
      (*art_portable_invoke_stub)(this, args, args_size, self, result, result_type);
#else
      (*art_quick_invoke_stub)(this, args, args_size, self, result, result_type);
#endif
      if (UNLIKELY(reinterpret_cast<int32_t>(self->GetException(NULL)) == -1)) {
        // Unusual case where we were running LLVM generated code and an
        // exception was thrown to force the activations to be removed from the
        // stack. Continue execution in the interpreter.
        self->ClearException();
        ShadowFrame* shadow_frame = self->GetAndClearDeoptimizationShadowFrame(result);
        self->SetTopOfStack(NULL, 0);
        self->SetTopOfShadowStack(shadow_frame);
        interpreter::EnterInterpreterFromDeoptimize(self, shadow_frame, result);
      }
      ......
    } else {
      ......
      if (result != NULL) {
        result->SetJ(0);
      }
    }
  }

  // Pop transition.
  self->PopManagedStackFragment(fragment);
}

複製代碼

 整個過程的重點就在art_protable_invoke_stub 和 art_quick_invoke_stub上,這也是整個hook工作的關鍵。函數中根據預定義宏ART_USE_PORTABLE_COMPILER來判斷是protable 還是 quick 的方式。這裏的protable 和 quick是android對於編譯dex文件採用的兩種不同的後端,protable生成的oat文件和傳統的so,dll文件類似,處理不同模塊之間的調用關係時需要重定位操作,而quick是通過線程的TLS中的跳轉表來實現,不需要重定位操作,因此加載的速度更快。而android默認的是採用的quick,所以我們只分析quick的調用過程,也就是這裏的art_quikc_invoke_stub。更詳細的過程參考《老羅的android之旅》。

 我們繼續看art_quick_invoke_stub的源碼:

複製代碼

/*art/runtime/arch/arm/quick_entrypoints_arm.S*/   
    /*
     * Quick invocation stub.
     * On entry:
     *   r0 = method pointer
     *   r1 = argument array or NULL for no argument methods
     *   r2 = size of argument array in bytes
     *   r3 = (managed) thread pointer
     *   [sp] = JValue* result
     *   [sp + 4] = result type char
     */
ENTRY art_quick_invoke_stub
    push   {r0, r4, r5, r9, r11, lr}       @ spill regs
    .save  {r0, r4, r5, r9, r11, lr}
    .pad #24
    .cfi_adjust_cfa_offset 24
    .cfi_rel_offset r0, 0
    .cfi_rel_offset r4, 4
    .cfi_rel_offset r5, 8
    .cfi_rel_offset r9, 12
    .cfi_rel_offset r11, 16
    .cfi_rel_offset lr, 20
    mov    r11, sp                         @ save the stack pointer
    .cfi_def_cfa_register r11
    mov    r9, r3                          @ move managed thread pointer into r9
    mov    r4, #SUSPEND_CHECK_INTERVAL     @ reset r4 to suspend check interval
    add    r5, r2, #16                     @ create space for method pointer in frame
    and    r5, #0xFFFFFFF0                 @ align frame size to 16 bytes
    sub    sp, r5                          @ reserve stack space for argument array
    add    r0, sp, #4                      @ pass stack pointer + method ptr as dest for memcpy
    bl     memcpy                          @ memcpy (dest, src, bytes)
    ldr    r0, [r11]                       @ restore method*
    ldr    r1, [sp, #4]                    @ copy arg value for r1
    ldr    r2, [sp, #8]                    @ copy arg value for r2
    ldr    r3, [sp, #12]                   @ copy arg value for r3
    mov    ip, #0                          @ set ip to 0
    str    ip, [sp]                        @ store NULL for method* at bottom of frame
    ldr    ip, [r0, #METHOD_CODE_OFFSET]   @ get pointer to the code
    blx    ip                              @ call the method
    mov    sp, r11                         @ restore the stack pointer
    ldr    ip, [sp, #24]                   @ load the result pointer
    strd   r0, [ip]                        @ store r0/r1 into result pointer
    pop    {r0, r4, r5, r9, r11, lr}       @ restore spill regs
    .cfi_adjust_cfa_offset -24
    bx     lr
END art_quick_invoke_stub

複製代碼

前面的註釋列出了 函數art_quick_invoke_stub被調用的時候,寄存器r0-r3的值,以及調用棧頂端的兩個值。其中,

r0指向當前被調用的類方法,                   

r1指向一個參數數組地址,                                   

r2記錄參數數組的大小,

r3指向當前線程,

調用棧頂端的兩個元素分別用來保存調用結果及其類型。

真正調用類方法的彙編指令如下:

ldr    ip, [r0, #METHOD_CODE_OFFSET]   @ get pointer to the code
blx    ip                              @ call the method

這裏的 METHOD_CODE_OFFSET 就是在ArtMethod*結構體中的偏移

/*art/runtime/asm_support.h*/
// Offset of field Method::entry_point_from_compiled_code_
#define METHOD_CODE_OFFSET 40

就是進入類方法的入口點,entry_point_from_compiled_code_字段,也是hook點。

 

0x03 調用約定

ART 其實也有兩種執行模式,一種是本地機器指令,一種是類似於虛擬機的解釋執行。ArtMethod結構體中的兩個成員就和類方法入口有關:

複製代碼

// Compiled code associated with this method for callers from managed code.
const void* entry_point_from_compiled_code_;                 //本地機器指令入口  code_offset / GetCompiledCodeToInterpreterBridge (art_quick_to_interpreter_bridge)
                                                                  
// Called by the interpreter to execute this method.
EntryPointFromInterpreter* entry_point_from_interpreter_;  //解釋執行入口  artInterpreterToInterpreterBridge / artInterpreterToCompiledCodeBridg

複製代碼

這兩個成員都指針,其中EntryPointFromInterpreter* 是函數指針類型,實際上也就是一種調用,表示調用者是來自解釋執行方式的一種調用約定

typedef void (EntryPointFromInterpreter)(Thread* self, MethodHelper& mh,
        const CodeItem* code_item, ShadowFrame* shadow_frame,
        JValue* result);

entry_point_from_interpreter_ 是作爲調用者是解釋執行的入口函數,也是分爲兩種情況:

  1.當前ArtMethod對應的方法如果是解釋執行話,將entry_point_from_interpreter_ 設置爲artInterpreterToInterpreterBridge;

  2.當前ArtMethod 對應的是方法是機器指令的話,就entry_point_from_interpreter_設置爲artInterpreterToCompiledCodeBridge

 

而entry_point_from_compiled_code_表示調用者是機器指令的類方法入口,而他的值也是分爲兩種情況:

  1.被調用的方法,也就是ArtMethod 所對應的方法如果需要通過解釋執行,則賦值爲GetCompiledCodeToInterpreterBridge() 函數的返回值;

  2.ArtMethod 所對應的方法如果是本地機器指令,則直接指向方法在oat文件中的指令。

這兩個字段的值的問題,更詳細的可以閱讀android art/runtime/class_linker.cc 文件中LinkCode()方法的源碼,而這裏我們hook的就是針對entry_point_from_compiled_code_ 字段。

可以通過art_quick_invoke_stub 彙編代碼得出在調用ArtMethod 應該方法的執行入口時的棧幀佈局:

複製代碼

   -(low)
   | caller(Method *)    | <- sp 
   | arg1                | <- r1
   | arg2                | <- r2
   | arg3                | <- r3
   | ...                 | 
   | argN                |
   | callee(Method *)    | <- r0
   +(high)

複製代碼

前三個參數還會額外地保存在寄存器r1、r2和r3中。這樣對於小於等於3個參數的類方法,就可以通過訪問寄存器來快速地獲得參數。

 注意,傳遞給被調用類方法的參數並不是從棧頂第一個位置(一個位置等於一個字長,即4個字節)開始保存的,而是從第二個位置開始的,即sp + 4。這是因爲棧頂的第一個位置是預留用來保存用來描述當調用類方法(Caller)的ArtMethod對象地址的。由於函數art_quick_invoke_stub是用來從外部進入到ART運行時的,即不存在調用類方法,因此這時候棧頂第一個位置會被設置爲NULL。

 

0x04  Hook

之前說過,Method的id也就是jmethod實際上是一個指針,指向的就是代碼類方法的ArtMethod結構體,通過類型轉換就可以獲得目標類方法的ArtMethod的指針

ArtMethod *artmeth = reinterpret_cast<ArtMethod *>(methid);

獲得了ArtMethod* ,就可以設置類方法的entrypoint:

複製代碼

 if(art_quick_dispatcher != artmeth->GetEntryPointFromCompiledCode()){
     uint64_t (*entrypoint)(ArtMethod* method, Object *thiz, u4 *arg1, u4 *arg2);
     entrypoint = (uint64_t (*)(ArtMethod*, Object *, u4 *, u4 *))artmeth->GetEntryPointFromCompiledCode();

     info->entrypoint = (const void *)entrypoint;
     info->nativecode = artmeth->GetNativeMethod();

     artmeth->SetEntryPointFromCompiledCode((const void *)art_quick_dispatcher);

複製代碼

也就是如果替換了entry_point_from_compiled_code_的值,使其指向我們的代碼art_quick_diapatcher,這時art_quick_invoke_stub調用我們自己的代碼,但是調用約定並不是普通的arm下C/C++的調用約定,所以我們需要用匯編代碼來對堆棧進行處理,然後再調用真正的額外執行的C++代碼,而在C++代碼中也需要返回原始的方法,同樣的也需要對堆棧進行處理,同樣需要藉助彙編來還原堆棧,調用原始的entrypoint。

複製代碼

ENTRY art_quick_dispatcher
    push     {r4, r5, lr}           @ sp - 12
    mov     r0, r0                  @ pass r0 to method
    str     r1, [sp, #(12 + 4)]     @ arg array
    str     r2, [sp, #(12 + 8)]
    str     r3, [sp, #(12 + 12)]
    mov     r1, r9                  @ pass r1 to thread
    add     r2, sp, #(12 + 4)       @ pass r2 to args array
    add     r3, sp, #12             @ pass r3 to old SP
    blx     artQuickToDispatcher    @ (Method* method, Thread*, u4 **, u4 **)
    pop     {r4, r5, pc}            @ return on success, r0 and r1 hold the result
END art_quick_dispatcher

複製代碼

上面的彙編代碼art_quick_dispatcher就是替換原始entrypoint的值,處理堆棧,然後調用自己的C++函數artQuickToDispatcher(),之後在artQuickToDispatcher()調用原始的entrypoint。當然這裏對於原始的entrypoint是不能直接進行調用的,需要在利用一段彙編代碼,將堆棧還原成art_quick_invoke_stub調用entrypoint時的樣子。

複製代碼

/*
 *
 * Art Quick Call Entrypoint
 * On entry:
 *  r0 = method pointer
 *  r1 = thread pointer
 *  r2 = args arrays pointer
 *  r3 = old_sp
 *  [sp] = entrypoint
 */
ENTRY art_quick_call_entrypoint
    push    {r4, r5, lr}           @ sp - 12
    sub     sp, #(40 + 20)         @ sp - 40 - 20
    str     r0, [sp, #(40 + 0)]    @ var_40_0 = method_pointer
    str     r1, [sp, #(40 + 4)]    @ var_40_4 = thread_pointer
    str     r2, [sp, #(40 + 8)]    @ var_40_8 = args_array
    str     r3, [sp, #(40 + 12)]   @ var_40_12 = old_sp
    mov     r0, sp
    mov     r1, r3
    ldr     r2, =40
    blx     memcpy                 @ memcpy(dest, src, size_of_byte)
    ldr     r0, [sp, #(40 + 0)]    @ restore method to r0
    ldr     r1, [sp, #(40 + 4)]
    mov     r9, r1                 @ restore thread to r9
    ldr     r5, [sp, #(40 + 8)]    @ pass r5 to args_array
    ldr     r1, [r5]               @ restore arg1
    ldr     r2, [r5, #4]           @ restore arg2
    ldr     r3, [r5, #8]           @ restore arg3
    ldr     r5, [sp, #(40 + 20 + 12)] @ pass ip to entrypoint
    blx     r5
    add     sp, #(40 + 20)
    pop     {r4, r5, pc}           @ return on success, r0 and r1 hold the result
END art_quick_call_entrypoint

複製代碼

也就是art_quick_call_entrypoint恢復原來的堆棧,調用原始的entrypoint。但是,還有一個問題存在,也就是ART中關於延遲加載的問題。

 LinkCode 源碼

 

複製代碼

 1 /* art/runtime/class_linker.cc*/
 2 static void LinkCode(SirtRef<mirror::ArtMethod>& method, const OatFile::OatClass* oat_class,
 3                      uint32_t method_index)
 4     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
 5   // Method shouldn't have already been linked.
 6   //判斷類方法是否已經加載鏈接了
 7   DCHECK(method->GetEntryPointFromCompiledCode() == NULL);
 8   // Every kind of method should at least get an invoke stub from the oat_method.
 9   // non-abstract methods also get their code pointers.
10 
11 /*method_index描述的索引號可以在oat_class表示的OatClass結構體中找到一個OatMethod結構體oat_method。
12   這個OatMethod結構描述了類方法method的本地機器指令相關信息,
13   通過調用它的成員函數LinkMethod可以將這些信息設置到參數method描述的ArtMethod對象中去
14 */
15   const OatFile::OatMethod oat_method = oat_class->GetOatMethod(method_index);
16 //在LinkMethod中將ArtMethod中的entry_point_from_compiled_code_設置爲code_offset
17   oat_method.LinkMethod(method.get());
18 
19   // Install entry point from interpreter.
20   Runtime* runtime = Runtime::Current();
21   boolenter_interpreter=NeedsInterpreter(method.get(), method->GetEntryPointFromCompiledCode());
22 /*爲了統一管理,爲一個類方法都設置一個解釋器入口點。需要通過解釋執行的類方法的解釋器入口點函數是artInterpreterToInterpreterBridge,
23   它會繼續通過解釋器來執行該類方法。需要通過本地機器指令執行的類方法的解釋器入口點函數是artInterpreterToCompiledCodeBridge,
24   它會間接地調用該類方法的本地機器指令。*/
25   if (enter_interpreter) {
26 //需要解釋執行 設置entry_point_from_interpreter_
27     method->SetEntryPointFromInterpreter(interpreter::artInterpreterToInterpreterBridge);
28   } else {
29   //native code
30     method->SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge);
31   }
32 
33   if (method->IsAbstract()) {
34 // 設置entry_point_from_compiled_code_
35     method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
36     return;
37   }
38    //trampoline 延遲鏈接
39   if (method->IsStatic() && !method->IsConstructor()) {
40     // For static methods excluding the class initializer, install the trampoline.
41     // It will be replaced by the proper entry point by ClassLinker::FixupStaticTrampolines
42     // after initializing class (see ClassLinker::InitializeClass method).
43     method->SetEntryPointFromCompiledCode(GetResolutionTrampoline(runtime->GetClassLinker()));
44   } else if (enter_interpreter) {
45     // Set entry point from compiled code if there's no code or in interpreter only mode.
46     method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
47   }
48 
49   if (method->IsNative()) {
50     // Unregistering restores the dlsym lookup stub.
51     method->UnregisterNative(Thread::Current());
52   }
53 
54   // Allow instrumentation its chance to hijack code.
55   runtime->GetInstrumentation()->UpdateMethodsCode(method.get(),
56                                                    method->GetEntryPointFromCompiledCode());
57 }

複製代碼

在LinkCode() 的源碼中可以看到這句代碼:

複製代碼

 //trampoline 延遲鏈接
  if (method->IsStatic() && !method->IsConstructor()) {
    // For static methods excluding the class initializer, install the trampoline.
    // It will be replaced by the proper entry point by ClassLinker::FixupStaticTrampolines
    // after initializing class (see ClassLinker::InitializeClass method).
    method->SetEntryPointFromCompiledCode(GetResolutionTrampoline(runtime->GetClassLinker()));
  }

複製代碼

將entrypoint的設置爲GetResolutionTrampoline() 的返回值,而這裏就是

複製代碼

/*art/runtime/entrypoints/entrypoint_utils.h*/
static inline const void* GetCompiledCodeToInterpreterBridge() {
#if defined(ART_USE_PORTABLE_COMPILER)
  return GetPortableToInterpreterBridge();
#else
  return GetQuickToInterpreterBridge();
#endif
}

複製代碼

這裏就是延遲鏈接,意思是在加載和鏈接類的時候,部分方法的entrypoint設置的並不是本地機器指令,或者解釋執行的入口,而是一個代理函數。而這個代理函數真正是幹什麼的?簡單來說就是延遲鏈接,只有當真正調用這個類方法的時候,調用trampoline 函數纔會對這個類方法進行鏈接,設置ArtMethod*的entry_point_from_compiled_code_的值爲真正的本地機器指令或者解釋執行入口。那這時在之前設置的entry_point_from_compiled_code_ 的值爲art_quick_dispatcher的地址就被覆蓋調用了,所以需要在我們自己的artQuickToDispatcher調用完原始的entrypoint以後,再對entrypoint進行一次判斷和賦值:

複製代碼

  /*
    * 處理的就是trampoline 在調用原來的tramp方法以後,重新綁定entry_pooint_from_complied_ 字段*/
    entrypoint = method->GetEntryPointFromCompiledCode();
    if(entrypoint != (const void *)art_quick_dispatcher){
        LOGW("[*] entrypoint was replaced. %s->%s", info->classDesc, info->methodName);

        method->SetEntryPointFromCompiledCode((const void *)art_quick_dispatcher);

複製代碼

整個ART模式下的hook流程大致就是如此。

 項目代碼:https://github.com/boyliang/AllHookInOne

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章