版權歸作者所有,如有轉發,請注明文章出處:https://cyrus-studio.github.io/blog/
FART簡介
ART 環境下基于主動調用的自動化脫殼方案,可以解決函數抽取殼。
關于函數抽取殼的實現原理可以參考:基于 art 下的類加載機制,實現函數抽取殼
FART 的作用就是所有這些被抽空的函數的還原和修復,把加固的 dex 整體 dump 下來。
項目地址:https://github.com/hanbinglengyue/FART
FART 框架
脫殼組件:將內存中的 Dex 數據完整 dump 出來
主動調用組件:構造主動調用鏈,完成對函數粒度的主動調用并完成 CodeItem 的 dump
修復組件:利用脫殼組件得到的 dex 和主動調用 dump 下來的函數體,完成函數粒度的修復
FART 中的脫殼點
其中 FART 脫殼組件 選擇 Execute 作為脫殼點,它是 Interpreter 模式執行所有 Java 方法的統一入口,能夠穩定截獲和提取所有解釋執行的真實方法,從而達到通用脫殼的目的。
ART 下函數在運行時可能是解釋執行(Interpreter 模式)或編譯執行(Quick 模式)。
為何選擇 Execute 作為脫殼點?
dex2oat 編譯流程
關于 dex2oat 以及 vdex、cdex、dex 格式轉換
dex2oat 編譯流程入口函數:
int main(int argc, char** argv) {int result = static_cast<int>(art::Dex2oat(argc, argv));// Everything was done, do an explicit exit here to avoid running Runtime destructors that take// time (bug 10645725) unless we're a debug or instrumented build or running on a memory tool.// Note: The Dex2Oat class should not destruct the runtime in this case.if (!art::kIsDebugBuild && !art::kIsPGOInstrumentation && !art::kRunningOnMemoryTool) {_exit(result);}return result;
}
https://cs.android.com/android/platform/superproject/+/android10-release:art/dex2oat/dex2oat.cc;l=3027
apk 安裝時進行的 dex2oat 編譯流程,dex2oat 的編譯驅動會對函數逐個編譯
https://www.processon.com/i/6825c4e0c168ca282c1b9fb4?full_name=PO_iNeoB2
模板函數 CompileDexFile:編譯 dex 文件中的所有類和方法,最后會調用 compile_fn 進行函數粒度編譯
// 模板函數:編譯 dex 文件中的所有類和方法
template <typename CompileFn>
static void CompileDexFile(CompilerDriver* driver,jobject class_loader,const DexFile& dex_file,const std::vector<const DexFile*>& dex_files,ThreadPool* thread_pool,size_t thread_count,TimingLogger* timings,const char* timing_name,CompileFn compile_fn) {// 用于性能分析記錄這段編譯過程的時間TimingLogger::ScopedTiming t(timing_name, timings);// 創建一個用于并行編譯的上下文管理器ParallelCompilationManager context(Runtime::Current()->GetClassLinker(),class_loader,driver,&dex_file,dex_files,thread_pool);// 編譯單個類的回調函數auto compile = [&context, &compile_fn](size_t class_def_index) {const DexFile& dex_file = *context.GetDexFile();SCOPED_TRACE << "compile " << dex_file.GetLocation() << "@" << class_def_index;ClassLinker* class_linker = context.GetClassLinker();jobject jclass_loader = context.GetClassLoader();ClassReference ref(&dex_file, class_def_index);const dex::ClassDef& class_def = dex_file.GetClassDef(class_def_index);ClassAccessor accessor(dex_file, class_def_index);CompilerDriver* const driver = context.GetCompiler();// 跳過驗證失敗的類(這些類在運行時也會失敗)if (driver->GetCompilerOptions().GetVerificationResults()->IsClassRejected(ref)) {return;}// 進入托管代碼環境,訪問 Java 對象ScopedObjectAccess soa(Thread::Current());StackHandleScope<3> hs(soa.Self());// 解碼 class_loader 對象Handle<mirror::ClassLoader> class_loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(jclass_loader)));// 查找類對象Handle<mirror::Class> klass(hs.NewHandle(class_linker->FindClass(soa.Self(), accessor.GetDescriptor(), class_loader)));Handle<mirror::DexCache> dex_cache;if (klass == nullptr) {// 類加載失敗,清除異常并使用 dex cachesoa.Self()->AssertPendingException();soa.Self()->ClearException();dex_cache = hs.NewHandle(class_linker->FindDexCache(soa.Self(), dex_file));} else if (SkipClass(jclass_loader, dex_file, klass.Get())) {// 判斷是否跳過該類(如外部類、系統類等)return;} else if (&klass->GetDexFile() != &dex_file) {// 重復類(已從另一個 dex 文件加載),跳過return;} else {dex_cache = hs.NewHandle(klass->GetDexCache());}// 沒有方法的類無需編譯if (accessor.NumDirectMethods() + accessor.NumVirtualMethods() == 0) {return;}// 進入 native 狀態,避免阻塞 GCScopedThreadSuspension sts(soa.Self(), kNative);// 判斷是否啟用 dex-to-dex 編譯(可能是省略優化過程)optimizer::DexToDexCompiler::CompilationLevel dex_to_dex_compilation_level =GetDexToDexCompilationLevel(soa.Self(), *driver, jclass_loader, dex_file, class_def);// 編譯類中所有 direct 和 virtual 方法int64_t previous_method_idx = -1;for (const ClassAccessor::Method& method : accessor.GetMethods()) {const uint32_t method_idx = method.GetIndex();if (method_idx == previous_method_idx) {// 處理非法 smali 文件:可能多個 method 共用同一個 method_idx(重復定義)continue;}previous_method_idx = method_idx;// 調用外部傳入的 compile_fn 進行實際方法編譯compile_fn(soa.Self(),driver,method.GetCodeItem(),method.GetAccessFlags(),method.GetInvokeType(class_def.access_flags_),class_def_index,method_idx,class_loader,dex_file,dex_to_dex_compilation_level,dex_cache);}};// 并發執行 compile 回調,對 dex 文件中的所有 class 進行編譯context.ForAllLambda(0, dex_file.NumClassDefs(), compile, thread_count);
}
https://cs.android.com/android/platform/superproject/+/android10-release:art/dex2oat/driver/compiler_driver.cc;l=2559
compile_fn 是一個回調函數,用于處理每個方法的編譯過程(通常是 JIT/AOT 編譯器提供的函數指針或 Lambda)。
https://cs.android.com/android/platform/superproject/+/android10-release:art/dex2oat/driver/compiler_driver.cc;l=2632
在 dex2oat 編譯流程中,compile_fn 是 CompileMethodQuick 函數,它是 Quick 編譯器(AOT) 編譯每個方法的核心入口。
// 快速編譯指定方法的包裝函數,用于傳入 CompileDexFile 進行批量編譯。
static void CompileMethodQuick(Thread* self,CompilerDriver* driver,const dex::CodeItem* code_item,uint32_t access_flags,InvokeType invoke_type,uint16_t class_def_idx,uint32_t method_idx,Handle<mirror::ClassLoader> class_loader,const DexFile& dex_file,optimizer::DexToDexCompiler::CompilationLevel dex_to_dex_compilation_level,Handle<mirror::DexCache> dex_cache) {// 實際執行編譯的 lambda 函數,傳給 CompileMethodHarness 執行auto quick_fn = [](Thread* self,CompilerDriver* driver,const dex::CodeItem* code_item,uint32_t access_flags,InvokeType invoke_type,uint16_t class_def_idx,uint32_t method_idx,Handle<mirror::ClassLoader> class_loader,const DexFile& dex_file,optimizer::DexToDexCompiler::CompilationLevel dex_to_dex_compilation_level,Handle<mirror::DexCache> dex_cache) {DCHECK(driver != nullptr);CompiledMethod* compiled_method = nullptr;MethodReference method_ref(&dex_file, method_idx); // 方法引用(用于 profile 與驗證等)// 如果是 native 方法if ((access_flags & kAccNative) != 0) {// 如果禁用了 JNI 編譯但目標平臺支持通用 stub,則跳過生成 stub,使用默認實現if (!driver->GetCompilerOptions().IsJniCompilationEnabled() &&InstructionSetHasGenericJniStub(driver->GetCompilerOptions().GetInstructionSet())) {// 什么也不做,走 generic jni stub} else {// 讀取方法上的 @FastNative 或 @CriticalNative 注解(優化調用約定)access_flags |= annotations::GetNativeMethodAnnotationAccessFlags(dex_file, dex_file.GetClassDef(class_def_idx), method_idx);// 使用編譯器生成 JNI stub(橋接 Java 和 native 函數的中間代碼)compiled_method = driver->GetCompiler()->JniCompile(access_flags, method_idx, dex_file, dex_cache);CHECK(compiled_method != nullptr); // 確保 JNI 編譯成功}// 如果是 abstract 方法,無需編譯(沒有實現體)} else if ((access_flags & kAccAbstract) != 0) {// Do nothing// 普通 Java 方法} else {const VerificationResults* results = driver->GetCompilerOptions().GetVerificationResults();DCHECK(results != nullptr);const VerifiedMethod* verified_method = results->GetVerifiedMethod(method_ref);// 判斷該方法是否應該被編譯bool compile =results->IsCandidateForCompilation(method_ref, access_flags) &&verified_method != nullptr &&!verified_method->HasRuntimeThrow() && // 驗證階段沒有失敗(verified_method->GetEncounteredVerificationFailures() &(verifier::VERIFY_ERROR_FORCE_INTERPRETER | verifier::VERIFY_ERROR_LOCKING)) == 0 &&driver->ShouldCompileBasedOnProfile(method_ref); // 在 profile 中標記為熱點if (compile) {// 編譯方法(返回 CompiledMethod 對象)compiled_method = driver->GetCompiler()->Compile(code_item,access_flags,invoke_type,class_def_idx,method_idx,class_loader,dex_file,dex_cache);// 根據設置校驗 profile 方法是否一定要被編譯成功ProfileMethodsCheck check_type =driver->GetCompilerOptions().CheckProfiledMethodsCompiled();if (UNLIKELY(check_type != ProfileMethodsCheck::kNone)) {bool violation = driver->ShouldCompileBasedOnProfile(method_ref) &&(compiled_method == nullptr);if (violation) {std::ostringstream oss;oss << "Failed to compile "<< method_ref.dex_file->PrettyMethod(method_ref.index)<< "[" << method_ref.dex_file->GetLocation() << "]"<< " as expected by profile";switch (check_type) {case ProfileMethodsCheck::kNone:break;case ProfileMethodsCheck::kLog:LOG(ERROR) << oss.str(); // 僅記錄錯誤日志break;case ProfileMethodsCheck::kAbort:LOG(FATAL_WITHOUT_ABORT) << oss.str(); // 直接終止程序_exit(1);}}}}// 如果 Quick 編譯失敗,且允許 Dex-to-Dex 編譯,則走 D2D 優化路徑if (compiled_method == nullptr &&dex_to_dex_compilation_level !=optimizer::DexToDexCompiler::CompilationLevel::kDontDexToDexCompile) {DCHECK(!Runtime::Current()->UseJitCompilation()); // AOT 模式driver->GetDexToDexCompiler().MarkForCompilation(self, method_ref); // 標記用于 D2D}}return compiled_method; // 返回最終生成的 CompiledMethod 對象或 nullptr};// 使用通用包裝器調用 lambda,用于處理計時、線程控制、異常處理等CompileMethodHarness(self,driver,code_item,access_flags,invoke_type,class_def_idx,method_idx,class_loader,dex_file,dex_to_dex_compilation_level,dex_cache,quick_fn);
}
https://cs.android.com/android/platform/superproject/+/android10-release:art/dex2oat/driver/compiler_driver.cc;l=2671
https://cs.android.com/android/platform/superproject/+/android10-release:art/dex2oat/driver/compiler_driver.cc;l=527
并不是所有函數都會被編譯!
比如類的初始化函數 <clinit>。因此,對于當一個類被初始化時,該類的初始化函數始終運行在 Interpreter 模式
https://cs.android.com/android/platform/superproject/+/android10-release:art/dex2oat/driver/compiler_driver.cc;l=577
ART 下函數執行模式
ART 下函數執行模式:
-
Interpreter 模式:使用 ART 自帶的解釋器逐條解釋執行 DEX 字節碼
-
Quick 模式:直接運行 DEX 字節碼 通過 dex2oat 編譯后的 平臺相關的機器碼(如 ARM64 指令)
調用 ArtMethod::Invoke 執行一個 Java 方法,執行流程大概如下:
ArtMethod::Invoke(...)├─ 判斷是否需要解釋執行(Interpreter 模式)│ └─ 是:調用 EnterInterpreterFromInvoke(...)│ └─ 構造 shadow frame(解釋器需要的棧幀)│ └─ 如果是非 native 方法:調用 Execute(...) 開始解釋執行│ └─ 如果是 native 方法:走 InterpreterJni(...)└─ 否:調用快速入口點 art_quick_invoke_stub 或 art_quick_invoke_static_stub
Interpreter 模式流程
從 ArtMethod 類中的 Invoke 方法開始
https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/art_method.cc;l=303
art::interpreter::EnterInterpreterFromInvoke 是解釋器模式的入口方法。
void EnterInterpreterFromInvoke(Thread* self,ArtMethod* method,ObjPtr<mirror::Object> receiver,uint32_t* args,JValue* result,bool stay_in_interpreter) {DCHECK_EQ(self, Thread::Current());// 🔒 檢查是否棧溢出(防止非法棧訪問)bool implicit_check = !Runtime::Current()->ExplicitStackOverflowChecks();if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEndForInterpreter(implicit_check))) {ThrowStackOverflowError(self);return;}// ?? 檢查是否調用了已經過時(Obsolete)的方法if (UNLIKELY(method->IsObsolete())) {ThrowInternalError("Attempting to invoke obsolete version of '%s'.",method->PrettyMethod().c_str());return;}// 🧵 禁止線程掛起,防止中間被 GC 打斷const char* old_cause = self->StartAssertNoThreadSuspension("EnterInterpreterFromInvoke");// 🎯 獲取該方法的 CodeItem 數據,用于獲取寄存器數和入參數CodeItemDataAccessor accessor(method->DexInstructionData());uint16_t num_regs;uint16_t num_ins;if (accessor.HasCodeItem()) {// Java 方法:從 CodeItem 中獲取寄存器數和參數數num_regs = accessor.RegistersSize();num_ins = accessor.InsSize();} else if (!method->IsInvokable()) {// 方法無法被調用(比如 abstract),拋出錯誤self->EndAssertNoThreadSuspension(old_cause);method->ThrowInvocationTimeError();return;} else {// Native 方法:計算參數數量(靜態方法不需要接收者)DCHECK(method->IsNative());num_regs = num_ins = ArtMethod::NumArgRegisters(method->GetShorty());if (!method->IsStatic()) {num_regs++;num_ins++;}}// 🧱 創建 ShadowFrame(棧幀結構體),用于解釋器執行ShadowFrame* last_shadow_frame = self->GetManagedStack()->GetTopShadowFrame();ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =CREATE_SHADOW_FRAME(num_regs, last_shadow_frame, method, /* dex pc */ 0);ShadowFrame* shadow_frame = shadow_frame_unique_ptr.get();self->PushShadowFrame(shadow_frame); // 壓入當前線程的 shadow frame 棧// 📦 將參數填充到 shadow frame 中(包括 this/receiver)size_t cur_reg = num_regs - num_ins;if (!method->IsStatic()) {// 實例方法的第一個參數是 receiverCHECK(receiver != nullptr);shadow_frame->SetVRegReference(cur_reg, receiver);++cur_reg;}// 根據方法簽名(shorty)將參數依次放入 shadow frame 的寄存器中uint32_t shorty_len = 0;const char* shorty = method->GetShorty(&shorty_len);for (size_t shorty_pos = 0, arg_pos = 0; cur_reg < num_regs; ++shorty_pos, ++arg_pos, cur_reg++) {DCHECK_LT(shorty_pos + 1, shorty_len);switch (shorty[shorty_pos + 1]) {case 'L': { // 對象引用ObjPtr<mirror::Object> o =reinterpret_cast<StackReference<mirror::Object>*>(&args[arg_pos])->AsMirrorPtr();shadow_frame->SetVRegReference(cur_reg, o);break;}case 'J': case 'D': { // long 或 double,占兩個寄存器uint64_t wide_value = (static_cast<uint64_t>(args[arg_pos + 1]) << 32) | args[arg_pos];shadow_frame->SetVRegLong(cur_reg, wide_value);cur_reg++; // 多占一個寄存器arg_pos++;break;}default: // 其他基本類型(int、float等)shadow_frame->SetVReg(cur_reg, args[arg_pos]);break;}}self->EndAssertNoThreadSuspension(old_cause); // 恢復線程掛起狀態// 🧪 如果是靜態方法,確保類已初始化(可能觸發類初始化)if (method->IsStatic() && UNLIKELY(!method->GetDeclaringClass()->IsInitialized())) {ClassLinker* class_linker = Runtime::Current()->GetClassLinker();StackHandleScope<1> hs(self);Handle<mirror::Class> h_class(hs.NewHandle(method->GetDeclaringClass()));if (UNLIKELY(!class_linker->EnsureInitialized(self, h_class, true, true))) {// 初始化失敗,拋出異常,退出CHECK(self->IsExceptionPending());self->PopShadowFrame();return;}}// 🧠【解釋器執行路徑】if (LIKELY(!method->IsNative())) {// 🎯 執行解釋器主函數// 🔥 這一步真正進入 Execute(根據配置進入 mterp 或 switch 實現)JValue r = Execute(self, accessor, *shadow_frame, JValue(), stay_in_interpreter);if (result != nullptr) {*result = r; // 保存執行結果}} else {// 💡 Native 方法:JNI 函數不會走普通解釋器路徑// 但在 image 寫入或測試時會通過 InterpreterJni 執行args = shadow_frame->GetVRegArgs(method->IsStatic() ? 0 : 1);if (!Runtime::Current()->IsStarted()) {// image 構建期模擬調用 native 方法UnstartedRuntime::Jni(self, method, receiver.Ptr(), args, result);} else {// 正常 JNI 方法調用InterpreterJni(self, method, shorty, receiver, args, result);}}// 🧹 彈出 shadow frame,恢復執行棧self->PopShadowFrame();
}
https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/interpreter/interpreter.cc;l=399
Interpreter 模式下,Java 函數最終都會走到 Execute。
總體調用流程
ArtMethod::Invoke↓
interpreter::EnterInterpreterFromInvoke↓
interpreter::Execute ← 解釋器執行核心↓
[use_mterp ?] → ExecuteMterpImplExecuteSwitchImpl
可以看到,對于任何一個運行在 interpreter 模式的 java 函數來說,最終都會進入到 ART 下的解釋器中進行解釋執行。
Execute
Execute 函數負責在 ART 虛擬機中根據當前執行環境選擇合適的解釋器(如 Mterp 或 Switch)執行指定的 Java 方法字節碼。
static inline JValue Execute(Thread* self,const CodeItemDataAccessor& accessor,ShadowFrame& shadow_frame,JValue result_register,bool stay_in_interpreter = false,bool from_deoptimize = false) REQUIRES_SHARED(Locks::mutator_lock_) {// 方法不能是 abstract 或 native,因為解釋器無法執行它們DCHECK(!shadow_frame.GetMethod()->IsAbstract());DCHECK(!shadow_frame.GetMethod()->IsNative());// 檢查當前線程是否使用了正確類型的解釋器(比如 mterp)if (kIsDebugBuild && self->UseMterp() != CanUseMterp()) {MutexLock tll_mu(self, *Locks::thread_list_lock_);DCHECK_EQ(self->UseMterp(), CanUseMterp());}// 如果不是從 deoptimization 進入(正常調用路徑)if (LIKELY(!from_deoptimize)) {if (kIsDebugBuild) {// 新進入方法,DexPC 應為 0,且不能有異常待處理CHECK_EQ(shadow_frame.GetDexPC(), 0u);self->AssertNoPendingException();}// 獲取當前運行時的 instrumentation 組件(用于調試/監控方法調用)instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();ArtMethod* method = shadow_frame.GetMethod();// 如果注冊了方法進入監聽器,則調用監聽邏輯if (UNLIKELY(instrumentation->HasMethodEntryListeners())) {instrumentation->MethodEnterEvent(self,shadow_frame.GetThisObject(accessor.InsSize()),method,0);// 如果 instrumentation 指定需要強制退出該幀if (UNLIKELY(shadow_frame.GetForcePopFrame())) {DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());DCHECK(PrevFrameWillRetry(self, shadow_frame));return JValue(); // 不執行,直接返回}// 如果 instrumentation 導致了異常,也直接返回if (UNLIKELY(self->IsExceptionPending())) {instrumentation->MethodUnwindEvent(self,shadow_frame.GetThisObject(accessor.InsSize()),method,0);return JValue();}}// 如果允許切換到 JIT 編譯執行(非強制 stay_in_interpreter 且非強制解釋器)if (!stay_in_interpreter && !self->IsForceInterpreter()) {jit::Jit* jit = Runtime::Current()->GetJit();if (jit != nullptr) {// 通知 JIT 方法已進入jit->MethodEntered(self, method);// 如果該方法已經被編譯過了,則可以直接調用機器碼if (jit->CanInvokeCompiledCode(method)) {JValue result;// 先彈出 ShadowFrameself->PopShadowFrame();// 計算參數偏移量(輸入參數寄存器在高位)uint16_t arg_offset = accessor.RegistersSize() - accessor.InsSize();// 通過橋接方法跳轉到已編譯代碼執行ArtInterpreterToCompiledCodeBridge(self, nullptr, &shadow_frame, arg_offset, &result);// 執行完成后重新壓回 ShadowFrameself->PushShadowFrame(&shadow_frame);return result;}}}}// 獲取當前方法ArtMethod* method = shadow_frame.GetMethod();// 驗證方法靜態狀態DCheckStaticState(self, method);// 如果啟用了訪問檢查,則必須關閉鎖計數器檢查DCHECK(!method->SkipAccessChecks() || !method->MustCountLocks());bool transaction_active = Runtime::Current()->IsActiveTransaction();// 判斷是否跳過訪問權限檢查if (LIKELY(method->SkipAccessChecks())) {// === 進入 "無需訪問檢查" 模式 ===if (kInterpreterImplKind == kMterpImplKind) {// 解釋器是 mterpif (transaction_active) {// mterp 不支持事務,回退到 switch 模式return ExecuteSwitchImpl<false, true>(self, accessor, shadow_frame, result_register, false);} else if (UNLIKELY(!Runtime::Current()->IsStarted())) {// Runtime 尚未啟動,mterp 不可用,回退 switch 模式return ExecuteSwitchImpl<false, false>(self, accessor, shadow_frame, result_register, false);} else {// 可使用 mterpwhile (true) {// mterp 不支持調試/斷點等,所以如果當前線程不允許用 mterp,就退回 switchif (!self->UseMterp()) {return ExecuteSwitchImpl<false, false>(self, accessor, shadow_frame, result_register, false);}// 調用 mterp 執行指令bool returned = ExecuteMterpImpl(self,accessor.Insns(), // 獲取指令序列&shadow_frame,&result_register);if (returned) {// mterp 執行成功(正常返回)return result_register;} else {// mterp 無法處理該指令,改用 switch 解釋器單步執行result_register = ExecuteSwitchImpl<false, false>(self, accessor, shadow_frame, result_register, true);if (shadow_frame.GetDexPC() == dex::kDexNoIndex) {// 已執行 return 或發生未捕獲異常,直接返回return result_register;}}}}} else {// 當前解釋器類型是 switchDCHECK_EQ(kInterpreterImplKind, kSwitchImplKind);if (transaction_active) {return ExecuteSwitchImpl<false, true>(self, accessor, shadow_frame, result_register, false);} else {return ExecuteSwitchImpl<false, false>(self, accessor, shadow_frame, result_register, false);}}} else {// === 進入 "需要訪問檢查" 模式 ===// 啟動路徑不應該運行到這里,除非是軟驗證失敗或者在 AOT 編譯中DCHECK(method->GetDeclaringClass()->GetClassLoader() != nullptr|| Runtime::Current()->IsVerificationSoftFail()|| Runtime::Current()->IsAotCompiler())<< method->PrettyMethod();if (kInterpreterImplKind == kMterpImplKind) {// mterp 不支持訪問檢查,強制使用 switch 模式if (transaction_active) {return ExecuteSwitchImpl<true, true>(self, accessor, shadow_frame, result_register, false);} else {return ExecuteSwitchImpl<true, false>(self, accessor, shadow_frame, result_register, false);}} else {// switch 模式解釋器分支DCHECK_EQ(kInterpreterImplKind, kSwitchImplKind);if (transaction_active) {return ExecuteSwitchImpl<true, true>(self, accessor, shadow_frame, result_register, false);} else {return ExecuteSwitchImpl<true, false>(self, accessor, shadow_frame, result_register, false);}}}
}
https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/interpreter/interpreter.cc;l=247
ExecuteMterpImpl 和 ExecuteSwitchImpl
ExecuteMterpImpl 和 ExecuteSwitchImpl 是 ART 虛擬機中兩種解釋器的執行實現方式,它們的主要區別在于 執行效率、可調試性 和 支持的功能。
-
ExecuteMterpImpl:使用 高性能的匯編模板代碼(如 x86、arm64 手寫匯編)執行字節碼,效率非常高。
-
ExecuteSwitchImpl:基于 C++ 的 switch-case 控制流,每條字節碼指令有一個 case 分支。
它們是如何被選擇執行的? 在 Execute 函數中:
if (kInterpreterImplKind == kMterpImplKind) {if (transaction_active) {return ExecuteSwitchImpl<...>(); // Mterp 不支持事務,退回 Switch} else if (!Runtime::Current()->IsStarted()) {return ExecuteSwitchImpl<...>(); // VM 沒啟動也不能用 Mterp} else {while (true) {if (!self->UseMterp()) {return ExecuteSwitchImpl<...>(); // 當前線程禁用了 Mterp}bool returned = ExecuteMterpImpl(...);if (returned) {return result_register; // Mterp 成功執行完成} else {// Mterp 遇到不支持的指令或狀態,單步回退到 Switchresult_register = ExecuteSwitchImpl(...);if (shadow_frame.GetDexPC() == dex::kDexNoIndex) {return result_register; // 已返回或異常拋出}}}}
}
脫殼實現
對于函數抽取殼的 dex 來說,因為需要禁用了 dex2oat ,所以都會以解釋模式運行,進入到 Execute 函數里面。
Execute 參數中有 ShadowFrame,能拿到 ArtMethod,再通過 GetDexFile() 函數獲得 DEX 字節碼等信息。
在 dex 中第一個類初始化的時候調用 dumpDexFileByExecute 脫殼
static inline JValue Execute(Thread* self, const DexFile::CodeItem* code_item,ShadowFrame& shadow_frame, JValue result_register) {// 在類初始化的時候脫殼 if(strstr(PrettyMethod(shadow_frame.GetMethod()).c_str(),"<clinit>")!=nullptr) {dumpDexFileByExecute(shadow_frame.GetMethod());}...}
在 dumpDexFileByExecute 中判斷 如果 dex 文件不存在就 dump
// 該函數在 ART 執行期間調用,用于在 Execute 函數內完成 dex 脫殼
extern "C" void dumpDexFileByExecute(ArtMethod *artmethod)SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {// 為 dex 文件保存路徑分配內存char *dexfilepath = (char *) malloc(sizeof(char) * 2000);if (dexfilepath == nullptr) {// 分配失敗,打印日志并返回LOG(INFO) << "ArtMethod::dumpDexFileByExecute, methodname: "<< PrettyMethod(artmethod).c_str()<< " malloc 2000 byte failed";return;}// 獲取當前進程的 cmdline 名稱,用于后續命名脫殼文件int fcmdline = -1;char szCmdline[64] = { 0 };char szProcName[256] = { 0 };int procid = getpid();sprintf(szCmdline, "/proc/%d/cmdline", procid);fcmdline = open(szCmdline, O_RDONLY, 0644);if (fcmdline > 0) {read(fcmdline, szProcName, 256);close(fcmdline);}// 若成功獲取到進程名if (szProcName[0]) {// 獲取當前 ArtMethod 所屬 dex 文件及其起始地址和大小const DexFile *dex_file = artmethod->GetDexFile();const uint8_t *begin_ = dex_file->Begin(); // dex 文件起始地址size_t size_ = dex_file->Size(); // dex 文件大小int size_int_ = (int) size_; // 用于命名文件// 創建保存路徑:/sdcard/fart/<process_name>/memset(dexfilepath, 0, 2000);sprintf(dexfilepath, "/sdcard/fart");mkdir(dexfilepath, 0777); // 創建 fart 目錄memset(dexfilepath, 0, 2000);sprintf(dexfilepath, "/sdcard/fart/%s", szProcName);mkdir(dexfilepath, 0777); // 創建子目錄為進程名// 拼接最終保存路徑,如:/sdcard/fart/com.xxx.xxx/123456_dexfile_execute.dexmemset(dexfilepath, 0, 2000);sprintf(dexfilepath,"/sdcard/fart/%s/%d_dexfile_execute.dex",szProcName, size_int_);// 檢查該文件是否已經存在,若存在則跳過寫入int dexfilefp = open(dexfilepath, O_RDONLY, 0666); // 以只讀方式嘗試打開指定路徑的 dex 文件if (dexfilefp > 0) {close(dexfilefp); // 已存在,關閉文件dexfilefp = 0;} else {// 不存在則創建并寫入 dex 內容dexfilefp = open(dexfilepath, O_CREAT | O_RDWR, 0666);if (dexfilefp > 0) {write(dexfilefp, (void *) begin_, size_);fsync(dexfilefp); // 刷新到磁盤close(dexfilefp); // 關閉文件}}}// 釋放申請的內存if (dexfilepath != nullptr) {free(dexfilepath);dexfilepath = nullptr;}
}
路徑:art/runtime/art_method.cc
這時候已經可以把 Dex 整體 dump 下來了,但是還沒有把抽空的函數修復,這個就需要 FART 中的主動調用組件來解決了。
相關文章:
-
FART 主動調用組件設計和源碼分析
-
撥云見日:安卓APP脫殼的本質以及如何快速發現ART下的脫殼點