<dd id="sga4y"><optgroup id="sga4y"></optgroup></dd>
<nav id="sga4y"><code id="sga4y"></code></nav>
  • <optgroup id="sga4y"></optgroup>

    原文地址:http://drops.wooyun.org/tips/10841

    之前因為需要,研究了一下ART的相關源碼,也做了一些記錄與總結,現在重新整理了一下與大家共同討論和交流一下。

    0x00 概述


    ART是Android平臺上的新一代運行時,用來代替dalvik。它主要采用了AOT的方法,在apk安裝的時候將dalvikbytecode一次性編譯成arm本地指令(但是這種AOT與c語言等還是有本質不同的,還是需要虛擬機的環境支持),這樣在運行的時候就無需進行任何解釋或編譯便可直接執行,節省了運行時間,提高了效率,但是在一定程度上使得安裝的時間變長,空間占用變大。

    從Android的源碼上看,ART相關的內容主要有compiler和與之相關的程序dex2oat、runtime、Java調試支持和對oat文件進行解析的工具oatdump。

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

    p1

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

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

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

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

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

    0x01 oat文件


    oat文件的格式,可以從dex2oat和oatdump兩個目錄入手。簡單的說,oat文件是嵌套在一個elf文件的格式中的。在elf文件的動態符號表中有三個重要的符號:oatdata、oatexec、oatlastword,分別表示oat的數據區,oat文件中的native code和結束位置。這些關系結構在圖中說明的很清楚,簡單理解就是在oatdata中,保存有原來的dex文件內容,在頭部還保留了尋址到dex文件內容的偏移地址和指向對應的oat class偏移,oat class中還保存了對應的native code的偏移地址,這樣也就間接的完成了dexbytecode和native code的對應關系。

    具體的一些代碼可以參考/art/dex2oat/dex2oat.cc中的static int dex2oat(intargc, char** argv)函數和/art/oatdump/oatdump.ccstatic intoatdump(intargc, char** argv)的函數,可以很快速的理解oat文件的格式和解析。在/art/compiler/elf_writer_quick.cc中的Write函數很值得參考。

    0x02 運行時的啟動


    ART運行時的啟動過程很早,是由zygote所啟動的,與dalvik的啟動過程完全一樣,保證了由dalvik到ART的無縫銜接。

    整個啟動過程是從app_processs(/frameworks/base/cmds/app_process/app_main.cpp)開始的,創建了一個對象AppRuntime runtime,這個是一個單例,整個系統運行時只有一個。隨著zygote的fork過程,只是在不斷地復制指向這個對象的指針個每個子進程。然后就開始執行runtime.start方法。這個方法里先調用startVm啟動虛擬機。是由JNI_CreateJavaVM方法具體執行的的,即/art/runtime/jni_internal.ccextern "C" jintJNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args)。然后調用startReg注冊一些native的method。在最后比較重要的是查找到要執行的java代碼的main方法,然后執行進入托管代碼的世界,這也是我們感興趣的地方。

    p2

    如圖,最后調用的是CallStaticVoidMethod,去看看它的實現:

    p3

    再去尋找InvokeWithVarArgs:

    p4

    跳到InvokeWithArgArray:

    p5

    可以看到一個很關鍵的class:

    p6

    即ArtMethod,它的一個成員方法就是負責調用oat文件中的native code的:

    p7

    最后這就是最終的入口:

    p8

    283行的blxip指令就是最終進入native code的位置。可以大致得到結論,通過查找相關的oat文件,得到所需要的類和方法,并將其對應的native code的位置放入ArtMethod結構,最后通過Invoke成員完成調用。下一步的工作需要著重關注的便是native code代碼調用其他的java方法時如何去通過運行時定位和跳轉的。

    注意注釋中描述了ART下的ABI,與標準的ARM調用約定相似,但是R0存放的是調用者的方法的ArtMethod對象地址,R0-R3包含的才是參數,包括this。多余的存放在棧中,從SP+16的位置開始。返回值同樣通過R0/R1傳遞。R9指向運行時分配的當前的線程對象指針。

    0x03 類加載


    類加載的任務主要由ClassLinker類來負責,先看一下這個過程的順序圖:

    p9

    順序圖中以靜態成員的初始化和虛函數的初始化為例,描述了調用的邏輯。下面進行詳細的敘述。

    從FindClass開始:

    #!java
    mirror::Class* ClassLinker::FindClass(constchar* descriptor, mirror::ClassLoader* class_loader) {
    ……
    mirror::Class* klass = LookupClass(descriptor, class_loader);
    if (klass != NULL) {
    returnEnsureResolved(self, klass);
      }
    if (descriptor[0] == '[') {
    returnCreateArrayClass(descriptor, class_loader);   
    
      } elseif (class_loader == NULL) {
    DexFile::ClassPathEntry pair = DexFile::FindInClassPath(descriptor, boot_class_path_);
    if (pair.second != NULL) {
    returnDefineClass(descriptor, NULL, *pair.first, *pair.second);
        }
    ……
    }
    

    省略次要的代碼,首先利用LookupClass查找所需要的類是否被加載,對于此場景所以不符合此條件。然后判斷是否是數組類型的類,也跳過此分支,進入到我們最感興趣的DefineClass中。

    #!java
    mirror::Class* ClassLinker::DefineClass(constchar* descriptor,
                                            mirror::ClassLoader* class_loader,
    constDexFile&dex_file,
    constDexFile::ClassDef&dex_class_def) {
    ……
    SirtRef<mirror::Class>klass(self, NULL);
    if (UNLIKELY(!init_done_)) {
    // finish up init of hand crafted class_roots_
    if (strcmp(descriptor, "Ljava/lang/Object;") == 0) {
    klass.reset(GetClassRoot(kJavaLangObject));
        } elseif (strcmp(descriptor, "Ljava/lang/Class;") == 0) {
    klass.reset(GetClassRoot(kJavaLangClass));
        } elseif (strcmp(descriptor, "Ljava/lang/String;") == 0) {
    klass.reset(GetClassRoot(kJavaLangString));
        } elseif (strcmp(descriptor, "Ljava/lang/DexCache;") == 0) {
    klass.reset(GetClassRoot(kJavaLangDexCache));
        } elseif (strcmp(descriptor, "Ljava/lang/reflect/ArtField;") == 0) {
    klass.reset(GetClassRoot(kJavaLangReflectArtField));
        } elseif (strcmp(descriptor, "Ljava/lang/reflect/ArtMethod;") == 0) {
    klass.reset(GetClassRoot(kJavaLangReflectArtMethod));
        } else {
    klass.reset(AllocClass(self, SizeOfClass(dex_file, dex_class_def)));
        }
      } else {
    klass.reset(AllocClass(self, SizeOfClass(dex_file, dex_class_def)));
      }
    klass->SetDexCache(FindDexCache(dex_file));
    LoadClass(dex_file, dex_class_def, klass, class_loader);
    ……
    returnklass.get();
    }
    

    揀重要的部分看,這個方法基本上完成了兩個個功能,即從dex文件加載類和加載過的類插入一個表中,供LookupClass查詢。

    我們關注第一個功能,首先是進行一些內置類的判斷,對于自定義的類則是手動分配空間、,然后查找相關的dex文件,最后進行加載。

    接著看LoadClass方法:

    #!java
    voidClassLinker::LoadClass(constDexFile&dex_file,
    constDexFile::ClassDef&dex_class_def,
    SirtRef<mirror::Class>&klass,
                                mirror::ClassLoader* class_loader) {
    ……
    // Load fields fields.
    const byte* class_data = dex_file.GetClassData(dex_class_def);
    if (class_data == NULL) {
    return;  // no fields or methods - for example a marker interface
      }
    ClassDataItemIteratorit(dex_file, class_data);
      Thread* self = Thread::Current();
    if (it.NumStaticFields() != 0) {
        mirror::ObjectArray<mirror::ArtField>* statics = AllocArtFieldArray(self, it.NumStaticFields());
    if (UNLIKELY(statics == NULL)) {
    CHECK(self->IsExceptionPending());  // OOME.
    return;
        }
    klass->SetSFields(statics);
      }
    if (it.NumInstanceFields() != 0) {
        mirror::ObjectArray<mirror::ArtField>* fields =
    AllocArtFieldArray(self, it.NumInstanceFields());
    if (UNLIKELY(fields == NULL)) {
    CHECK(self->IsExceptionPending());  // OOME.
    return;
        }
    klass->SetIFields(fields);
      }
    for (size_ti = 0; it.HasNextStaticField(); i++, it.Next()) {
    SirtRef<mirror::ArtField>sfield(self, AllocArtField(self));
    if (UNLIKELY(sfield.get() == NULL)) {
    CHECK(self->IsExceptionPending());  // OOME.
    return;
        }
    klass->SetStaticField(i, sfield.get());
    LoadField(dex_file, it, klass, sfield);
      }
    for (size_ti = 0; it.HasNextInstanceField(); i++, it.Next()) {
    SirtRef<mirror::ArtField>ifield(self, AllocArtField(self));
    if (UNLIKELY(ifield.get() == NULL)) {
    CHECK(self->IsExceptionPending());  // OOME.
    return;
        }
    klass->SetInstanceField(i, ifield.get());
    LoadField(dex_file, it, klass, ifield);
      } 
    
    UniquePtr<constOatFile::OatClass>oat_class;
    if (Runtime::Current()->IsStarted() && !Runtime::Current()->UseCompileTimeClassPath()) {
    oat_class.reset(GetOatClass(dex_file, klass->GetDexClassDefIndex()));
      } 
    
    // Load methods.
    if (it.NumDirectMethods() != 0) {
    // TODO: append direct methods to class object
        mirror::ObjectArray<mirror::ArtMethod>* directs =
    AllocArtMethodArray(self, it.NumDirectMethods());
    if (UNLIKELY(directs == NULL)) {
    CHECK(self->IsExceptionPending());  // OOME.
    return;
        }
    klass->SetDirectMethods(directs);
      }
    if (it.NumVirtualMethods() != 0) {
    // TODO: append direct methods to class object
        mirror::ObjectArray<mirror::ArtMethod>* virtuals =
    AllocArtMethodArray(self, it.NumVirtualMethods());
    if (UNLIKELY(virtuals == NULL)) {
    CHECK(self->IsExceptionPending());  // OOME.
    return;
        }
    klass->SetVirtualMethods(virtuals);
      }
    size_tclass_def_method_index = 0;
    for (size_ti = 0; it.HasNextDirectMethod(); i++, it.Next()) {
    SirtRef<mirror::ArtMethod>method(self, LoadMethod(self, dex_file, it, klass));
    if (UNLIKELY(method.get() == NULL)) {
    CHECK(self->IsExceptionPending());  // OOME.
    return;
        }
    klass->SetDirectMethod(i, method.get());
    if (oat_class.get() != NULL) {
    LinkCode(method, oat_class.get(), class_def_method_index);
        }
    method->SetMethodIndex(class_def_method_index);
    class_def_method_index++;
      }
    for (size_ti = 0; it.HasNextVirtualMethod(); i++, it.Next()) {
    SirtRef<mirror::ArtMethod>method(self, LoadMethod(self, dex_file, it, klass));
    if (UNLIKELY(method.get() == NULL)) {
    CHECK(self->IsExceptionPending());  // OOME.
    return;
        }
    klass->SetVirtualMethod(i, method.get());
        DCHECK_EQ(class_def_method_index, it.NumDirectMethods() + i);
    if (oat_class.get() != NULL) {
    LinkCode(method, oat_class.get(), class_def_method_index);
        }
    class_def_method_index++;
      }
    ……
    }
    

    為了弄清這個方法,我們先得看看Class類利用了什么重要的成員:

    #!java
    ObjectArray<ArtMethod>* direct_methods_;
    // instance fields
    // specifies the number of reference fields.
    ObjectArray<ArtField>* ifields_;
    // For every interface a concrete class implements, we create an array of the concrete vtable_
    // methods for the methods in the interface.
    IfTable* iftable_;
    // Static fields
    ObjectArray<ArtField>* sfields_;
    // The superclass, or NULL if this is java.lang.Object, an interface or primitive type.
    // Virtual methods defined in this class; invoked through vtable.
    ObjectArray<ArtMethod>* virtual_methods_;
    // Virtual method table (vtable), for use by "invoke-virtual".  The vtable from the superclass is
    // copied in, and virtual methods from our class either replace those from the super or are
    // appended. For abstract classes, methods may be created in the vtable that aren't in
    // virtual_ methods_ for miranda methods.
    ObjectArray<ArtMethod>* vtable_;
    // Total size of the Class instance; used when allocating storage on gc heap.
    // See also object_size_.
    size_tclass_size_;
    

    這樣就比較清晰了。LoadClass首先讀取dex文件中的classdata,然后初始化一個迭代器來對classdata中的數據進行遍歷。接下來分部分進行:

    分配一個對象ObjectArray來表示靜態成員,并利用靜態成員的數量初始化,并將這個對象的地址賦值給Class的sfields_成員。

    同樣的完成Class的ifields_成員的初始化,用來表示私有數據成員

    接下來,遍歷靜態成員,對于每個成員分配一個Object對象,然后將地址放入之前分配的ObjectArray數組中,并將dex文件中的相關信息加載到Object對象中,從而完成了靜態成員信息的讀取。

    同理,完成了私有成員信息的讀取。

    像對于數據成員一樣,分配一個ObjectArray用于表示directmethod,并用于初始化direct_methods_成員。

    同理,初始化了virtual_methods_成員。

    遍歷directmethod成員,對于每一個directmethod生成一個ArtMethod對象,并在構造函數中通過LoadMethod完成dex文件中相應信息的讀取。再將ArtMethod對象放入之前的ObjectArray中,還需要利用LinkCode將實際的方法代碼起始地址用來初始化ArtMethod的entry_point_from_compiled_code_成員,最后更新每個ArtMethod的method_index_成員用于方法索引查找。

    同樣的過程完成了對于VirtualMethod的處理

    最終就完成了類的加載。

    下面需要再關注一下一個類實例化的過程。

    類的實例化是通過TLS(線程局部存儲)中的一個函數表中的pAllocObject來進行的。pAllocObject這個函數指針被指向了art_quick_alloc_object函數。這個函數是與硬件相關的,實際上它又調用了artAllocObjectFromCode函數,又調用了AllocObjectFromCode函數,在完成了一系列檢查判斷后調用了Class::AllocObject,這個方法很簡單,就是一句話:

    returnRuntime::Current()->GetHeap()->AllocObject(self, this, this->object_size_)
    

    其實是在堆上根據之前LoadClass時指定的類對象的大小分配了一塊內存,按照一個Object對象指針返回。

    可以以圖形來展示一下:

    p10

    看一下最后調用的這個函數:

    #!java
    mirror::Object* Heap::AllocObject(Thread* self, mirror::Class* c, size_tbyte_count) {
    ……
    obj = Allocate(self, alloc_space_, byte_count, &bytes_allocated);
     ……
    if (LIKELY(obj != NULL)) {
    obj->SetClass(c);
       ……
    returnobj;
      } else {
       ……
    }
    

    在這個函數中分配了內存空間之后,還調用了SetClass這個關鍵的函數,把Object對象中的klass_成員利用LoadClass的結果初始化了。

    這樣的話一個完整的類的實例化的內存結構就如圖所示了:

    p11

    0x04 編譯過程


    關于ART的編譯過程,主要是由dex2oat程序啟動的,所以可以從dex2oat入手,先畫出整個過程的順序圖。

    p12

    上圖是第一階段的流程,主要是由dex2oat調用編譯器的過程。

    p13

    第二階段主要是進入編譯器的處理流程,通過對dalvik指令進行一次編譯為MIR,然后二次編譯為LIR,最后編譯成ARM指令。

    下面擇要對關鍵代碼進行整理:

    #!java
    staticintdex2oat(intargc, char** argv){
    ……
    UniquePtr<constCompilerDriver>compiler(dex2oat->CreateOatFile(boot_image_option,
    host_prefix.get(),
    android_root,
    is_host,
    dex_files,
    oat_file.get(),
    bitcode_filename,
    image,
    image_classes,
    dump_stats,
    timings));
    ……
    }
    

    在這個函數的調用中,主要進行的多線程進行編譯

    #!java
    voidCompilerDriver::CompileAll(jobjectclass_loader,
    conststd::vector<constDexFile*>&dex_files,
    base::TimingLogger&timings)
    {
    ……
    Compile(class_loader, dex_files, *thread_pool.get(), timings);
    ……
    }   
    
    voidCompilerDriver::Compile(jobjectclass_loader,
    conststd::vector<constDexFile*>&dex_files,
    ThreadPool&thread_pool, base::TimingLogger&timings) {
    ……
    CompileDexFile(class_loader, *dex_file, thread_pool, timings);
    ……
    }
    

    一直到

    #!java
    voidCompilerDriver::CompileDexFile(jobjectclass_loader,
    constDexFile&dex_file,ThreadPool&thread_pool,
    base::TimingLogger&timings) {
    ……
    context.ForAll(0, dex_file.NumClassDefs(),
    CompilerDriver::CompileClass, thread_count_);
    ……
    }
    

    啟動了多線程,執行CompilerDriver::CompileClass函數進行真正的編譯過程。

    #!java
    voidCompilerDriver::CompileClass(constParallelCompilationManager* manager, size_tclass_def_index) {
    ……
    ClassDataItemIteratorit(dex_file, class_data);
    CompilerDriver* driver = manager->GetCompiler();
    int64_tprevious_direct_method_idx = -1;
    while (it.HasNextDirectMethod()) {
    uint32_tmethod_idx = it.GetMemberIndex();
    if (method_idx == previous_direct_method_idx) {
    it.Next();
    continue;
        }
    previous_direct_method_idx = method_idx;
    driver->CompileMethod(it.GetMethodCodeItem(),
        it.GetMemberAccessFlags(),
        it.GetMethodInvokeType(class_def),class_def_index,
        method_idx, jclass_loader, dex_file,
        dex_to_dex_compilation_level);
    it.Next();
      }
    int64_tprevious_virtual_method_idx = -1;
    while (it.HasNextVirtualMethod()) {
    uint32_tmethod_idx = it.GetMemberIndex();
    if (method_idx == previous_virtual_method_idx) {
    it.Next();
    continue;
        }
    previous_virtual_method_idx = method_idx;
    driver->CompileMethod(it.GetMethodCodeItem(),
        it.GetMemberAccessFlags(),
        it.GetMethodInvokeType(class_def), class_def_index,
        method_idx, jclass_loader, dex_file,
        dex_to_dex_compilation_level);
    it.Next();
      }
    

    主要過程就是通過讀取class中的數據,利用迭代器遍歷每個DirectMethod和VirtualMethod,然后分別對每個Method作為單元利用CompilerDriver::CompileMethod進行編譯。

    CompilerDriver::CompileMethod函數主要是調用了CompilerDriver::CompilerDriver* constcompiler_這個成員變量(函數指針)。

    這個變量是在CompilerDriver的構造函數中初始化的,根據不同的編譯器后端選擇不同的實現,不過基本上的流程都是一樣的,通過對Portable后端的分析,可以看到最后調用的是static CompiledMethod* CompileMethod函數。

    #!java
    staticCompiledMethod* CompileMethod(CompilerDriver&compiler,
    constCompilerBackendcompiler_backend,
    constDexFile::CodeItem* code_item,
    uint32_taccess_flags, InvokeTypeinvoke_type,
    uint16_tclass_def_idx, uint32_tmethod_idx,
    jobjectclass_loader, constDexFile&dex_file
    #ifdefined(ART_USE_PORTABLE_COMPILER)
     , llvm::LlvmCompilationUnit* llvm_compilation_unit
    #endif
    ) {
    ……
        cu.mir_graph.reset(newMIRGraph(&cu, &cu.arena));
        cu.mir_graph->InlineMethod(code_item, access_flags, invoke_type, class_def_idx, method_idx,class_loader, dex_file);
        cu.mir_graph->CodeLayout();
        cu.mir_graph->SSATransformation();
        cu.mir_graph->PropagateConstants();
        cu.mir_graph->MethodUseCount();
        cu.mir_graph->NullCheckElimination();
        cu.mir_graph->BasicBlockCombine();
        cu.mir_graph->BasicBlockOptimization();
        ……
        cu.cg.reset(ArmCodeGenerator(&cu, cu.mir_graph.get(), &cu.arena));
        ……
        cu.cg->Materialize();
        result = cu.cg->GetCompiledMethod();
        returnresult;
    }
    

    在這個過程中牽涉了幾種重要的數據結構:

    #!java
    classMIRGraph {
    ……
    BasicBlock* entry_block_;
    BasicBlock* exit_block_;
    BasicBlock* cur_block_;
    intnum_blocks_;
     ……
    }
    structBasicBlock {
      ……
    MIR* first_mir_insn;
    MIR* last_mir_insn;
    BasicBlock* fall_through;
    BasicBlock* taken;
    BasicBlock* i_dom;                // Immediate dominator.
     ……
    };
    structMIR {
    DecodedInstructiondalvikInsn;
    ……
    MIR* prev;
    MIR* next;
     ……
    };
    structDecodedInstruction {
    uint32_tvA;
    uint32_tvB;
    uint64_tvB_wide;        /* for k51l */
    uint32_tvC;
    uint32_targ[5];         /* vC/D/E/F/G in invoke or filled-new-array */
    Instruction::Codeopcode;    
    
    explicitDecodedInstruction(constInstruction* inst) {
    inst->Decode(vA, vB, vB_wide, vC, arg);
    opcode = inst->Opcode();
      }
    };
    

    這幾個數據結構的關系如圖所示:

    p14

    簡單地說,一個MIRGraph對應著一個編譯單元即一個方法,對一個方法進行控制流分析,劃分出BasicBlock,并在BasicBlock中的fall_through和taken域中指向下一個BasicBlock(適用于分支出口)。每一個BasicBlock包含若干dalvik指令,每一天dalvik指令被翻譯為若干MIR語句,這些MIR結構體之間形成雙向鏈表。每一個BasicBlock也指示了第一條和最后一條MIR語句。

    InlineMethod函數主要是解析一個方法,并劃分BasicBlock邊界,但是只是簡單地把BasicBlock連接成一個鏈表,利用fall_through指示。

    在CodeLayout函數中具體地再次遍歷BasicBlock鏈表,并根據每個BasicBlock出口的指令,再次調整taken域和fall_through域,形成完整的控制流圖結構。

    SSATransformation函數是對每條指令進行靜態單賦值變換。先對控制流圖進行深度優先遍歷,并計算出BasicBlock之間的支配關系,插入Phi函數,并對變量進行命名更新。

    其余的方法主要是一些代碼優化過程,例如常量傳播、消除空指針檢查;并在BasicBlock組合之后再進行BasicBlock的優化,消除冗余指令。

    這樣基本上就完成了MIR的生成過程,在某種程度上,可以認為MIR即為對dalvik指令進行SSA變換之后的指令形態。

    接著就調用cu.cg->Materialize()用來產生最終代碼。cu.cg在之前的代碼被指向了Mir2Lir對象,所以調用的是:

    #!java
    voidMir2Lir::Materialize() {
    CompilerInitializeRegAlloc();  // Needs to happen after SSA naming  
    
    /* Allocate Registers using simple local allocation scheme */
    SimpleRegAlloc();   
    
     ……
    /* Convert MIR to LIR, etc. */
    if (first_lir_insn_ == NULL) {
    MethodMIR2LIR();
      } 
    
    /* Method is not empty */
    if (first_lir_insn_) {
    // mark the targets of switch statement case labels
    ProcessSwitchTables();  
    
    /* Convert LIR into machine code. */
    AssembleLIR();  
    
      ……
      }
    }
    

    其中重要的兩個調用就是MethodMIR2LIR()和AssembleLIR()。

    #!java
    MethodMIR2LIR將MIR轉化為LIR,遍歷每個BasicBlock,對每個基本塊執行MethodBlockCodeGen,本質上最后是執行了CompileDalvikInstruction。    
    
    voidMir2Lir::CompileDalvikInstruction(MIR* mir, BasicBlock* bb, LIR* label_list) {
      ……
    Instruction::Codeopcode = mir->dalvikInsn.opcode;
    intopt_flags = mir->optimization_flags;
    uint32_tvB = mir->dalvikInsn.vB;
    uint32_tvC = mir->dalvikInsn.vC;    
    
    ……
    switch (opcode) {
    case XXX:
    GenXXXXXX(……)
    default:
    LOG(FATAL) <<"Unexpected opcode: "<<opcode;
      }
    }  
    

    也就是通過解析指令,然后根據opcode進行分支判斷,調用最終不同的指令生成函數。最后將LIR之間也形成一個雙向鏈表。

    AssembleLIR最終調用的是AssembleInstructions函數。程序中維護了一個編碼指令表ArmMir2Lir::EncodingMap,AssembleInstructions即是通過查找這個表來進行翻譯,將LIR轉化為了ARM指令,并將所翻譯的指令存儲到CodeBufferMir2Lir::code_buffer_之中。

    這樣就完成了一次編譯的完整流程。

    0x05 JNI分析


    ART環境中的JNI接口與Dalvik同樣符合JVM標準,但是其中的實現卻有所不同。以下通過三個過程來進行簡述。

    1、類加載初始化

    首先觀察一個native的java成員方法通過dex2oat編譯后的結果:

    java.lang.Stringcom.example.hellojni.HelloJni.stringFromJNI() (dex_method_idx=9)
        DEX CODE:
        CODE: 0xb6bfd1ac (offset=0x000011ac size=148)...
          0xb6bfd1ac: e92d4de0  stmdbsp!, {r5, r6, r7, r8, r10, r11, lr}
          0xb6bfd1b0: e24dd024  sub     sp, sp, #36
          0xb6bfd1b4: e58d0000  str     r0, [sp, #0]
          0xb6bfd1b8: e58d1044  str     r1, [sp, #68]
          0xb6bfd1bc: e3a0c001  mov    r12, r0, #1
          0xb6bfd1c0: e58dc004  str     r12, [sp, #4]
          0xb6bfd1c4: e599c074  ldr     r12, [r9, #116]  ;top_sirt_
          0xb6bfd1c8: e58dc008  str     r12, [sp, #8]
          0xb6bfd1cc: e28dc004  add    r12, sp, #4
          0xb6bfd1d0: e589c074  str     r12, [r9, #116]  ;top_sirt_
          0xb6bfd1d4: e59dc044  ldr     r12, [sp, #68]
          0xb6bfd1d8: e58dc00c  str     r12, [sp, #12]
          0xb6bfd1dc: e589d01c  strsp, [r9, #28]  ; 28
          0xb6bfd1e0: e3a0c000  mov    r12, r0, #0
          0xb6bfd1e4: e589c020  str     r12, [r9, #32]  ; 32
          0xb6bfd1e8: e1a00009  mov    r0, r9
          0xb6bfd1ec: e590c1b8  ldr  r12, [r0, #440] //qpoints->pJniMethodStart = JniMethodStart
          0xb6bfd1f0: e12fff3c  blx     r12
          0xb6bfd1f4: e58d0010  str     r0, [sp, #16]
          0xb6bfd1f8: e28d100c  add     r1, sp, #12
          0xb6bfd1fc: e5990024  ldr     r0, [r9, #36]  ;jni_env_
          0xb6bfd200: e59dc000  ldr     r12, [sp, #0]
          0xb6bfd204: e59cc048  ldr     r12, [r12, #72]
          0xb6bfd208: e12fff3c  blx     r12    // const void* ArtMethod::native_method_
          0xb6bfd20c: e59d1010  ldr     r1, [sp, #16]
          0xb6bfd210: e1a02009  mov    r2, r9
          0xb6bfd214: e592c1c8  ldr     r12, [r2, #456]
          0xb6bfd218: e12fff3c  blx r12//qpoints->pJniMethodEndWithReference= JniMethodEndWithReference
          0xb6bfd21c: e599c00c  ldr     r12, [r9, #12]  ; exception_
          0xb6bfd220: e35c0000  cmp     r12, #0
          0xb6bfd224: 1a000001  bne     +4 (0xb6bfd230)
          0xb6bfd228: e28dd03c  add     sp, sp, #60
          0xb6bfd22c: e8bd8000  ldmiasp!, {pc}
          0xb6bfd230: e1a0000c  mov     r0, r12
          0xb6bfd234: e599c260  ldr     r12, [r9, #608]  ;pDeliverException
          0xb6bfd238: e12fff3c  blx     r12
          0xb6bfd23c: e1200070  bkpt    #0
    

    可以看到,它沒有對應的dex code。

    用偽碼表示這個過程:

    JniMethodStart(Thread*);
    ArtMethod ::native_method_(…..);
    JniMethodEndWithReference(……);
    return;
    

    基本上就是這三個函數的調用。

    但是從ART的LoadClass的函數來分析,ArtMethod對象與真實執行的代碼鏈接的過程主要是通過LinkCode函數執行的。

    #!java
    staticvoidLinkCode(SirtRef<mirror::ArtMethod>&method, constOatFile::OatClass* oat_class,
    uint32_tmethod_index)
    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
    DCHECK(method->GetEntryPointFromCompiledCode() == NULL);
    constOatFile::OatMethodoat_method = oat_class->GetOatMethod(method_index);
    oat_method.LinkMethod(method.get());    
    
    Runtime* runtime = Runtime::Current();
    boolenter_interpreter = NeedsInterpreter(method.get(), method->GetEntryPointFromCompiledCode());
    if (enter_interpreter) {  method->SetEntryPointFromInterpreter(interpreter::artInterpreterToInterpreterBridge);
    } else{ method->SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge);
     }  
    
    if (method->IsAbstract()) { method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
    return;
      } 
    
    if (method->IsStatic() && !method->IsConstructor()) {
    method->SetEntryPointFromCompiledCode(GetResolutionTrampoline(runtime->GetClassLinker()));
      } elseif (enter_interpreter) {
    method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
      } 
    
    if (method->IsNative()) {
    method->UnregisterNative(Thread::Current());
      } 
    
    runtime->GetInstrumentation()->UpdateMethodsCode(method.get(),
    method->GetEntryPointFromCompiledCode());
    }
    

    可以看到,在LinkCode的開始就將通過oat_method.LinkMethod(method.get())將對象與代碼進行了鏈接,但是在后邊又針對幾種特殊情況做了一些處理,包括解釋執行入口和靜態方法等等。我們主要關注的是JNI方法,即

    if (method->IsNative()) {
    method->UnregisterNative(Thread::Current());
      }
    

    展開函數:

    #!java
    voidArtMethod::UnregisterNative(Thread* self) {
    CHECK(IsNative()) <<PrettyMethod(this);
    RegisterNative(self, GetJniDlsymLookupStub());
    }   
    
    extern"C"void* art_jni_dlsym_lookup_stub(JNIEnv*, jobject);
    staticinlinevoid* GetJniDlsymLookupStub() {
    returnreinterpret_cast<void*>(art_jni_dlsym_lookup_stub);
    }   
    
    voidArtMethod::RegisterNative(Thread* self, constvoid* native_method) {
    DCHECK(Thread::Current() == self);
    CHECK(IsNative()) <<PrettyMethod(this);
    CHECK(native_method != NULL) <<PrettyMethod(this);
    if (!self->GetJniEnv()->vm->work_around_app_jni_bugs) {
    SetNativeMethod(native_method);
      } else {
    SetNativeMethod(reinterpret_cast<void*>(art_work_around_app_jni_bugs));
    SetFieldPtr<constuint8_t*>(OFFSET_OF_OBJECT_MEMBER(ArtMethod, gc_map_),
    reinterpret_cast<constuint8_t*>(native_method), false);
      }
    }   
    
    voidArtMethod::SetNativeMethod(constvoid* native_method) {
    SetFieldPtr<constvoid*>(OFFSET_OF_OBJECT_MEMBER(ArtMethod, native_method_),
    native_method, false);
    }
    

    很清晰可以看到,在類加載的時候是把ArtMethod的native_method_成員設置為了art_jni_dlsym_lookup_stub函數,那么在執行JNI方法的時候就會執行art_jni_dlsym_lookup_stub函數。

    2、通過java調用JNI方法

    art_jni_dlsym_lookup_stub函數入手,這個函數使用匯編寫的,與具體的平臺相關。

    ENTRYart_jni_dlsym_lookup_stub
    push   {r0, r1, r2, r3, lr}           @ spillregs
        .save  {r0, r1, r2, r3, lr}
        .pad #20
        .cfi_adjust_cfa_offset20
    subsp, #12                        @ padstackpointertoalignframe
        .pad #12
        .cfi_adjust_cfa_offset12
    blxartFindNativeMethod
    movr12, r0                        @ saveresultinr12
    addsp, #12                        @ restorestackpointer
        .cfi_adjust_cfa_offset -12
    cbzr0, 1f                         @ ismethodcodenull?
    pop    {r0, r1, r2, r3, lr}           @ restoreregs
        .cfi_adjust_cfa_offset -20
    bxr12                            @ ifnon-null, tailcalltomethod's code
    1:
        .cfi_adjust_cfa_offset 20
    pop    {r0, r1, r2, r3, pc}           @ restore regs and return to caller to handle exception
        .cfi_adjust_cfa_offset -20
    END art_jni_dlsym_lookup_stub
    

    主要的過程就是先調用artFindNativeMethod得到真正的native code的地址,然后在跳轉到相應地址去執行,即對應了

    blxartFindNativeMethod
    bxr12                            @ ifnon-null, tailcalltomethod's code
    

    兩條指令。

    #!java
    extern"C"void* artFindNativeMethod() {
    Thread* self = Thread::Current();
    Locks::mutator_lock_->AssertNotHeld(self);  
    ScopedObjectAccesssoa(self);    
    
    mirror::ArtMethod* method = self->GetCurrentMethod(NULL);
    DCHECK(method != NULL); 
    
    void* native_code = soa.Vm()->FindCodeForNativeMethod(method);
    if (native_code == NULL) {
    DCHECK(self->IsExceptionPending());
    returnNULL;
      } else {
    method->RegisterNative(self, native_code);
    returnnative_code;
      }
    }   
    

    主要的過程也就是查找到相應方法的native code,然后再次設置ArtMethod的native_method_成員,這樣以后再執行的時候就直接跳到了native code執行了。

    3、Native方法中調用java方法

    這個主要是通過JNIEnv來間接調用的。JNIEnv中維持了許多JNI API可以被native code來使用。C和C++的實現形式略有不同,C++是對C的事先進行了一個簡單的包裝,具體可以參見jni.h。這里為了便于敘述以C為例。

    #!java
    typedefconststructJNINativeInterface* JNIEnv;
    structJNINativeInterface {
    void*       reserved0;
    void*       reserved1;
    void*       reserved2;
    void*       reserved3;
    jint        (*GetVersion)(JNIEnv *);
    jclass      (*DefineClass)(JNIEnv*, constchar*, jobject, constjbyte*,  jsize);
    jclass      (*FindClass)(JNIEnv*, constchar*);
    …………
    …………
    jobject     (*NewDirectByteBuffer)(JNIEnv*, void*, jlong);
    void*       (*GetDirectBufferAddress)(JNIEnv*, jobject);
    jlong       (*GetDirectBufferCapacity)(JNIEnv*, jobject);
    jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
    };
    

    這些API以函數指針的形式存在,并在libart.so中實現,在整個art的初始化的過程中進行了對應。

    在libart.so中的對應:

    #!java
    constJNINativeInterfacegJniNativeInterface = {
    NULL,  // reserved0.
    NULL,  // reserved1.
    NULL,  // reserved2.
    NULL,  // reserved3.
    JNI::GetVersion,
    JNI::DefineClass,
    JNI::FindClass,
    …………
    …………
    JNI::NewDirectByteBuffer,
    JNI::GetDirectBufferAddress,
    JNI::GetDirectBufferCapacity,
    JNI::GetObjectRefType,
    };
    

    下面以一個常見的native code調用java的過程進行下分析:

    (*pEnv)->FindClass(……);
    getMethodID(……);
    (*pEnv)->CallVoidMethod(……);
    

    即查找類,得到相應的方法的ID,然后通過此ID去調用。

    #!java
    staticjclassFindClass(JNIEnv* env, constchar* name) {
    CHECK_NON_NULL_ARGUMENT(FindClass, name);
    Runtime* runtime = Runtime::Current();
    ClassLinker* class_linker = runtime->GetClassLinker();
    std::stringdescriptor(NormalizeJniClassDescriptor(name));
    ScopedObjectAccesssoa(env);
    Class* c = NULL;
    if (runtime->IsStarted()) {
    ClassLoader* cl = GetClassLoader(soa);
          c = class_linker->FindClass(descriptor.c_str(), cl);
        } else {
          c = class_linker->FindSystemClass(descriptor.c_str());
        }
    returnsoa.AddLocalReference<jclass>(c);
      } 
    

    可以看到JNI中的FindClass實際調用的是ClassLinker::FindClass,這與ART的類加載過程一致。

    #!java
    staticvoidCallVoidMethod(JNIEnv* env, jobjectobj, jmethodIDmid, ...) {
    va_listap;
    va_start(ap, mid);
    CHECK_NON_NULL_ARGUMENT(CallVoidMethod, obj);
    CHECK_NON_NULL_ARGUMENT(CallVoidMethod, mid);
    ScopedObjectAccesssoa(env);
    InvokeVirtualOrInterfaceWithVarArgs(soa, obj, mid, ap);
    va_end(ap);
      }
    

    最后調用的是ArtMethod::Invoke()。

    可以說如出一轍,即JNI的這些API其實還是做了一遍ART的類加載和初始化及調用的過程。

    0x06 總結與補充


    1. oat文件作為一個靜態庫的形式被加載到zygote進程的空間中,并由libart.so負責虛擬機的功能,完成對oat文件的解析,方法的查找和調用,并負責垃圾回收。
    2. runtime可以實現在部分未被編譯的方法和已被編譯的方法之前的交互調用,為此runtime提供了諸如artInterpreterToInterpreterBridge、artInterpreterToCompiledCodeBridge之類的函數進行銜接。
    3. 所有的Java方法在編譯為arm指令后都符合一定的標準。由于是在runtime中運行的,所有的R0寄存器代表著一個隱含的參數,指令當前的ArtMethod對象,R1-R3傳遞前幾個參數(包括this),多余的參數依靠堆棧傳遞。
    4. 系統的啟動類(在環境變量BOOTCLASSPATH中指定)被翻譯為boot.oat,boot.art包含了其加載后的類對象,啟動時以直接被載入進程空間中。
    5. 同一個dex文件中的方法,載入的時候會被直接解析到ArtMethod對象的dex_cache_resolved_methods_成員中,直接通過R0寄存器尋址。而系統的API主要是通過找到代表包含API的對象Object實例中的Class域,然后在其中的函數表中查找解決的;Class實例的初始化,是在載入每個oat文件解析類信息時建立的。
    6. 一些關鍵的系統調用,如分配對象等,是有libart.so來提供的,并且與平臺有相關性,存放在每個Thread對象的quick_entrypoints_域中。
    7. dex2oat在兩個時間被執行,一是apk安裝的時候,二是在調用DexClassLoader動態載入dex文件的時候。
    8. 具體的說編譯的目標指令為Thumb-2指令集,支持16位指令和32位指令的混合執行。
    9. 在編譯boot.art和boot.oat文件時,不需要其他的支持,但是在編譯其他的dex文件時需要在虛擬機環境中載入上述文件。編譯執行的過程也需要虛擬機環境的支持,只不過是用于編譯而非執行,這樣可以保證編譯的目標文件是在虛擬機環境中的一個完整的映像而不會出現尋址錯誤等。
    10. 整個編譯過程基本上是依靠dex2oat來加載CompilerDriver,然后逐個方法來進行編譯。將每個方法劃分BasicBlock,繪制MIRGraph(控制流圖),逐個翻譯為以dalvikbtecode的SSA形式為基礎的MIR,然后將MIR解析為LIR,最后翻譯為Thumb-2指令,最后統一寫入一個ELF文件即oat文件。

    97av