FART 自動化脫殼框架簡介與脫殼點的選擇

版權歸作者所有,如有轉發,請注明文章出處:https://cyrus-studio.github.io/blog/

FART簡介

ART 環境下基于主動調用的自動化脫殼方案,可以解決函數抽取殼。

關于函數抽取殼的實現原理可以參考:基于 art 下的類加載機制,實現函數抽取殼

FART 的作用就是所有這些被抽空的函數的還原和修復,把加固的 dex 整體 dump 下來。

項目地址:https://github.com/hanbinglengyue/FART

FART 框架

word/media/image1.png

脫殼組件:將內存中的 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 的編譯驅動會對函數逐個編譯

word/media/image2.png
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 模式

word/media/image3.png
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 方法開始

word/media/image4.png
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 字節碼等信息。

word/media/image5.png

在 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下的脫殼點

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/81687.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/81687.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/81687.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

卷積神經網絡進階:轉置卷積與棋盤效應詳解

【內容摘要】 本文深入解析卷積神經網絡中的轉置卷積&#xff08;反卷積&#xff09;技術&#xff0c;重點闡述標準卷積與轉置卷積的計算過程、轉置卷積的上采樣作用&#xff0c;以及其常見問題——棋盤效應的產生原因與解決方法&#xff0c;為圖像分割、超分辨率等任務提供理論…

Redis進階知識

Redis 1.事務2. 主從復制2.1 如何啟動多個Redis服務器2.2 監控主從節點的狀態2.3 斷開主從復制關系2.4 額外注意2.5拓撲結構2.6 復制過程2.6.1 數據同步 3.哨兵選舉原理注意事項 4.集群4.1 數據分片算法4.2 故障檢測 5. 緩存5.1 緩存問題 6. 分布式鎖 1.事務 Redis的事務只能保…

SDC命令詳解:使用get_libs命令進行查詢

相關閱讀 SDC命令詳解https://blog.csdn.net/weixin_45791458/category_12931432.html?spm1001.2014.3001.5482 get_libs命令用于創建一個庫對象集合&#xff0c;關于設計對象和集合的更詳細介紹&#xff0c;可以參考下面的博客。需要注意的是&#xff0c;在有些工具中還存在…

idea2024 不知道安裝了什么插件,界面都是中文的了,不習慣,怎么修改各個選項改回英文

如果你的 IntelliJ IDEA 2024 突然變成中文界面&#xff0c;很可能是安裝了中文語言包插件&#xff08;如 “Chinese (Simplified) Language Pack”&#xff09;。以下是 徹底恢復英文界面 的方法&#xff1a; 方法 1&#xff1a;直接卸載中文插件&#xff08;推薦&#xff09;…

物流項目第二期(用戶端登錄與雙token三驗證)

第一期內容&#xff1a; 物流項目第一期&#xff08;登錄業務&#xff09;-CSDN博客 用戶端登錄 實現分析 登錄功能 Data public class UserLoginRequestVO {ApiModelProperty("登錄臨時憑證")private String code;ApiModelProperty("手機號臨時憑證"…

精準掌控張力動態,重構卷對卷工藝設計

一、MapleSim Web Handling Library仿真和虛擬調試解決方案 在柔性材料加工領域&#xff0c;卷對卷&#xff08;Roll-to-Roll&#xff09;工藝的效率與質量直接決定了產品競爭力。如何在高動態生產場景中實現張力穩定、減少斷裂風險、優化加工速度&#xff0c;是行業長期面臨的…

Voxblox算法

文章目錄 1. 算法簡介2. 由 TSDF 構建 ESDF 的方法2.1. 論文解讀2.2. 偽代碼實現 1. 算法簡介 Voxblox 算法出現于文獻《Voxblox: Incremental 3D Euclidean Signed Distance Fields for On-Board MAV Planning》&#xff0c;PDF 鏈接&#xff1a;https://arxiv.org/pdf/1611.…

計算機圖形學基礎--Games101筆記(一)數學基礎與光柵化

文章目錄 數學基礎向量插值三角形插值雙線性插值 平面定義法線-點表示 第一部分&#xff1a;光柵化坐標變換二維變換3D變換視圖變換&#xff08;MVP&#xff09;投影變換 光柵化采樣抗鋸齒&#xff08;反走樣&#xff09;可見性&#xff08;遮擋&#xff09; 著色與紋理Blinn-P…

@RequestParam 和 @RequestBody、HttpServletrequest 與HttpServletResponse

在Java Web開發中&#xff0c;RequestParam、RequestBody、HttpServletRequest 和 HttpServletResponse 是常用的組件&#xff0c;它們用于處理HTTP請求和響應。下面分別介紹它們的使用場景和使用方法&#xff1a; 1. RequestParam RequestParam 是Spring MVC框架中的注解&am…

【硬核數學】2. AI如何“學習”?微積分揭秘模型優化的奧秘《從零構建機器學習、深度學習到LLM的數學認知》

在上一篇中&#xff0c;我們探索了線性代數如何幫助AI表示數據&#xff08;向量、矩陣&#xff09;和變換數據&#xff08;矩陣乘法&#xff09;。但AI的魅力遠不止于此&#xff0c;它最核心的能力是“學習”——從數據中自動調整自身&#xff0c;以做出越來越準確的預測或決策…

10.15 LangChain v0.3重磅升級:Tool Calling技術顛覆大模型工具調用,效率飆升300%!

LangChain v0.3 技術生態與未來發展:支持 Tool Calling 的大模型 關鍵詞:LangChain Tool Calling, 大模型工具調用, @tool 裝飾器, ToolMessage 管理, Few-shot Prompting 1. Tool Calling 的技術革新 LangChain v0.3 的工具調用(Tool Calling)功能標志著大模型應用開發進…

[架構之美]從PDMan一鍵生成數據庫設計文檔:Word導出全流程詳解(二十)

[架構之美]從PDMan一鍵生成數據庫設計文檔&#xff1a;Word導出全流程詳解&#xff08;二十&#xff09; 一、痛點 你是否經歷過這些場景&#xff1f; 數據庫字段頻繁變更&#xff0c;維護文檔耗時費力用Excel維護表結構&#xff0c;版本混亂難以追溯手動編寫Word文檔&#…

Image and depth from a conventional camera with a coded aperture論文閱讀

Image and depth from a conventional camera with a coded aperture 1. 研究目標與實際意義1.1 研究目標1.2 實際問題與產業意義2. 創新方法:編碼光圈設計與統計模型2.1 核心思路2.2 關鍵公式與模型架構2.2.1 圖像形成模型2.2.2 深度可區分性準則2.2.3 統計模型與優化框架2.2…

JMeter 教程:使用 HTTP 請求的參數列表發送 POST 請求(form 表單格式)

目錄 ? 教程目的 &#x1f6e0;? 準備工作 &#x1f4c4; 操作步驟 第一步&#xff1a;新建測試計劃 第二步&#xff1a;添加 HTTP 請求 第三步&#xff1a;添加參數列表&#xff08;表單參數&#xff09; 第四步&#xff1a;添加結果查看器 第五步&#xff1a;運行測…

交易所開發:構建功能完備的金融基礎設施全流程指南

交易所開發&#xff1a;構建功能完備的金融基礎設施全流程指南 ——從技術架構到合規安全的系統性解決方案 一、開發流程&#xff1a;從需求分析到運維優化 開發一款功能完備的交易所需要遵循全生命周期管理理念&#xff0c;涵蓋市場定位、技術實現、安全防護和持續迭代四大階…

【數據結構篇】排序1(插入排序與選擇排序)

注&#xff1a;本文以排升序為例 常見的排序算法&#xff1a; 目錄&#xff1a; 一 直接插入排序&#xff1a; 1.1 基本思想&#xff1a; 1.2 代碼&#xff1a; 1.3 復雜度&#xff1a; 二 希爾排序&#xff08;直接插入排序的優化&#xff09;&#xff1a; 2.1 基本思想…

Cursor日常配置指南

文章目錄 整體說明一、簡單介紹1.1、簡介1.2、功能 二、日常配置2.1、Profiles 簡介2.2、Cursor 配置2.2.1、通用設置&#xff08;General&#xff09;2.2.2、功能設置&#xff08;Features&#xff09;2.2.2.1、長上下文&#xff08;Large context&#xff09;2.2.2.2、代碼索…

客戶體驗數據使用的三種視角——旅程視角

企業收集到大量的客戶體驗數據之后&#xff0c;應該如何應用&#xff1f;有哪些主要的使用場景和分析視角呢&#xff1f;接下來&#xff0c;體驗家團隊將通過三篇文章陸續介紹體驗數據的三種應用場景&#xff0c;以幫助企業更有效地利用體驗數據進行改進。 這三個場景分別是…

大語言模型怎么進行記憶的

大語言模型怎么進行記憶的 大語言模型(LLM)本身是無狀態的,每次輸入獨立處理,但可通過以下方式實現對話記憶及長期記憶能力: 模型架構改進 顯式記憶模塊: 記憶網絡(Memory Networks) :在模型里嵌入可讀寫的記憶單元,像鍵值存儲 (Key - Value Memory)或動態記憶矩…

Spring Boot 與 RabbitMQ 的深度集成實踐(三)

高級特性實現 消息持久化 在實際的生產環境中&#xff0c;消息的可靠性是至關重要的。消息持久化是確保 RabbitMQ 在發生故障或重啟后&#xff0c;消息不會丟失的關鍵機制。它涉及到消息、隊列和交換機的持久化配置。 首先&#xff0c;配置隊列持久化。在創建隊列時&#xf…