ART 虚拟机 — Interpreter 模式
前言
ART 虚拟机执行 Java 方法主要有两种模式:quick code 模式和 Interpreter 模式
- quick code 模式:执行 arm 汇编指令
- Interpreter 模式:由解释器解释执行 Dalvik 字节码
本篇文章就来讲一下,Interpreter 模式是如何运行的(基于 Android 8.1)
一、 Interpreter 模式
点击查看大图
上图是将断点打在 art_quick_invoke_stub 时出现的一段 backtraces,这段 backtraces 很好地描述出了 Interpreter 模式是如何运转的,以及 quick code 模式与 Interpreter 模式之间是如何切换的
1.1 art_quick_to_interpreter_bridge
从 f 19、f 18 可以看到由 quick code 模式进入 Interpreter 模式需要通过 art_quick_to_interpreter_bridge 这个 bridge,
点击查看大图
从 f 18 可以看到,artQuickToInterpreterBridge 会通过调用 interpreter::EnterInterpreterFromEntryPoint(self, code_item, shadow_frame); 来进入 Interpreter 模式,查看一下 EnterInterpreterFromEntryPoint 的定义:
JValue EnterInterpreterFromEntryPoint(Thread* self, const DexFile::CodeItem* code_item,ShadowFrame* shadow_frame) {DCHECK_EQ(self, Thread::Current());bool implicit_check = !Runtime::Current()->ExplicitStackOverflowChecks();if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEndForInterpreter(implicit_check))) {ThrowStackOverflowError(self);return JValue();}jit::Jit* jit = Runtime::Current()->GetJit();if (jit != nullptr) {jit->NotifyCompiledCodeToInterpreterTransition(self, shadow_frame->GetMethod());}return Execute(self, code_item, *shadow_frame, JValue());
}
可以看到其会调用 Execute() 函数,结合上面的 backtraces,我们可以将 Execute() 函数看作是 Interpreter 模式的起点
1.2 Execute()
art/runtime/interpreter/interpreter.cc
enum InterpreterImplKind {kSwitchImplKind, // Switch-based interpreter implementation.kMterpImplKind // Assembly interpreter
};static constexpr InterpreterImplKind kInterpreterImplKind = kMterpImplKind; // 默认使用 Mterp 类型的实现static inline JValue Execute(Thread* self,const DexFile::CodeItem* code_item,ShadowFrame& shadow_frame,JValue result_register,bool stay_in_interpreter = false) REQUIRES_SHARED(Locks::mutator_lock_) {...if (LIKELY(shadow_frame.GetDexPC() == 0)) { // Entering the method, but not via deoptimization.if (kIsDebugBuild) {self->AssertNoPendingException();}instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();ArtMethod *method = shadow_frame.GetMethod();...if (!stay_in_interpreter) {jit::Jit* jit = Runtime::Current()->GetJit();if (jit != nullptr) {jit->MethodEntered(self, shadow_frame.GetMethod());if (jit->CanInvokeCompiledCode(method)) { // 1、jit 不为 nullptr,并且 jit 编译出了对应的 quick code,那么 ArtInterpreterToCompiledCodeBridgeJValue result;// Pop the shadow frame before calling into compiled code.self->PopShadowFrame();// Calculate the offset of the first input reg. The input registers are in the high regs.// It's ok to access the code item here since JIT code will have been touched by the// interpreter and compiler already.uint16_t arg_offset = code_item->registers_size_ - code_item->ins_size_;ArtInterpreterToCompiledCodeBridge(self, nullptr, &shadow_frame, arg_offset, &result);// Push the shadow frame back as the caller will expect it.self->PushShadowFrame(&shadow_frame);return result;}}}}shadow_frame.GetMethod()->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self);// Lock counting is a special version of accessibility checks, and for simplicity and// reduction of template parameters, we gate it behind access-checks mode.ArtMethod* method = shadow_frame.GetMethod();DCHECK(!method->SkipAccessChecks() || !method->MustCountLocks());bool transaction_active = Runtime::Current()->IsActiveTransaction();if (LIKELY(method->SkipAccessChecks())) {// Enter the "without access check" interpreter.if (kInterpreterImplKind == kMterpImplKind) {if (transaction_active) {// No Mterp variant - just use the switch interpreter.return ExecuteSwitchImpl<false, true>(self, code_item, shadow_frame, result_register,false);} else if (UNLIKELY(!Runtime::Current()->IsStarted())) {...} else {while (true) {...bool returned = ExecuteMterpImpl(self, code_item, &shadow_frame, &result_register);if (returned) {return result_register;} else {// Mterp didn't like that instruction. Single-step it with the reference interpreter.result_register = ExecuteSwitchImpl<false, false>(self, code_item, shadow_frame,result_register, true);if (shadow_frame.GetDexPC() == DexFile::kDexNoIndex) {// Single-stepped a return or an exception not handled locally. Return to caller.return result_register;}}}}} else {...}} else {// Enter the "with access check" interpreter.if (kInterpreterImplKind == kMterpImplKind) {// No access check variants for Mterp. Just use the switch version.if (transaction_active) {return ExecuteSwitchImpl<true, true>(self, code_item, shadow_frame, result_register,false);} else {return ExecuteSwitchImpl<true, false>(self, code_item, shadow_frame, result_register,false);}} else {DCHECK_EQ(kInterpreterImplKind, kSwitchImplKind);if (transaction_active) {return ExecuteSwitchImpl<true, true>(self, code_item, shadow_frame, result_register,false);} else {return ExecuteSwitchImpl<true, false>(self, code_item, shadow_frame, result_register,false);}}}
}
从上面可以看到,Execute() 的作用就是:
- 如果有 jit,并且 jit 编译出了对应 method 的 quick code,那么选择通过 ArtInterpreterToCompiledCodeBridge 这个去执行对应的 quick code
- 如果上面的条件不满足,那么根据情况选择 Mterp 或者 Switch 类型的解释器实现来解释执行对应的 Dalvik 字节码
因为默认使用 Mterp 类型的 Interpreter 实现,所以大多数情况下会调用 ExecuteMterpImpl() 函数来解释执行 Dalvik 字节码,下面来重点看一下 ExecuteMterpImpl() 的实现
1.3 ExecuteMterpImpl
art/runtime/interpreter/mterp/out/mterp_arm64.S
/* During bringup, we'll use the shadow frame model instead of xFP */
/* single-purpose registers, given names for clarity */
#define xPC x20
#define xFP x21
#define xSELF x22
#define xINST x23
#define wINST w23
#define xIBASE x24
#define xREFS x25
#define wPROFILE w26
#define xPROFILE x26
#define ip x16
#define ip2 x17.macro EXPORT_PCstr xPC, [xFP, #OFF_FP_DEX_PC_PTR]
.endm.macro FETCH_INSTldrh wINST, [xPC]
.endm.macro GOTO_OPCODE regadd \reg, xIBASE, \reg, lsl #7br \reg
.endm.macro GET_INST_OPCODE regand \reg, xINST, #255
.endm/** Interpreter entry point.* On entry:* x0 Thread* self/* x1 code_item* x2 ShadowFrame* x3 JValue* result_register**/.global ExecuteMterpImpl.type ExecuteMterpImpl, %function.balign 16ExecuteMterpImpl:.cfi_startprocSAVE_TWO_REGS_INCREASE_FRAME xPROFILE, x27, 80SAVE_TWO_REGS xIBASE, xREFS, 16SAVE_TWO_REGS xSELF, xINST, 32SAVE_TWO_REGS xPC, xFP, 48SAVE_TWO_REGS fp, lr, 64add fp, sp, #64/* Remember the return register */str x3, [x2, #SHADOWFRAME_RESULT_REGISTER_OFFSET]/* Remember the code_item */str x1, [x2, #SHADOWFRAME_CODE_ITEM_OFFSET]/* set up "named" registers */mov xSELF, x0ldr w0, [x2, #SHADOWFRAME_NUMBER_OF_VREGS_OFFSET]add xFP, x2, #SHADOWFRAME_VREGS_OFFSET // point to vregs.add xREFS, xFP, w0, lsl #2 // point to reference array in shadow frameldr w0, [x2, #SHADOWFRAME_DEX_PC_OFFSET] // Get starting dex_pc.add xPC, x1, #CODEITEM_INSNS_OFFSET // Point to base of insns[]add xPC, xPC, w0, lsl #1 // Create direct pointer to 1st dex opcodeEXPORT_PC/* Starting ibase */ldr xIBASE, [xSELF, #THREAD_CURRENT_IBASE_OFFSET]/* Set up for backwards branches & osr profiling */ldr x0, [xFP, #OFF_FP_METHOD]add x1, xFP, #OFF_FP_SHADOWFRAMEbl MterpSetUpHotnessCountdownmov wPROFILE, w0 // Starting hotness countdown to xPROFILE/* start executing the instruction at rPC */FETCH_INST // load wINST from rPCGET_INST_OPCODE ip // extract opcode from wINSTGOTO_OPCODE ip // jump to next instruction/* NOTE: no fallthrough */
在 gdb 中查看上面这一段即为:
Dump of assembler code for function ExecuteMterpImpl:0x000000790e52e280 <+0>: stp x26, x27, [sp,#-80]!0x000000790e52e284 <+4>: stp x24, x25, [sp,#16]0x000000790e52e288 <+8>: stp x22, x23, [sp,#32]0x000000790e52e28c <+12>: stp x20, x21, [sp,#48]0x000000790e52e290 <+16>: stp x29, x30, [sp,#64]0x000000790e52e294 <+20>: add x29, sp, #0x40/* Remember the return register */0x000000790e52e298 <+24>: str x3, [x2,#16]/* Remember the code_item */0x000000790e52e29c <+28>: str x1, [x2,#32]/* set up "named" registers */0x000000790e52e2a0 <+32>: mov x22, x00x000000790e52e2a4 <+36>: ldr w0, [x2,#48]0x000000790e52e2a8 <+40>: add x21, x2, #0x3c0x000000790e52e2ac <+44>: add x25, x21, x0, uxtx #20x000000790e52e2b0 <+48>: ldr w0, [x2,#52] // w0 指向 SHADOWFRAME 的 dex_pc_0x000000790e52e2b4 <+52>: add x20, x1, #0x10 // xPC 指向 CodeItem 中 bytecode array 的起点, 即 base of insns[]0x000000790e52e2b8 <+56>: add x20, x20, x0, uxtx #1 // xPC 指向第一条 dex opcode0x000000790e52e2bc <+60>: stur x20, [x21,#-36]/* Starting ibase */0x000000790e52e2c0 <+64>: ldr x24, [x22,#1736]0x000000790e52e2c4 <+68>: ldur x0, [x21,#-52]0x000000790e52e2c8 <+72>: sub x1, x21, #0x3c0x000000790e52e2cc <+76>: bl 0x790e52e02c <MterpSetUpHotnessCountdown(art::ArtMethod*, art::ShadowFrame*)>0x000000790e52e2d0 <+80>: mov w26, w0/* start executing the instruction at rPC */0x000000790e52e2d4 <+84>: ldrh w23, [x20] // Fetch the next instruction from xPC into w230x000000790e52e2d8 <+88>: and x16, x23, #0xff // 将 instruction's opcode field 放到特殊寄存器 x16 当中0x000000790e52e2dc <+92>: add x16, x24, x16, lsl #7 0x000000790e52e2e0 <+96>: br x16 // Begin executing the opcode in x160x000000790e52e2e4 <+100>: nop
1.4
执行 opcode,每个 opcode 以 128 字节对齐,并且绝大多数 opcode 都会包含如下指令:
FETCH_ADVANCE_INST 2 // 此处的数字可以是其他的,譬如 1、3
GET_INST_OPCODE ip
GOTO_OPCODE ip
看一下 FETCH_ADVANCE_INST 的定义:
.macro FETCH_ADVANCE_INST countldrh wINST, [xPC, #((\count)*2)]!
.endm
这几条指令的作用是,移动 xPC 到下一条 instruction,并将移动后的 xPC 的值 load 到 wINST 中,然后跳转去执行 opcode
例如:
/* ------------------------------ */.balign 128
.L_op_nop: /* 0x00 */
/* File: arm64/op_nop.S */FETCH_ADVANCE_INST 1 // advance to next instr, load rINSTGET_INST_OPCODE ip // ip<- opcode from rINSTGOTO_OPCODE ip // execute it/* ------------------------------ */
这条 opcode 相当于什么都没做,然后移动 xPC,再去执行下一条 instruction;
通过上面这些分析,我们可以看出对 Dalvik 字节码解释执行的运行模式:
- 在 Mterp 解释器当中维护了一种对应关系:opcode 与实现这个 opcode 的汇编指令的对应关系
- 我们在解释执行的时候,实际上是取出一条指令,通过 opcode 找到对应的汇编实现,然后运行
- 大部分 opcode 中都会包含取出下一条 instruction、然后跳转执行的操作,形成一个循环
- 一些带有 invoke 操作的 opcode 将会开启下一个 Java 调用
例如,图1中的 f 16:
/* ------------------------------ */.balign 128
.L_op_invoke_virtual_quick: /* 0xe9 */
/* File: arm64/op_invoke_virtual_quick.S */
/* File: arm64/invoke.S *//** Generic invoke handler wrapper.*//* op vB, {vD, vE, vF, vG, vA}, class@CCCC *//* op {vCCCC..v(CCCC+AA-1)}, meth@BBBB */.extern MterpInvokeVirtualQuickEXPORT_PCmov x0, xSELFadd x1, xFP, #OFF_FP_SHADOWFRAMEmov x2, xPCmov x3, xINSTbl MterpInvokeVirtualQuickcbz w0, MterpExceptionFETCH_ADVANCE_INST 3bl MterpShouldSwitchInterpreterscbnz w0, MterpFallbackGET_INST_OPCODE ipGOTO_OPCODE ip/* ------------------------------ */
从图1中可以看到,其后会经过:
MterpInvokeVirtualQuick|_DoInvokeVirtualQuick|_DoCall|_DoCallCommon|_PerformCall|_ArtInterpreterToInterpreterBridge|_Execute
的调用栈开启下一个 method 的解释执行
1.4.2 PerformCall
art/runtime/common_dex_operations.h
inline void PerformCall(Thread* self,const DexFile::CodeItem* code_item,ArtMethod* caller_method,const size_t first_dest_reg,ShadowFrame* callee_frame,JValue* result,bool use_interpreter_entrypoint)REQUIRES_SHARED(Locks::mutator_lock_) {if (LIKELY(Runtime::Current()->IsStarted())) {if (use_interpreter_entrypoint) {interpreter::ArtInterpreterToInterpreterBridge(self, code_item, callee_frame, result);} else {interpreter::ArtInterpreterToCompiledCodeBridge(self, caller_method, callee_frame, first_dest_reg, result);}} else {interpreter::UnstartedRuntime::Invoke(self, code_item, callee_frame, result, first_dest_reg);}
}
可以看到在上面的调用栈中,在执行 PerformCall 方法时会判断 use_interpreter_entrypoint 是否为 true,从而选择是跳转到 ArtInterpreterToInterpreterBridge 进行解释执行还是通过 ArtInterpreterToCompiledCodeBridge 跳转到被调用 method 的 entry_point_from_quick_compiled_code_ 入口
1.4.3 DoCallCommon
art/runtime/interpreter/interpreter_common.cc
template <bool is_range,bool do_assignability_check>
static inline bool DoCallCommon(ArtMethod* called_method,Thread* self,ShadowFrame& shadow_frame,JValue* result,uint16_t number_of_inputs,uint32_t (&arg)[Instruction::kMaxVarArgRegs],uint32_t vregC) {bool string_init = false;...// Compute method information.const DexFile::CodeItem* code_item = called_method->GetCodeItem();// Number of registers for the callee's call frame.uint16_t num_regs;// 1、是否使用 interpreter 模式const bool use_interpreter_entrypoint = !Runtime::Current()->IsStarted() ||ClassLinker::ShouldUseInterpreterEntrypoint(called_method,called_method->GetEntryPointFromQuickCompiledCode());if (LIKELY(code_item != nullptr)) {...} else {DCHECK(called_method->IsNative() || called_method->IsProxyMethod());num_regs = number_of_inputs;}// 2、Hack for String init:...// Parameter registers go at the end of the shadow frame.DCHECK_GE(num_regs, number_of_inputs);size_t first_dest_reg = num_regs - number_of_inputs;DCHECK_NE(first_dest_reg, (size_t)-1);// 3、Allocate shadow frame on the stack.const char* old_cause = self->StartAssertNoThreadSuspension("DoCallCommon");ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =CREATE_SHADOW_FRAME(num_regs, &shadow_frame, called_method, /* dex pc */ 0);ShadowFrame* new_shadow_frame = shadow_frame_unique_ptr.get();// 4、Initialize new shadow frame by copying the registers from the callee shadow frame....// 5、PerformCallPerformCall(self,code_item,shadow_frame.GetMethod(),first_dest_reg,new_shadow_frame,result,use_interpreter_entrypoint);if (string_init && !self->IsExceptionPending()) {SetStringInitValueToAllAliases(&shadow_frame, string_init_vreg_this, *result);}return !self->IsExceptionPending();
}
可以看到在 DoCallCommon 中主要做了5件事,详细的细节暂时先不关注,主要看一下 use_interpreter_entrypoint,其是通过 ClassLinker::ShouldUseInterpreterEntrypoint 方法取得的值
1.4.4 ClassLinker::ShouldUseInterpreterEntrypoint
art/runtime/class_linker.cc
bool ClassLinker::ShouldUseInterpreterEntrypoint(ArtMethod* method, const void* quick_code) {if (UNLIKELY(method->IsNative() || method->IsProxyMethod())) {return false;}if (quick_code == nullptr) {return true;}Runtime* runtime = Runtime::Current();instrumentation::Instrumentation* instr = runtime->GetInstrumentation();if (instr->InterpretOnly()) {return true;}if (runtime->GetClassLinker()->IsQuickToInterpreterBridge(quick_code)) {// Doing this check avoids doing compiled/interpreter transitions.return true;}if (Dbg::IsForcedInterpreterNeededForCalling(Thread::Current(), method)) {// Force the use of interpreter when it is required by the debugger.return true;}if (runtime->IsJavaDebuggable()) {// For simplicity, we ignore precompiled code and go to the interpreter// assuming we don't already have jitted code.// We could look at the oat file where `quick_code` is being defined,// and check whether it's been compiled debuggable, but we decided to// only rely on the JIT for debuggable apps.jit::Jit* jit = Runtime::Current()->GetJit();return (jit == nullptr) || !jit->GetCodeCache()->ContainsPc(quick_code);}if (runtime->IsNativeDebuggable()) {DCHECK(runtime->UseJitCompilation() && runtime->GetJit()->JitAtFirstUse());// If we are doing native debugging, ignore application's AOT code,// since we want to JIT it (at first use) with extra stackmaps for native// debugging. We keep however all AOT code from the boot image,// since the JIT-at-first-use is blocking and would result in non-negligible// startup performance impact.return !runtime->GetHeap()->IsInBootImageOatFile(quick_code);}return false;
}
可以看到上面每个判断条件都会作为是否使用 Interpreter 模式的一个依据,我们主要关注一下下面几个条件:
- quick_code == nullptr
- instr->InterpretOnly()
- IsQuickToInterpreterBridge(quick_code)
- ……
当上面这几个条件有一个满足时,ShouldUseInterpreterEntrypoint 就会返回 true,使用 Interpreter 模式
二、总结
Interpreter 模式的运行流程如下所示:
ART 虚拟机 — Interpreter 模式相关推荐
- Java方法在art虚拟机中的执行
前言 ART 虚拟机执行 Java 方法主要有两种模式:quick code 模式和 Interpreter 模式 quick code 模式:执行 arm 汇编指令 Interpreter 模式:由 ...
- Android ART虚拟机执行引擎-Interpreter(八)
ART虚拟机是一个Interpreter+JIT+AOT的共存环境. ART虚拟机中解释器的实现源码在art/runtime/interpreter中,其中与平台相关的汇编代码保存在目录art/run ...
- AOSP 8.0 系统启动之四ART虚拟机启动(一)
目录 前言 一.创建虚拟机 1.1 JniInvocation.Init 1.2 startVm 1.2.1 JNI_CreateJavaVM 1.2.2 Runtime::Create 1.2.3 ...
- android art 远程控制,IT之家学院:认识Android中的Dalvik与ART虚拟机
又是一年高考时啊,在文章开始之前,IT之家先祝各位高考考生金榜题名~ 每到这个时候,小编就会想起自己的高三时光和高考经历,那段时光真是让人难忘.提起高三生活,可能很多同学都会想到桌子上堆得厚厚的书,黑 ...
- android dalvik虚拟机的作用,IT之家学院:认识Android中的Dalvik与ART虚拟机
又是一年高考时啊,在文章开始之前,IT之家先祝各位高考考生金榜题名~ 每到这个时候,小编就会想起自己的高三时光和高考经历,那段时光真是让人难忘.提起高三生活,可能很多同学都会想到桌子上堆得厚厚的书,黑 ...
- JVM(JAVA虚拟机)、DVM(Dalvik虚拟机)和ART虚拟机
一.什么是DVM,和JVM有什么不同? JVM是Java Virtual Machine,而DVM就是Dalvik Virtual Machine,是安卓中使用的虚拟机,所有安卓程序都运行在安卓系统进 ...
- 【Android 逆向】ART 脱壳 ( DexClassLoader 脱壳 | ART 虚拟机下 DexClassLoader 类加载器脱壳点总结 )
文章目录 一.ART 虚拟机下 DexClassLoader 类加载器脱壳点总结 1.file_magic.cc#OpenAndReadMagic 函数 2.dex_file.cc#DexFile:: ...
- 【Java 虚拟机原理】动态字节码技术 | Dalvik ART 虚拟机 | Android 字节码打包过程
文章目录 一.动态字节码技术 二.Dalvik & ART 虚拟机 三.Android 字节码打包过程 总结 一.动态字节码技术 动态字节码技术 就是在 运行时 , 动态修改 Class 字节 ...
- 设计模式--解析器(Interpreter)模式
模式定义 给定一个语言,定义它的文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子 类图 要点总结 Interpreter模式的应用场合是Interpreter模式应用中的难点, ...
- [转]虚拟机网络模式简介
虚拟机网络模式 全文转自 http://blog.csdn.net/youxin2012/article/details/17231149 无论是vmware,virtual box,virtual ...
最新文章
- 2022-2028年中国液体燃料行业市场研究及前瞻分析报告
- Linux的简单介绍.
- 对10个元素进行快速排序,在最好情况下,元素间的比较次数为( )次。
- OpenSSL--Window生成证书实战
- Gradle Introduction
- Javaweb MVC设计模式、Modle发展史、项目分层和三层架构
- 3- 基于代理 Dao 实现 CRUD 操作
- Tmk吃汤饭(模拟)
- 董明珠:格力绝不裁员;腾讯缺席首批游戏版号;iPhone XS Max 口袋自燃 | 极客头条...
- 程序员为什么热衷造轮子?
- ShellCode欺骗的艺术!
- 手机模拟门禁卡 — 加密门禁卡模拟教程
- QML 编译release 报错: qmlcache_loader.cpp:-1: error: undefined reference to `__imp__ZN11QQmlPrivate13qml
- 全球光纤接头闭合器(FOSC)收入预计2028年达到42.159亿美元
- 03-12306验证码文字 识别
- 求职面试时,如何从面试官话语中揣测是否被录用?
- 家庭局域网网站服务器,1000元打造家庭局域网
- 基本面分析中必须了解的88条避雷常识
- 2020年度总结 | 葡萄城软件开发技术回顾
- 如何在Linux中轻松隐藏文件和文件夹
热门文章
- 自监督学习(四)Joint Unsupervised Learning of Deep Representations and Image Clusters
- matlab 从字符串里面提取出数字
- 数据分析入门系列教程-SVM实战
- 2019电商数据分析师实战项目教程 电商数据分析报告 电商运营数据分析 电商数据分析流程
- php能做定时关机吗,windows怎么定时关机?
- unity 打包APK 应用未安装
- Windows10专业版系统“本地组策略编辑器”丢失解决方案
- 跨境支付产品:现钞与现汇
- 【Windows】网线直连实现两台电脑共享文件夹
- 【真北读书】弗兰克意义三途径,让你人生的意义不漂移