Effective immediately
In the middle of App startup, all classes that need to be changed have been loaded. It is impossible to uninstall a loaded class in Android system. Tencent Tinker’s solution is to let the ClassLoader load new classes. If the App is not restarted, the original class is still in the VIRTUAL machine, so the new class cannot be loaded. Therefore, it is necessary to load the new class in the repair patch first after cold startup, so as to achieve the purpose of hot repair.
The method adopted by AndFix is to directly replace the original method in the native layer of the loaded class, which is modified on the basis of the original class.
Underlying substitution principle
Each Java method corresponds to an ArtMethod in the Art VM. ArtMethod records all information about the method, including its owning class, access permission, and code execution address.
Using env->FromReflectedMethod, you can get the real starting address of the ArtMethod corresponding to the Method from the Method object, and then convert it into the ArtMethod pointer, and modify and replace its member attributes through the operation of the pointer.
Why is this hot repair possible after this substitution?
You need to understand the principle of virtual machine method call
Principle of virtual machine method call
Android version 5.1.1 ArtMethod structure of the path is: https://www.androidos.net.cn/android/5.1.0_r3/xref/art/runtime/mirror/art_method.h. The most important of these are entry_point_from_interpreter_ and entry_point_from_quick_compiled_code_, which, as their names suggest, are the entry points for method execution.
Java code is compiled into Dex code on Android, and Dex code can be executed in explain mode or AOT machine code mode on Art VMS.
- Explanation mode: Take out Dex Code and explain it one by one. If the method’s caller is running in explain mode, it is retrieved when the method is called
entry_point_from_interpreter_
Then jump to execute. - AOT mode: the system precompiles the machine code corresponding to Dex code and executes the machine code directly during runtime, without the need to execute Dex code one by one. If the caller of a method executes in AOT machine code, the method is called to jump to
entry_point_from_quick_compiled_code_
Is executed in.
Is that enough to replace the method’s execution entry?
Of course not. Whether in explain mode or AOT machine code mode, other member fields in ArtMethod will need to be called at run time
Source tracing validation
Class loading process
- FindClass (/art/runtime/jni_internal.cc)
- FindClass (/art/runtime/class_linker.cc)
- DefineClass (/art/runtime/class_linker.cc)
- LoadClass (/art/runtime/class_linker.cc)
- LoadClassMembers (/art/runtime/class_linker.cc)
- LinkCode (/art/runtime/class_linker.cc)
- LinkMethod (/art/runtime/oat_file.cc)
- NeedsInterpreter (/art/runtime/class_linker.cc)
- UnregisterNative(/art/runtime/mirror/art_method.cc)
- UpdateMethodsCode(/art/runtime/instrumentation.cc)
- UpdateEntrypoints(/art/runtime/instrumentation.cc)
Static member functions of the JNI classFindClass
static jclass FindClass(JNIEnv* env, const char* name) {
CHECK_NON_NULL_ARGUMENT(name);
Runtime* runtime = Runtime::Current();
ClassLinker* class_linker = runtime->GetClassLinker();
std::string descriptor(NormalizeJniClassDescriptor(name));
ScopedObjectAccess soa(env);
mirror::Class* c = nullptr;
if (runtime->IsStarted()) {
StackHandleScope<1> hs(soa.Self());
Handle<mirror::ClassLoader> class_loader(hs.NewHandle(GetClassLoader(soa)));
c = class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader);
} else {
c = class_linker->FindSystemClass(soa.Self(), descriptor.c_str());
}
return soa.AddLocalReference<jclass>(c);
}
Copy the code
In the ART VM process, there is a Runtime singleton to describe the ART Runtime. The above Runtime singleton can be obtained by calling Current, a static member of the Runtime class. Once you have this singleton, you can call its member function GetClassLinker to get a ClassLinker object.
The ClassLinker object is created during the creation of the ART virtual machine to load classes and link class methods
First determine if the ART runtime is up. If already started, the ClassLoader associated with the current thread is obtained by calling the function GetClassLoader, and with this parameter, the member function FindClass of the previously obtained ClassLinker object is called to load the class specified by the parameter name.
If the ART runtime is not already started, only system classes can be loaded at this time. This is done through the member function FindSystemClass of the ClassLinker object obtained earlier.
Member function of the ClassLinker classFindClass
mirror::Class* ClassLinker::FindClass(Thread* self, const char* descriptor, Handle<mirror::ClassLoader> class_loader) { DCHECK_NE(*descriptor, '\0') << "descriptor is empty string"; DCHECK(self ! = nullptr); self->AssertNoPendingException(); if (descriptor[1] == '\0') { // only the descriptors of primitive types should be 1 character long, also avoid class lookup // for primitive classes that aren't backed by dex files. return FindPrimitiveClass(descriptor[0]); } const size_t hash = ComputeModifiedUtf8Hash(descriptor); // Find the class in the loaded classes table. mirror::Class* klass = LookupClass(descriptor, hash, class_loader.Get()); if (klass ! = nullptr) { return EnsureResolved(self, descriptor, klass); } // Class is not yet loaded. if (descriptor[0] == '[') { return CreateArrayClass(self, descriptor, hash, class_loader); } else if (class_loader.Get() == nullptr) { // The boot class loader, search the boot class path. ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_); if (pair.second ! = nullptr) { return DefineClass(self, descriptor, hash, NullHandle<mirror::ClassLoader>(), *pair.first, *pair.second); } else { // The boot class loader is searched ahead of the application class loader, failures are // expected and will be wrapped in a ClassNotFoundException. Use the pre-allocated error to // trigger the chaining with a proper stack trace. mirror::Throwable* pre_allocated = Runtime::Current()->GetPreAllocatedNoClassDefFoundError(); self->SetException(ThrowLocation(), pre_allocated); return nullptr; } } else if (Runtime::Current()->UseCompileTimeClassPath()) { // First try with the bootstrap class loader. if (class_loader.Get() != nullptr) { klass = LookupClass(descriptor, hash, nullptr); if (klass ! = nullptr) { return EnsureResolved(self, descriptor, klass); } } // If the lookup failed search the boot class path. We don't perform a recursive call to avoid // a NoClassDefFoundError being allocated. ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_); if (pair.second ! = nullptr) { return DefineClass(self, descriptor, hash, NullHandle<mirror::ClassLoader>(), *pair.first, *pair.second); } // Next try the compile time class path. const std::vector<const DexFile*>* class_path; { ScopedObjectAccessUnchecked soa(self); ScopedLocalRef<jobject> jclass_loader(soa.Env(), soa.AddLocalReference<jobject>(class_loader.Get())); class_path = &Runtime::Current()->GetCompileTimeClassPath(jclass_loader.get()); } pair = FindInClassPath(descriptor, hash, *class_path); if (pair.second ! = nullptr) { return DefineClass(self, descriptor, hash, class_loader, *pair.first, *pair.second); } } else { ScopedObjectAccessUnchecked soa(self); mirror::Class* klass = FindClassInPathClassLoader(soa, self, descriptor, hash, class_loader); if (klass ! = nullptr) { return klass; } ScopedLocalRef<jobject> class_loader_object(soa.Env(), soa.AddLocalReference<jobject>(class_loader.Get())); std::string class_name_string(DescriptorToDot(descriptor)); ScopedLocalRef<jobject> result(soa.Env(), nullptr); { ScopedThreadStateChange tsc(self, kNative); ScopedLocalRef<jobject> class_name_object(soa.Env(), soa.Env()->NewStringUTF(class_name_string.c_str())); if (class_name_object.get() == nullptr) { DCHECK(self->IsExceptionPending()); // OOME. return nullptr; } CHECK(class_loader_object.get() ! = nullptr); result.reset(soa.Env()->CallObjectMethod(class_loader_object.get(), WellKnownClasses::java_lang_ClassLoader_loadClass, class_name_object.get())); } if (self->IsExceptionPending()) { // If the ClassLoader threw, pass that exception up. return nullptr; } else if (result.get() == nullptr) { // broken loader - throw NPE to be compatible with Dalvik ThrowNullPointerException(nullptr, StringPrintf("ClassLoader.loadClass returned null for %s", class_name_string.c_str()).c_str()); return nullptr; } else { // success, return mirror::Class* return soa.Decode<mirror::Class*>(result.get()); } } ThrowNoClassDefFoundError("Class %s not found", PrintableString(descriptor).c_str()); return nullptr; }Copy the code
The parameter descriptor points to the signature of the class to be loaded, and the parameter class_loader points to a classloader.
The first is to call another member function, LookupClass, to check whether the class specified by the parameter descriptor has already been loaded. If so, LookupClass, a member function of the ClassLinker Class, returns a corresponding Class object, which is then returned to the caller to indicate that the load is complete.
If the class descriptor has not been loaded, it depends on the value of the class_loader parameter. If the value of the class_loader argument is equal to NULL, you need to call FindInClassPath to find the corresponding class in the system startup classpath. Once found, you get the DEX file containing the target class, so you then call DefineClass, another member of the ClassLinker class, to load the class specified by the parameter Descriptor from the DEX file.
Once you know which DEX file the class definition specified by the parameter Descriptor is in, you can load it from it via DefineClass, another member function of the ClassLinker class.
Member function of the ClassLinker classDefineClass
mirror::Class* ClassLinker::DefineClass(Thread* self, const char* descriptor, size_t hash, Handle<mirror::ClassLoader> class_loader, const DexFile& dex_file, const DexFile::ClassDef& dex_class_def) { StackHandleScope<3> hs(self); auto klass = hs.NewHandle<mirror::Class>(nullptr); // Load the class from the dex file. if (UNLIKELY(! init_done_)) { // finish up init of hand crafted class_roots_ if (strcmp(descriptor, "Ljava/lang/Object;" ) == 0) { klass.Assign(GetClassRoot(kJavaLangObject)); } else if (strcmp(descriptor, "Ljava/lang/Class;" ) == 0) { klass.Assign(GetClassRoot(kJavaLangClass)); } else if (strcmp(descriptor, "Ljava/lang/String;" ) == 0) { klass.Assign(GetClassRoot(kJavaLangString)); } else if (strcmp(descriptor, "Ljava/lang/ref/Reference;" ) == 0) { klass.Assign(GetClassRoot(kJavaLangRefReference)); } else if (strcmp(descriptor, "Ljava/lang/DexCache;" ) == 0) { klass.Assign(GetClassRoot(kJavaLangDexCache)); } else if (strcmp(descriptor, "Ljava/lang/reflect/ArtField;" ) == 0) { klass.Assign(GetClassRoot(kJavaLangReflectArtField)); } else if (strcmp(descriptor, "Ljava/lang/reflect/ArtMethod;" ) == 0) { klass.Assign(GetClassRoot(kJavaLangReflectArtMethod)); } } if (klass.Get() == nullptr) { // Allocate a class with the status of not ready. // Interface object should get the right size here. Regular class will // figure out the right size later and be replaced with one of the right // size when the class becomes resolved. klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def))); } if (UNLIKELY(klass.Get() == nullptr)) { CHECK(self->IsExceptionPending()); // Expect an OOME. return nullptr; } klass->SetDexCache(FindDexCache(dex_file)); LoadClass(dex_file, dex_class_def, klass, class_loader.Get()); ObjectLock<mirror::Class> lock(self, klass); if (self->IsExceptionPending()) { // An exception occured during load, set status to erroneous while holding klass' lock in case // notification is necessary. if (! klass->IsErroneous()) { klass->SetStatus(mirror::Class::kStatusError, self); } return nullptr; } klass->SetClinitThreadId(self->GetTid()); // Add the newly loaded class to the loaded classes table. mirror::Class* existing = InsertClass(descriptor, klass.Get(), hash); if (existing ! = nullptr) { // We failed to insert because we raced with another thread. Calling EnsureResolved may cause // this thread to block. return EnsureResolved(self, descriptor, existing); } // Finish loading (if necessary) by finding parents CHECK(! klass->IsLoaded()); if (! LoadSuperAndInterfaces(klass, dex_file)) { // Loading failed. if (! klass->IsErroneous()) { klass->SetStatus(mirror::Class::kStatusError, self); } return nullptr; } CHECK(klass->IsLoaded()); // Link the class (if necessary) CHECK(! klass->IsResolved()); // TODO: Use fast jobjects? auto interfaces = hs.NewHandle<mirror::ObjectArray<mirror::Class>>(nullptr); mirror::Class* new_class = nullptr; if (! LinkClass(self, descriptor, klass, interfaces, &new_class)) { // Linking failed. if (! klass->IsErroneous()) { klass->SetStatus(mirror::Class::kStatusError, self); } return nullptr; } self->AssertNoPendingException(); CHECK(new_class ! = nullptr) << descriptor; CHECK(new_class->IsResolved()) << descriptor; Handle<mirror::Class> new_class_h(hs.NewHandle(new_class)); /* * We send CLASS_PREPARE events to the debugger from here. The * definition of "preparation" is creating the static fields for a * class and initializing them to the standard default values, but not * executing any code (that comes later, during "initialization"). * * We did the static preparation in LinkClass. * * The class has been prepared and resolved but possibly not yet verified * at this point. */ Dbg::PostClassPrepare(new_class_h.Get()); return new_class_h.Get(); }Copy the code
The ClassLinker class has a member variable init_done_ of type bool that indicates whether ClassLinker has been initialized.
If ClassLinker is being initialized, that is, its member variable init_done_ is equal to false, and the argument descriptor describes specific inner classes, then point the local variable klass to them, The rest of the cases are allocated with the member function AllocClass for later initialization with the member function LoadClass.
The ClassLinker member function LoadClass is used to load the specified class from the specified DEX file. Once the specified class is loaded from the DEX file, it needs to be added to ClassLinker’s list of loaded classes via InsertClass, another member function. If the specified class has been loaded before, that is, InsertClass does not return null, then another thread is loading the specified class. One need to call the member function EnsureResolved to ensure (pending) that the class has been loaded and resolved. On the other hand, if no other thread loads the specified class, after the current thread loads the specified class from the specified DEX file, it needs to call the member function LinkClass to parse the loaded class. Finally, an object of type Class is returned to the caller to represent a Class that has been loaded and parsed.
Member function of the ClassLinker classLoadClass
void ClassLinker::LoadClass(const DexFile& dex_file, const DexFile::ClassDef& dex_class_def, Handle<mirror::Class> klass, mirror::ClassLoader* class_loader) { CHECK(klass.Get() ! = nullptr); CHECK(klass->GetDexCache() ! = nullptr); CHECK_EQ(mirror::Class::kStatusNotReady, klass->GetStatus()); const char* descriptor = dex_file.GetClassDescriptor(dex_class_def); CHECK(descriptor ! = nullptr); klass->SetClass(GetClassRoot(kJavaLangClass)); if (kUseBakerOrBrooksReadBarrier) { klass->AssertReadBarrierPointer(); } uint32_t access_flags = dex_class_def.GetJavaAccessFlags(); CHECK_EQ(access_flags & ~kAccJavaFlagsMask, 0U); klass->SetAccessFlags(access_flags); klass->SetClassLoader(class_loader); DCHECK_EQ(klass->GetPrimitiveType(), Primitive::kPrimNot); klass->SetStatus(mirror::Class::kStatusIdx, nullptr); klass->SetDexClassDefIndex(dex_file.GetIndexForClassDef(dex_class_def)); klass->SetDexTypeIndex(dex_class_def.class_idx_); CHECK(klass->GetDexCacheStrings() ! = nullptr); const byte* class_data = dex_file.GetClassData(dex_class_def); if (class_data == nullptr) { return; // no fields or methods - for example a marker interface } OatFile::OatClass oat_class; if (Runtime::Current()->IsStarted() && ! Runtime::Current()->UseCompileTimeClassPath() && FindOatClass(dex_file, klass->GetDexClassDefIndex(), &oat_class)) { LoadClassMembers(dex_file, class_data, klass, class_loader, &oat_class); } else { LoadClassMembers(dex_file, class_data, klass, class_loader, nullptr); }}Copy the code
Dex_file: DexFile, describing the DEX file where the class to be loaded resides. Dex_class_def: the type is ClassDef, which describes the information about the class to be loaded in the DEX file. Klass: Class of type, describing the Class that has been loaded. Class_loader: the type is ClassLoader and describes the ClassLoader used.
The task of LoadClass is to set the information contained in the dex_file, dex_class_def, and class_loader parameters to the Class object described by klass to get a complete view of the loaded Class.
Key:
- SetClassLoader: Sets the ClassLoader described by class_loader into the Class object described by Klass, that is, associating each loaded Class with a ClassLoader.
- SetDexClassDefIndex: Get the index number of the class being loaded in the Dex file from the DexFile member function GetIndexForClassDef and set it to klass
FindOatClass: OAT_class, a OatClass structure corresponding to the class being loaded, is found from the corresponding OAT file. This needs to use the index number of DEX class mentioned above, because there is a one-to-one correspondence between DEX class and OAT class according to the index number.
Member function of the ClassLinker classLoadClassMembers
void ClassLinker::LoadClassMembers(const DexFile& dex_file, const byte* class_data, Handle<mirror::Class> klass, mirror::ClassLoader* class_loader, const OatFile::OatClass* oat_class) { // Load fields. ClassDataItemIterator it(dex_file, class_data); Thread* self = Thread::Current(); if (it.NumStaticFields() ! = 0) { mirror::ObjectArray<mirror::ArtField>* statics = AllocArtFieldArray(self, it.NumStaticFields()); if (UNLIKELY(statics == nullptr)) { CHECK(self->IsExceptionPending()); // OOME. return; } klass->SetSFields(statics); } if (it.NumInstanceFields() ! = 0) { mirror::ObjectArray<mirror::ArtField>* fields = AllocArtFieldArray(self, it.NumInstanceFields()); if (UNLIKELY(fields == nullptr)) { CHECK(self->IsExceptionPending()); // OOME. return; } klass->SetIFields(fields); } for (size_t i = 0; it.HasNextStaticField(); i++, it.Next()) { StackHandleScope<1> hs(self); Handle<mirror::ArtField> sfield(hs.NewHandle(AllocArtField(self))); if (UNLIKELY(sfield.Get() == nullptr)) { CHECK(self->IsExceptionPending()); // OOME. return; } klass->SetStaticField(i, sfield.Get()); LoadField(dex_file, it, klass, sfield); } for (size_t i = 0; it.HasNextInstanceField(); i++, it.Next()) { StackHandleScope<1> hs(self); Handle<mirror::ArtField> ifield(hs.NewHandle(AllocArtField(self))); if (UNLIKELY(ifield.Get() == nullptr)) { CHECK(self->IsExceptionPending()); // OOME. return; } klass->SetInstanceField(i, ifield.Get()); LoadField(dex_file, it, klass, ifield); } // 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 == nullptr)) { 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 == nullptr)) { CHECK(self->IsExceptionPending()); // OOME. return; } klass->SetVirtualMethods(virtuals); } size_t class_def_method_index = 0; uint32_t last_dex_method_index = DexFile::kDexNoIndex; size_t last_class_def_method_index = 0; for (size_t i = 0; it.HasNextDirectMethod(); i++, it.Next()) { StackHandleScope<1> hs(self); Handle<mirror::ArtMethod> method(hs.NewHandle(LoadMethod(self, dex_file, it, klass))); if (UNLIKELY(method.Get() == nullptr)) { CHECK(self->IsExceptionPending()); // OOME. return; } klass->SetDirectMethod(i, method.Get()); LinkCode(method, oat_class, dex_file, it.GetMemberIndex(), class_def_method_index); uint32_t it_method_index = it.GetMemberIndex(); if (last_dex_method_index == it_method_index) { // duplicate case method->SetMethodIndex(last_class_def_method_index); } else { method->SetMethodIndex(class_def_method_index); last_dex_method_index = it_method_index; last_class_def_method_index = class_def_method_index; } class_def_method_index++; } for (size_t i = 0; it.HasNextVirtualMethod(); i++, it.Next()) { StackHandleScope<1> hs(self); Handle<mirror::ArtMethod> method(hs.NewHandle(LoadMethod(self, dex_file, it, klass))); if (UNLIKELY(method.Get() == nullptr)) { CHECK(self->IsExceptionPending()); // OOME. return; } klass->SetVirtualMethod(i, method.Get()); DCHECK_EQ(class_def_method_index, it.NumDirectMethods() + i); LinkCode(method, oat_class, dex_file, it.GetMemberIndex(), class_def_method_index); class_def_method_index++; } DCHECK(! it.HasNext()); }Copy the code
Get the number of static and instance member variables of the class being loaded from the DEX file described by parameter dex_file, and assign an ArtField object to each static and instance member variable, These ArtField objects are then initialized using the LoadField member function of the ClassLinker class. The resulting ArtField objects are all stored in the Class objects described by Klass.
The Class object described by the klass parameter contains A series of ArtField objects, which describe member variable information, and ArtMethod objects, which describe member function information.
We will continue to examine the implementation of the LinkCode function so that we can see how to find a local machine instruction for a DEX class method in an OAT file.
Member function of the ClassLinker classLinkCode
void ClassLinker::LinkCode(Handle<mirror::ArtMethod> method, const OatFile::OatClass* oat_class, const DexFile& dex_file, uint32_t dex_method_index, uint32_t method_index) { if (Runtime::Current()->IsCompiler()) { // The following code only applies to a non-compiler runtime. return; } // Method shouldn't have already been linked. DCHECK(method->GetEntryPointFromQuickCompiledCode() == nullptr); #if defined(ART_USE_PORTABLE_COMPILER) DCHECK(method->GetEntryPointFromPortableCompiledCode() == nullptr); #endif if (oat_class ! = nullptr) { // Every kind of method should at least get an invoke stub from the oat_method. // non-abstract methods also get their code pointers. const OatFile::OatMethod oat_method = oat_class->GetOatMethod(method_index); oat_method.LinkMethod(method.Get()); } // Install entry point from interpreter. bool enter_interpreter = NeedsInterpreter(method.Get(), method->GetEntryPointFromQuickCompiledCode(), #if defined(ART_USE_PORTABLE_COMPILER) method->GetEntryPointFromPortableCompiledCode()); #else nullptr); #endif if (enter_interpreter && ! method->IsNative()) { method->SetEntryPointFromInterpreter(interpreter::artInterpreterToInterpreterBridge); } else { method->SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge); } if (method->IsAbstract()) { method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge()); #if defined(ART_USE_PORTABLE_COMPILER) method->SetEntryPointFromPortableCompiledCode(GetPortableToInterpreterBridge()); #endif return; } bool have_portable_code = false; 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->SetEntryPointFromQuickCompiledCode(GetQuickResolutionTrampoline()); #if defined(ART_USE_PORTABLE_COMPILER) method->SetEntryPointFromPortableCompiledCode(GetPortableResolutionTrampoline()); #endif } else if (enter_interpreter) { if (! method->IsNative()) { // Set entry point from compiled code if there's no code or in interpreter only mode. method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge()); #if defined(ART_USE_PORTABLE_COMPILER) method->SetEntryPointFromPortableCompiledCode(GetPortableToInterpreterBridge()); #endif } else { method->SetEntryPointFromQuickCompiledCode(GetQuickGenericJniTrampoline()); #if defined(ART_USE_PORTABLE_COMPILER) method->SetEntryPointFromPortableCompiledCode(GetPortableToQuickBridge()); #endif } #if defined(ART_USE_PORTABLE_COMPILER) } else if (method->GetEntryPointFromPortableCompiledCode() ! = nullptr) { DCHECK(method->GetEntryPointFromQuickCompiledCode() == nullptr); have_portable_code = true; method->SetEntryPointFromQuickCompiledCode(GetQuickToPortableBridge()); #endif } else { DCHECK(method->GetEntryPointFromQuickCompiledCode() ! = nullptr); #if defined(ART_USE_PORTABLE_COMPILER) method->SetEntryPointFromPortableCompiledCode(GetPortableToQuickBridge()); #endif } if (method->IsNative()) { // Unregistering restores the dlsym lookup stub. method->UnregisterNative(Thread::Current()); if (enter_interpreter) { // We have a native method here without code. Then it should have either the GenericJni // trampoline as entrypoint (non-static), or the Resolution trampoline (static). DCHECK(method->GetEntryPointFromQuickCompiledCode() == GetQuickResolutionTrampoline() || method->GetEntryPointFromQuickCompiledCode() == GetQuickGenericJniTrampoline()); } } // Allow instrumentation its chance to hijack code. Runtime* runtime = Runtime::Current(); runtime->GetInstrumentation()->UpdateMethodsCode(method.Get(), method->GetEntryPointFromQuickCompiledCode(), #if defined(ART_USE_PORTABLE_COMPILER) method->GetEntryPointFromPortableCompiledCode(), #else nullptr, #endif have_portable_code); }Copy the code
- The method parameter represents the class method to set the local machine instruction.
- Oat_class indicates the OatClass structure corresponding to method in the OAT file.
- The dex_file parameter indicates the DexFile in the dex file.
- The dex_method_index argument represents the index number of the method in DexFile.
- The method_index parameter represents the index number of the class method.
With the index number described by parameter method_index, a OatMethod structure OAT_method can be found in the OatClass structure represented by OAT_class. This OatMethod structure describes the local machine instruction information of the method class, which can be set to the ArtMethod object described by method by calling its member function LinkMethod.
Member function of OatMethodLinkMethod
void OatFile::OatMethod::LinkMethod(mirror::ArtMethod* method) const { CHECK(method ! = NULL); #if defined(ART_USE_PORTABLE_COMPILER) method->SetEntryPointFromPortableCompiledCode(GetPortableCode()); #endif method->SetEntryPointFromQuickCompiledCode(GetQuickCode()); }Copy the code
The code_offset_ field in OatMethod structure can be obtained by GetPortableCode and GetQuickCode member functions of OatMethod class. And by calling ArtMethod class member functions SetEntryPointFromCompiledCode set in the parameter method to describe ArtMethod object. The code_offset_ field in OatMethod structure points to a local machine instruction function, which is obtained by translating the DEX bytecode of the class method described by the parameter method.
The OatMethodGetPortableCodefunction
const void* GetPortableCode() const { // TODO: encode whether code is portable/quick in flags within OatMethod. if (kUsePortableCompiler) { return GetOatPointer<const void*>(code_offset_); } else { return nullptr; }}Copy the code
The OatMethodGetQuickCodefunction
const void* GetQuickCode() const { if (kUsePortableCompiler) { return nullptr; } else { return GetOatPointer<const void*>(code_offset_); }}Copy the code
A global function of the ClassLinker classNeedsInterpreter
// Returns true if the method must run with interpreter, false otherwise. static bool NeedsInterpreter( mirror::ArtMethod* method, const void* quick_code, const void* portable_code) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { if ((quick_code == nullptr) && (portable_code == nullptr)) { // No code: need interpreter. // May return true for native code, in the case of generic JNI // DCHECK(! method->IsNative()); return true; } #ifdef ART_SEA_IR_MODE ScopedObjectAccess soa(Thread::Current()); if (std::string::npos ! = PrettyMethod(method).find("fibonacci")) { LOG(INFO) << "Found " << PrettyMethod(method); return false; } #endif // If interpreter mode is enabled, every method (except native and proxy) must // be run with interpreter. return Runtime::Current()->GetInstrumentation()->InterpretOnly() && ! method->IsNative() && ! method->IsProxyMethod(); }Copy the code
Check whether the class method described by method needs to be executed through the interpreter.
There are two cases in which a class method needs to be executed through the interpreter:
- There is no corresponding local machine instruction, that is, the quick_code and portable_code parameters have values equal to NULL.
- The ART virtual machine operates in interpreted mode, and the class methods are not JNI methods, nor are they proxy methods.
Because JNI methods do not have DEX bytecode equivalents, JNI methods cannot be executed by the interpreter even if the ART virtual machine is running in interpreted mode. As for proxy methods, because they are dynamically generated (there is no corresponding DEX bytecode), they are not executed by the interpreter even if the ART VIRTUAL machine is in interpreted mode.
Calling Current, a static member of the Runtime class, yields a Runtime object that describes the ART Runtime. Calling the GetInstrumentation member of this Runtime object returns an Instrumentation object. The Instrumentation object is used to debug the ART runtime, and its member function InterpretOnly is called to determine whether the ART virtual machine is running in interpreted mode.
Back to the member function of the ClassLinker classLinkCode
If calling NeedsInterpreter returns enter_interpreter equal to true and is not a Native method, it means that the class methods described by method need to be executed through the interpreter, Time is set function artInterpreterToInterpreterBridge to interpreter performs the entry point of this method. Otherwise, it will be another function artInterpreterToCompiledCodeBridge Settings for the interpreter to perform the entry point of this method.
Why do we need to set interpreter entry points for class methods? As you can see from the previous analysis, not all class methods in the ART virtual machine have corresponding local machine instructions, and even if a class method has corresponding local machine instructions, when the ART virtual machine is running in explain mode, it needs to be executed through the interpreter. When a class method executed by the interpreter calls other class methods in the process of execution, the interpreter needs to know further whether the called class method should be executed by interpretation or by local machine instruction methods. To allow for uniform processing, each class method is assigned an interpreter entry point. Need to be explained by the implementation of class methods the interpreter is artInterpreterToInterpreterBridge entry point function, it will continue through the interpreter to execute the method. Need through the local machine instruction execution class methods of the interpreter is the entry point function artInterpreterToCompiledCodeBridge, it indirectly calls to the method of local machine instructions.
Determine if method is an abstract method. Abstract method declarations are not implemented in classes and must be implemented by subclasses. Therefore, abstract methods have no local machine instructions in the declarative class; they must be executed through the interpreter. In order to be able to undertake unity processing, however, we still pretend to abstract methods have a corresponding local machine directive function, but this function is set to GetQuickToInterpreterBridge local machine instructions. When the function GetQuickToInterpreterBridge, will automatically enter into the interpreter.
When method is a static method that is not a class initializer, we cannot directly execute the local machine instructions that translate its DEX bytecode. This is because class static methods can be executed without creating class objects. This means that a static method of a class may be executed when the corresponding class is not properly initialized. In this case, we need to initialize the corresponding class, and then execute the corresponding static method. In order to be able to do that. We call GetResolutionTrampoline to get a Tampoline function, and then use the Trampoline function as the local machine instruction for the static method. This will trigger the Trampoline function to be executed if the class static method is called before the corresponding class is initialized. When the Trampoline functions are executed, they first initialize the corresponding class and then call the local machine instructions corresponding to the original class static methods. According to the comments in the code, when a class initialization is completed, you can call a function ClassLinker: : FixupStaticTrampolines to repair the class the static member function of a local machine instructions, and through a translator DEX bytecode get local machine instructions. The important thing to note here is that the class static initializer doesn’t need to set the Tampoline function like any other class static method. This is because class static initializers are guaranteed to be executed during class initialization.
When method need through an interpreter executes, so when this kind of method execution, will not be able to perform its local machine instructions, so we’ll call GetCompiledCodeToInterpreterBridge function to obtain a bridge function, And pretend that the bridge function is a local machine instruction for the class method. Once the bridge function is executed, it goes into the interpreter to execute the class method. In this way, we can call class methods that interpret execution and execution of local machine instructions in a uniform way.
Check whether method is a JNI method. If so, the member function UnregisterNative of the ArtMethod class is called to initialize its JNI method invocation interface.
A member function of the ArtMethod classUnregisterNative
void ArtMethod::UnregisterNative(Thread* self) { CHECK(IsNative() && ! IsFastNative()) << PrettyMethod(this); // restore stub to lookup native pointer via dlsym RegisterNative(self, GetJniDlsymLookupStub(), false); }Copy the code
UnregisterNative is essentially setting the initialization entry of a JNI method to a Stub obtained by calling the function GetJniDlsymLookupStub. The purpose of this Stub is that when a JNI method is called, if a Native function is not explicitly registered, it will automatically check the loaded SO file to see if a corresponding Native function exists. If it exists, register it as a Native function of the JNI method and execute it. This is the implicit JNI method registration.
Back to the member function of the ClassLinker classLinkCode
Finally, call the member function UpdateMethodsCode of the Instrumentation class to check whether the local machine instruction entry for the class method described by the method parameter needs to be further modified.
void Instrumentation::UpdateMethodsCode(mirror::ArtMethod* method, const void* quick_code, const void* portable_code, bool have_portable_code) { const void* new_portable_code; const void* new_quick_code; bool new_have_portable_code; if (LIKELY(! instrumentation_stubs_installed_)) { new_portable_code = portable_code; new_quick_code = quick_code; new_have_portable_code = have_portable_code; } else { if ((interpreter_stubs_installed_ || IsDeoptimized(method)) && ! method->IsNative()) { #if defined(ART_USE_PORTABLE_COMPILER) new_portable_code = GetPortableToInterpreterBridge(); #else new_portable_code = portable_code; #endif new_quick_code = GetQuickToInterpreterBridge(); new_have_portable_code = false; } else { ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); if (quick_code == class_linker->GetQuickResolutionTrampoline() || quick_code == class_linker->GetQuickToInterpreterBridgeTrampoline() || quick_code == GetQuickToInterpreterBridge()) { #if defined(ART_USE_PORTABLE_COMPILER) DCHECK((portable_code == class_linker->GetPortableResolutionTrampoline()) || (portable_code == GetPortableToInterpreterBridge())); #endif new_portable_code = portable_code; new_quick_code = quick_code; new_have_portable_code = have_portable_code; } else if (entry_exit_stubs_installed_) { new_quick_code = GetQuickInstrumentationEntryPoint(); #if defined(ART_USE_PORTABLE_COMPILER) new_portable_code = GetPortableToInterpreterBridge(); #else new_portable_code = portable_code; #endif new_have_portable_code = false; } else { new_portable_code = portable_code; new_quick_code = quick_code; new_have_portable_code = have_portable_code; } } } UpdateEntrypoints(method, new_quick_code, new_portable_code, new_have_portable_code); }Copy the code
The Instrumentation class is used to call the ART runtime. For example, when we need to monitor class method calls, we can register some listeners into Instrumentation. These registered Listeners will then get callbacks when the class method is called. When Instrumentation registers a Listener, its member variable instrumentation_stubs_installed_ will be equal to true.
Conclusion:
With the source tracing analysis above, the loading process of a class is complete. After loading, you get a Class object. This Class object is associated with a series of ArtField objects and ArtMethod objects. The ArtField object describes member variables, while the ArtMethod object describes member functions. For each ArtMethod object, it has an interpreter entry point and a local machine instruction entry point. This way, whether a class method is executed through the interpreter or directly from local machine instructions, we can call it in a uniform way. At the same time, after understanding the above Class loading process, we can know that when we search or load a Class in the Native layer through the JNI interface FindClass, we get an opaque JClass value that actually points to a Class object.
Class method lookup flow
- GetStaticMethodID (/art/runtime/jni_internal.cc#943)
- FindMethodID (/art/runtime/jni_internal.cc#140)
Static member functions of the JNI classFindClass
static jmethodID GetStaticMethodID(JNIEnv* env, jclass java_class, const char* name,
const char* sig) {
CHECK_NON_NULL_ARGUMENT(java_class);
CHECK_NON_NULL_ARGUMENT(name);
CHECK_NON_NULL_ARGUMENT(sig);
ScopedObjectAccess soa(env);
return FindMethodID(soa, java_class, name, sig, true);
}
Copy the code
The parameters name and SIG describe the name and signature of the class method to be looked up, respectively, while the parameter javA_class is the corresponding class. The java_class argument is of type jclass, and as you can see from the previous analysis of the classloading process, it actually points to a Class object.
Static member functions of the JNI classFindMethodID
static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class, const char* name, const char* sig, bool is_static) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { mirror::Class* c = EnsureInitialized(soa.Self(), soa.Decode<mirror::Class*>(jni_class)); if (c == nullptr) { return nullptr; } mirror::ArtMethod* method = nullptr; if (is_static) { method = c->FindDirectMethod(name, sig); } else if (c->IsInterface()) { method = c->FindInterfaceMethod(name, sig); } else { method = c->FindVirtualMethod(name, sig); if (method == nullptr) { // No virtual method matching the signature. Search declared // private methods and constructors. method = c->FindDeclaredDirectMethod(name, sig); } } if (method == nullptr || method->IsStatic() ! = is_static) { ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static"); return nullptr; } return soa.EncodeMethod(method); }Copy the code
Execution process:
- A Class object is obtained by converting the value of the jni_class argument to a Class pointer C, and EnsureInitialized, a member of the ClassLinker Class, ensures that the Class described by the Class object is initialized.
- The Class object C describes a Class that has a set of member functions associated with it after parsing during loading. These member functions can be divided into two categories: Direct and Virtual. The member functions of the Direct class include all static member functions, private member functions, and constructors, while Virtual includes all Virtual member functions.
- After the previous lookup, a NoSuchMethodError exception is raised if a member function corresponding to the parameters Name and SIG cannot be found in the Class described by Class object C. Otherwise, the ArtMethod object found is wrapped as a jmethodID value and returned to the caller.
The opaque jmethodID value we get by calling the JNI interface GetStaticMethodID refers to an ArtMethod object. Once we have an ArtMethod object, we can easily get its local machine instruction entry and execute it.
Conclusion:
When we replace all the member fields of the old (ArtMethod) method with the member fields of the new (ArtMethod) method, all the data at execution will be consistent with the data of the new method. In this way, wherever the old method is executed, the execution entry, owning type, method index number, and owning dex information of the new method will be obtained, and the logic of the new method will be executed as if the old method was invoked.