Zyq8709 2015/12/08 10:22

Because of the need, I studied the relevant source code of ART and made some records and summaries. Now I have reorganized it to discuss and communicate with you.

0 x00 overview


ART is a new generation of runtime on the Android platform, used to replace Dalvik. It mainly adopts AOT method, which compilers Dalvikbytecode into ARM local instruction at one time when APK is installed (but this AOT is fundamentally different from C language and still needs the support of virtual machine environment), so it can be executed directly without any explanation or compilation. It saves running time and improves efficiency, but to some extent it takes longer time to install and takes up more space.

From the perspective of Android source code, ART related content mainly includes Compiler and related program Dex2OAT, Runtime, Java debugging support and OATdump tool for parsing OAT files.

Here is the ART source directory structure:

There are a few key directories in the middle,

The first is Dex2OAT, which is responsible for converting dex files into OAT files. The specific translation work needs to be completed by compiler, and finally compiled into Dex2OAT.

Libart. so is used to replace libdvm.so. Dalvik is a shell that still calls the ART Runtime.

Oatdump is also an important tool, compiled as OATdump program, mainly used to analyze OAT files and format them to show the structure of the files.

Jdwpspy is the debug support part of Java, the implementation of the JDWP server.

0 x01 oat file


The formats of OAT files can be started from dex2OAT and OATdump directories. Simply put, oAT files are nested in the format of an ELF file. There are three important symbols, OATData, OATexec, oatLastWord, in the dynamic symbol table of ELF file, indicating oAT data area, native code and end position respectively. These relationship structures are clearly explained in the figure. A simple understanding is that in OATData, the original DEX file content is stored, the offset address to the DEX file content and the offset to the corresponding OAT class are also kept in the head, and the offset address of the corresponding native code is also stored in the OAT class. In this way, the correspondence between DexBytecode and native code is indirectly completed.

/art/ dex2Oat/dex2Oat.cc static int dex2OAT (intarGC, Char ** argv) and /art/oatdump/oatdump.cc static intoatdump(intarGC, char** argv) functions, you can quickly understand the oAT file format and parse. The Write function in /art/ Compiler /elf_writer_quick.cc is a good reference.

0x02 Runtime startup


The startup process of ART runtime is very early, started by Zygote, which is exactly the same as that of Dalvik, ensuring the seamless connection from Dalvik to ART.

The whole process from start app_processs (/ frameworks/base/CMDS app_process/app_main. CPP), created an object AppRuntime runtime, this is a singleton, the whole system is running only one. As Zygote forks, it just keeps copying Pointers to the object for each child process. The runtime.start method is then executed. This method starts the vm by calling startVm. Is executed by the JNI_CreateJavaVM method, /art/runtime/jni_internal. Cc extern “C” jintJNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) Then call startReg to register some native methods. At the end of the day it’s important to find the Main method of the Java code to execute, and then execute into the world of managed code, which is where we’re interested.

The last call is the CallStaticVoidMethod. Let’s see how it works:

Look again for InvokeWithVarArgs:

Jump to InvokeWithArgArray:

You can see a key class:

ArtMethod, one of its member methods is responsible for calling native code in oAT files:

And here’s the final entry:

The blxIP instruction on line 283 is the final entry into native code. It can be roughly concluded that the required class and method can be obtained by searching relevant OAT files, and the corresponding native code location is put into the ArtMethod structure. Finally, the Invoke member is used to complete the invocation. The next step is to focus on how native Code calls other Java methods through the runtime to locate and jump.

Note that the comments describe the ABI under ART, similar to the standard ARM calling convention, except that R0 stores the ArtMethod object address of the caller’s method, while r0-R3 contains the parameters, including this. The surplus is stored on the stack, starting at SP+16. The return value is also passed through R0/R1. R9 points to the current thread object pointer allocated by the runtime.

0 x03 class loading


Class loading is mostly done by the ClassLinker class. Let’s look at the sequence of the process:

The sequence diagram describes the logic of the call using static member initialization and virtual function initialization as examples. The following is a detailed description.

Start with 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 (ppair. Second!= NULL) {returnDefineClass(Descriptor, NULL, * ppair. First, * ppair.Copy the code

Omit minor code and use LookupClass first to find if the desired class is loaded, which is not true for this scenario. Then determine if it is a class of array type and skip the branch as well and go into DefineClass, which we are most interested in.

#! 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(); }Copy the code

Looking at the important parts, this method basically does two things: load classes from the dex file and insert loaded classes into a table for LookupClass lookup.

We focus on the first function, first of all, some built-in class judgment, for the custom class is to manually allocate space, and then find the relevant DEX file, and finally load.

Then look at the LoadClass method:

#! 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++; }... }Copy the code

To understand this approach, we need to look at what important members the Class Class makes use of:

#! 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_;Copy the code

So that’s a little bit clearer. LoadClass first reads classData from the dex file and initializes an iterator to iterate over the data in classData. Proceed in parts:

Assign an object ObjectArray to represent static members, initialize with the number of static members, and assign the address of this object to the sfields_ member of Class.

Also completes the initialization of the Class’s ifields_ member to represent private data members

Next, we iterate over the static members, assign an Object to each member, then put the address into the previously allocated ObjectArray array, and load the relevant information from the DEX file into the Object, thus completing the reading of the static member information.

Similarly, private member information is read.

As with the data members, an ObjectArray is assigned to represent directMethod and used to initialize the Direct_methods_ member.

Similarly, the Virtual_methods_ member is initialized.

Iterate through the DirectMethod members, generate an ArtMethod object for each DirectMethod, and read the corresponding information in the dex file through LoadMethod in the constructor. To put the ArtMethod object into the previous ObjectArray, you also need to initialize the entry_poinT_FROM_compiled_code_ member of ArtMethod with the actual method code starting address using LinkCode. Finally, the method_index_ member of each ArtMethod is updated for method index lookups.

The same process completes the processing of VirtualMethod

The class is finally loaded.

Now we need to focus on a class instantiation process.

Classes are instantiated through pAllocObject in a table of functions in TLS (Thread-local storage). The pAllocObject function pointer is pointed to the art_quick_alloc_object function. This is a hardware-specific function, and it actually calls artAllocObjectFromCode, AllocObjectFromCode, Class::AllocObject, after doing a bunch of checks, which is very simple:

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

A block of memory is allocated on the heap according to the size of the Object specified in the LoadClass, which is returned as an Object pointer.

It can be shown graphically:

Take a look at the function called at the end:

#! 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 {... }Copy the code

After allocating memory in this function, the key SetClass function is called to initialize the klass_ member of the Object with the result of LoadClass.

The memory structure of a complete class instantiation looks like this:

0x04 Compilation process


The compilation process of ART is mainly started by the Dex2OAT program, so we can start with Dex2OAT and draw the sequence diagram of the whole process first.

The figure above shows the first stage of the process, which is mainly the process of invoking the compiler by Dex2OAT.

The second stage is mainly to enter the processing flow of the compiler, through the compilation of dalvik instruction to MIR, then the second compilation to LIR, and finally the compilation to ARM instruction.

The key codes are sorted as follows:

#! 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)); ... }Copy the code

In the call of this function, the main multithreading is compiled

#! 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); ... }Copy the code

All the way to the

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

Launched a multi-threaded, perform CompilerDriver: : CompileClass function real compilation process.

#! 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(); }Copy the code

Main process is by reading the data in the class, the use of iterators iterate through each DirectMethod and VirtualMethod, then respectively for each Method utilized as CompilerDriver: unit: CompileMethod compiled.

CompilerDriver: : CompileMethod function mainly is to call the CompilerDriver: : CompilerDriver * constcompiler_ this member variable (a function pointer).

This variable is initialized in the constructor of CompilerDriver, and the implementation is selected according to the compiler back end, but basically the flow is the same. You can see that the static CompiledMethod* CompileMethod is called last.

#! 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; }Copy the code

Several important data structures are involved in this process:

#! 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(); }};Copy the code

The relationship between these data structures is shown below:

In simple terms, a MIRGraph corresponds to a compilation unit (a method), performs a control flow analysis on a method, divides the BasicBlock, and points to the next BasicBlock in the Fall_THROUGH and taken fields of the BasicBlock (for branch exits). Each BasicBlock contains several Dalvik instructions, and dalvik instructions are translated into several MIR statements every day. These MIR structures form a bidirectional linked list. Each BasicBlock also indicates the first and last MIR statements.

The InlineMethod function primarily parses a method and demarcates BasicBlock boundaries, but simply concatenates basicBlocks into a linked list, indicated by fall_through.

The BasicBlock list is specifically iterated again in the CodeLayout function, and the taken and FALL_through fields are adjusted again according to the instructions of each BasicBlock exit to form a complete control flow diagram structure.

The SSATransformation function performs a static single assignment transformation on each instruction. Firstly, the depth-first traversal of the control flow diagram was carried out, and the dominant relationship between basicblocks was calculated. Phi function was inserted, and the variables were named and updated.

The rest of the method is mainly some code optimization process, such as constant propagation, eliminating null pointer checking; BasicBlock is optimized after the combination of BasicBlock to eliminate redundant instructions.

In this way, the generation process of MIR is basically completed. To some extent, MIR can be considered as the instruction form after SSA transformation of Dalvik instruction.

The call to cu.cg->Materialize() is then used to produce the final code. Cu.cg was referred to the Mir2Lir object in the previous code, so the call is:

#! 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(); ... }}Copy the code

Two important calls are MethodMIR2LIR() and AssembleLIR().

#! Java MethodMIR2LIR converts MIR to LIR, iterates over each BasicBlock, executes MethodBlockCodeGen on each base block, and essentially ends up executing 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; }}Copy the code

That is, by parsing the instruction, and then branch according to OpCode, call the final different instruction generation function. Finally, a bidirectional linked list is formed between LIRs.

The AssembleLIR finally calls the AssembleInstructions function. A coding instruction table ArmMir2Lir::EncodingMap is maintained in the program. AssembleInstructions are translated by looking up this table and converting LIR into ARM instructions. And store the translated instructions in CodeBufferMir2Lir::code_buffer_.

This completes the complete flow of a compilation.

0 x05 JNI analysis


The JNI interface in the ART environment meets the same JVM standards as Dalvik, but the implementation is different. The following three processes are briefly described.

Class load initialization

First observe a native Java member method compiled by 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 #0Copy the code

As you can see, it doesn’t have a dex code.

This process is represented by pseudo-code:

JniMethodStart(Thread*); ArtMethod: : native_method_ (... ..) ; JniMethodEndWithReference (...) ; return;Copy the code

It’s basically a call to these three functions.

However, from the analysis of the LoadClass function of ART, the process of linking the ArtMethod object to the actual executing code is mainly carried out through the LinkCode function.

#! 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()); }Copy the code

LinkMethod(method.get()) will be used to link the object with the code at the beginning of LinkCode, but some processing will be done for several special cases later, including explaining the execution entry and static method, etc. Our main concern is the JNI method, i.e

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

Expansion function:

#! 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); }Copy the code

It is clear that the native_method_ member of ArtMethod is set to the art_jni_DLsym_lookup_stub function when the JNI method is executed.

2. Call JNI methods from Java

Start with the art_jni_DLsym_lookup_stub function, which is written in assembly and platform-specific.

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
Copy the code

The main process is to call artFindNativeMethod first to get the real native code address, and then jump to the corresponding address to execute, that is, corresponding

blxartFindNativeMethod
bxr12                            @ ifnon-null, tailcalltomethod's code
Copy the code

Two instructions.

#! 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; }}Copy the code

The main process is to find the native code of the corresponding method, and then set the native_method_ member of ArtMethod again, so that the future execution will directly jump to the native code execution.

Call Java methods in Native methods

This is mainly invoked indirectly via JNIEnv. JNIEnv maintains a number of JNI apis that can be used by native code. C and C++ are implemented slightly differently. C++ is a simple wrapper around C, as shown in jni.h. So let’s take C for the sake of the statement.

#! 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); };Copy the code

These apis exist in the form of function Pointers and are implemented in libart.so, corresponding to the entire art initialization process.

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, };Copy the code

The following is an analysis of a common process of invoking Java in Native code:

(* pEnv) - > FindClass (...) ; GetMethodID (...) ; (* pEnv) - > CallVoidMethod (...) ;Copy the code

That is, find the class, get the ID of the corresponding method, and then call it with that 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); }Copy the code

You can see that FindClass in JNI actually calls ClassLinker::FindClass, which is consistent with the ART class loading process.

#! 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); }Copy the code

The last call is ArtMethod::Invoke().

It can be said that the same, that is, these APIS of JNI actually do the ART class loading and initialization and call process.

0x06 Summary and Supplement


  1. The OAT file is loaded into the zygote process space as a static library, and libart.so takes care of the virtual machine functions, parsing oAT files, finding and calling methods, and garbage collection.
  2. The Runtime can implement interactive calls before partially uncompiled methods and compiled methods, For the runtime provides the function such as artInterpreterToInterpreterBridge, artInterpreterToCompiledCodeBridge cohesion.
  3. All Java methods meet certain standards when compiled as ARM instructions. Since it is run in Runtime, all R0 registers represent an implicit argument to the current ArtMethod object, r1-R3 passes the first few arguments (including this), and the extra arguments are passed by the stack.
  4. The boot class of the system (specified in the environment variable BOOTCLASSPATH) is translated as boot.oat, and boot.art contains its loaded class object, which is loaded directly into process space at boot time.
  5. Methods in the same dex file are resolved directly into the dex_cache_resolved_methods_ member of the ArtMethod object when loaded, addressed directly through the R0 register. The API of the system is mainly solved by finding the Class domain of the Object instance which represents the API, and then searching in the function table. The initialization of a Class instance is created when each OAT file is loaded to parse Class information.
  6. Some key system calls, such as allocating objects, are provided by libart.so and are platform-dependent and stored in the Quick_entrypoints_ field of each Thread object.
  7. Dex2oat is executed at two times, one is when APK is installed and the other is when DexClassLoader is called to dynamically load the DEX file.
  8. Specifically, the target instruction for compilation is thumb-2 instruction set, which supports mixed execution of 16-bit instruction and 32-bit instruction.
  9. No additional support is required to compile boot.art and boot.oat files, but the above files need to be loaded in the virtual machine environment to compile other dex files. The process of compiling execution also needs to be supported by the virtual machine environment, but for compilation rather than execution, so that the compiled object file is a complete image in the virtual machine environment without addressing errors, etc.
  10. The whole compilation process basically relies on Dex2OAT to load the CompilerDriver and then compile it method by method. Each method was divided into basicblocks, MIRGraph (control flow graph) was drawn and translated into MIR based on SSA form of Dalvikbtecode one by one, then MIR was resolved into LIR, finally translated into thumb-2 instructions, and finally written into an ELF file, namely OAT file.