Interpreter
首先分析 Interpreter 如何解釋執行 dalvik byte code,Interpreter 在 ART 7.0有 3種實現:
InterpereImpl:
enum InterpreterImplKind {
kSwitchImplKind, // Switch-based interpreter implementation.
kComputedGotoImplKind, // Computed-goto-based interpreter implementation.
kMterpImplKind // Assembly interpreter
};
template<bool do_access_check, bool transaction_active>
extern JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item,
ShadowFrame& shadow_frame, JValue result_register,
bool interpret_one_instruction);
template<bool do_access_check, bool transaction_active>
extern JValue ExecuteGotoImpl(Thread* self, const DexFile::CodeItem* code_item,
ShadowFrame& shadow_frame, JValue result_register);
// Mterp does not support transactions or access check, thus no templated versions.
extern "C" bool ExecuteMterpImpl(Thread* self, const DexFile::CodeItem* code_item,
ShadowFrame* shadow_frame, JValue* result_register);
在 7.0 上,ART 默認使用 Mterp 類型的 Interpreter 實現,在一些特殊情況也會使用 Swtich類型的 Interpreter;
在 L & M 上,ART 只有 Switch 和 Goto 的實現;印象中 Kitkat 上使用的是 GotoT;
這就決定了,dex文件中的 dalvik byte code 會使用 ExecuteMterpImpl() 這個函數來進行解釋執行;
而看其參數:code_item,shadow_frame,result_register 應該是調用它(ExecuteMterpImpl)的函數準備好的;
也就是說,不管調用java函數的是 quick code執行還是也是解釋執行,其在通過 ExecuteMterpImpl 調用一個解釋執行的函數時,
必須要先準備好上面 3 個參數;
下面看 ExecuteMterpImpl 函數的實現:
/* 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 GET_INST_OPCODE reg
and \reg, xINST, #255
.endm
.macro GOTO_OPCODE reg
add \reg, xIBASE, \reg, lsl #7
br \reg
.endm
.macro EXPORT_PC
str xPC, [xFP, #OFF_FP_DEX_PC_PTR]
.endm
.macro FETCH_INST
ldrh wINST, [xPC]
.endm
/*
* Interpreter entry point.
* On entry:
* x0 Thread* self/
* x1 code_item
* x2 ShadowFrame
* x3 JValue* result_register
*
*/
.global ExecuteMterpImpl
.type ExecuteMterpImpl, %function
.balign 16
ExecuteMterpImpl:
.cfi_startproc
stp xPROFILE, x27, [sp, #-80]! ; callee save regs
stp xIBASE, xREFS, [sp, #16]
stp xSELF, xINST, [sp, #32]
stp xPC, xFP, [sp, #48]
stp fp, lr, [sp, #64]
add fp, sp, #64
/* Remember the return register */
str x3, [x2, #SHADOWFRAME_RESULT_REGISTER_OFFSET] ; return register 記錄在shadow frame中
/* Remember the code_item */
str x1, [x2, #SHADOWFRAME_CODE_ITEM_OFFSET] ; code item 記錄在 shadow frame
/* set up "named" registers */
mov xSELF, x0 ; Thread*
ldr w0, [x2, #SHADOWFRAME_NUMBER_OF_VREGS_OFFSET] ; 這 3條指令使得 xREFS指向 shadow frame的 vregs_ 的末尾地址,要幹嘛 ?
add xFP, x2, #SHADOWFRAME_VREGS_OFFSET // point to vregs.
add xREFS, xFP, w0, lsl #2 // point to reference array in shadow frame
ldr w0, [x2, #SHADOWFRAME_DEX_PC_OFFSET] // Get starting dex_pc. ; 開始執行的第一條指令 dex pc
add xPC, x1, #CODEITEM_INSNS_OFFSET // Point to base of insns[] ; 獲取記錄在 code item中的 dalvik byte code
add xPC, xPC, w0, lsl #1 // Create direct pointer to 1st dex opcode ; 把 xPC指向第一條 opcode,但是這裏爲何要 lsl #1 ?
EXPORT_PC ; 把 xPC 保存在 shadow frame的 const uint16_t* dex_pc_ptr_; 成員中,fast interpreter使用direct dexpc,不使用offset
/* Starting ibase */
ldr xIBASE, [xSELF, #THREAD_CURRENT_IBASE_OFFSET] ; Thread::tlsPtr_.mterp_current_ibase load 到寄存器 xIBASE中;
// mterp_current_ibase是在 InitInterpreterTls()時設置的, 設置的值是 artMterpAsmInstructionStart = .L_op_nop ;實際就是 .L_op_nop這段代碼在 內存中的地址
// 後續在 GOTO_OPCODE 時,會從這個基礎地址 +off 即可獲取各個 opcode 對應在內存中的地址,跳轉過去即可
/* Set up for backwards branches & osr profiling */
ldr x0, [xFP, #OFF_FP_METHOD] ; 記錄 hotness countdown(剩餘多少次即可達到 threshold)
add x1, xFP, #OFF_FP_SHADOWFRAME
bl MterpSetUpHotnessCountdown
mov wPROFILE, w0 // Starting hotness countdown to xPROFILE
/* start executing the instruction at rPC */
FETCH_INST // load wINST from rPC ; 取指
GET_INST_OPCODE ip // extract opcode from wINST ; 驛馬
GOTO_OPCODE ip // jump to next instruction ; 跳轉到 opcode 對應的解釋器代碼
/* NOTE: no fallthrough */
.global artMterpAsmInstructionStart
.type artMterpAsmInstructionStart, %function
artMterpAsmInstructionStart = .L_op_nop ; artMterpAsmInstructionStart指向 .L_op_nop 的起始內存
.text
/* ------------------------------ */
.balign 128 ; .balign 128,這個很關鍵,每個 opcode 對應代碼段的 align 都是相同(128),方便根據 opcode 計算代碼位置進行跳轉
; 爲什麼是 128 ? 懷疑是經過對比,每個 opcode 對應的解釋器代碼的 size 都不超過 128 byte
.L_op_nop: /* 0x00 */
/* File: arm64/op_nop.S */
FETCH_ADVANCE_INST 1 // advance to next instr, load rINST
GET_INST_OPCODE ip // ip<- opcode from rINST
GOTO_OPCODE ip // execute it
接下來進行一遍實際的運行過程:
(gdb) disassemble
Dump of assembler code for function ExecuteMterpImpl:
0x0000007f8683cf80 <+0>: stp x26, x27, [sp,#-80]!
0x0000007f8683cf84 <+4>: stp x24, x25, [sp,#16]
0x0000007f8683cf88 <+8>: stp x22, x23, [sp,#32]
0x0000007f8683cf8c <+12>: stp x20, x21, [sp,#48]
0x0000007f8683cf90 <+16>: stp x29, x30, [sp,#64]
0x0000007f8683cf94 <+20>: add x29, sp, #0x40
0x0000007f8683cf98 <+24>: str x3, [x2,#16]
0x0000007f8683cf9c <+28>: str x1, [x2,#32]
0x0000007f8683cfa0 <+32>: mov x22, x0
0x0000007f8683cfa4 <+36>: ldr w0, [x2,#48]
0x0000007f8683cfa8 <+40>: add x21, x2, #0x3c
0x0000007f8683cfac <+44>: add x25, x21, x0, uxtx #2
0x0000007f8683cfb0 <+48>: ldr w0, [x2,#52] ; 從shadowframe獲取 dex pc offset到 w0
0x0000007f8683cfb4 <+52>: add x20, x1, #0x10 ; xPC(x20) 指向 code item中偏移 0x10的位置,該位置就是函數 bytecode 存放的位置
0x0000007f8683cfb8 <+56>: add x20, x20, x0, uxtx #1 ; xPC 指向 java 函數的第一個 dalvik bytecode
=> 0x0000007f8683cfbc <+60>: stur x20, [x21,#-36] ; xPC保存在 shadowframe的 dex_pc_ptr_
0x0000007f8683cfc0 <+64>: ldr x24, [x22,#1504] ;從 Thread* 中取出 mterp_current_ibase,保存到 x24,它的值是 0x0000007f8683d000
0x0000007f8683cfc4 <+68>: ldur x0, [x21,#-52] ; 取出 shadowframe中的 ArtMethod* 放到x0
(gdb) p /x $x21
$7 = 0x7ff85caa4c
(gdb) x 0x7ff85caa4c-52
0x7ff85caa18: 0x712c81f0
(gdb) art_get_method_name_by_method_id 0x712c81f0
android.os.BaseLooper.updateMessageByState "(Landroid/os/Message;Landroid/os/BaseLooper$MessageMonitorInfo;I)V"
當前正在解釋執行的 java 函數是: updateMessageByState
0x0000007f8683cfc8 <+72>: sub x1, x21, #0x3c ; shadowframe 指針放在 x1
0x0000007f8683cfcc <+76>: bl 0x7f86d3560c <MterpSetUpHotnessCountdown(art::ArtMethod*, art::ShadowFrame*)>
0x0000007f8683cfd0 <+80>: mov w26, w0 ; 記錄 countdown剩餘到 xPROFILE 寄存器
0x0000007f8683cfd4 <+84>: ldrh w23, [x20] ; 從 xPC 指向的內存中取出 1 byte 到 w23,即 opcode
0x0000007f8683cfd8 <+88>: and x16, x23, #0xff ; 把 opcode 和 0xff 做 & 計算,保存到 x16
取 4 byte 時:
(gdb) p /x $x20
$11 = 0x733f8350
(gdb) x 0x733f8350
0x733f8350: 0xbb1d0071
實際是取 1 byte:
(gdb) x /b 0x733f8350
0x733f8350: 0x71
(gdb) p /x $w23
$15 = 0x71
(gdb) p /x 0x71&0xff
$16 = 0x71
(gdb) p /x 0x0000007f8683d000+0x71*128
$18 = 0x7f86840880
即: opcode 是 0x71(71: invoke-static),對應的解釋器代碼入口在: 0x7f86840880; 這是一條 invoke-static 調用 static 函數的指令
0x0000007f8683cfdc <+92>: add x16, x24, x16, lsl #7 ; 根據 x24(mterp_current_ibase) + offset(opcode * 128)來計算這個 opcode 對應的譯碼器代碼在內存中的位置
0x0000007f8683cfe0 <+96>: br x16 ; 跳轉到 opcode 對應的譯碼器代碼,即跳轉到 0x7f86840880
0x0000007f8683cfe4 <+100>: nop
0x0000007f8683cfe8 <+104>: nop
0x0000007f8683cfec <+108>: nop
0x0000007f8683cff0 <+112>: nop
0x0000007f8683cff4 <+116>: nop
0x0000007f8683cff8 <+120>: nop
0x0000007f8683cffc <+124>: nop
[0x0000007f8683d000]<+0>: ldrh w23, [x20,#2]! ; .L_op_nop,爲何 從 <+0>開始了,是因爲該段的 align(128) 與上面的 align(16) 不同導致的 ?
0x0000007f8683d004 <+4>: and x16, x23, #0xff
0x0000007f8683d008 <+8>: add x16, x24, x16, lsl #7
0x0000007f8683d00c <+12>: br x16
0x0000007f8683d010 <+16>: nop
...
0x0000007f8683d07c <+124>: nop
0x0000007f8683d080 <+128>: lsr w1, w23, #12 ; .L_op_move
0x0000007f8683d084 <+132>: ubfx w0, w23, #8, #4
0x0000007f8683d088 <+136>: ldrh w23, [x20,#2]!
0x0000007f8683d08c <+140>: ldr w2, [x21,w1,uxtw #2]
0x0000007f8683d090 <+144>: and x16, x23, #0xff
0x0000007f8683d094 <+148>: str w2, [x21,w0,uxtw #2]
0x0000007f8683d098 <+152>: str wzr, [x25,w0,uxtw #2]
0x0000007f8683d09c <+156>: add x16, x24, x16, lsl #7
0x0000007f8683d0a0 <+160>: br x16
0x0000007f8683d0a4 <+164>: nop
...
0x0000007f8684087c <+14460>: nop
[0x0000007f86840880]<+14464>: stur x20, [x21,#-36] ; .L_op_invoke_static+0; 保存 dex_pc 到 shadowframe的dex_pc_ptr_
0x0000007f86840884 <+14468>: mov x0, x22 ; 準備參數 Thread(
0x0000007f86840888 <+14472>: sub x1, x21, #0x3c ; 準備參數 shadowframe
0x0000007f8684088c <+14476>: mov x2, x20 ; 準備參數 dex pc
0x0000007f86840890 <+14480>: mov x3, x23 ; 準備參數 dalvik byte instruction
0x0000007f86840894 <+14484>: bl 0x7f86d2cb14 < (art::Thread*, art::ShadowFrame*, uint16_t*, uint16_t)> ; 跳轉到 MterpInvokeStatic()
0x0000007f86840898 <+14488>: cbz w0, 0x7f8684d040 <MterpException> ; 如果返回值是 0,跳轉到 MterpException
0x0000007f8684089c <+14492>: ldrh w23, [x20,#6]! ; 取指下一條,並且 dex_pc+6 (因爲一條完整的invoke-static指令需要 6個byte)
所以當前準備執行的這條指令是:
(gdb) x /6b 0x733f8350
0x733f8350: 0x71 0x00 0x1d 0xbb 0x00 0x00
意思是 invoke-vitual 無參 [dex_method_idx] [參數寄存器1,2,3,4]
0x0000007f868408a0 <+14496>: bl 0x7f86d2b15c <MterpShouldSwitchInterpreters()> ; 是否切換 Interpreter
0x0000007f868408a4 <+14500>: cbnz w0, 0x7f8684d1bc <MterpFallback> ; 切換
0x0000007f868408a8 <+14504>: and x16, x23, #0xff ; 取指
0x0000007f868408ac <+14508>: add x16, x24, x16, lsl #7 ; 譯碼
0x0000007f868408b0 <+14512>: br x16 ; 跳轉到 x16 對應 opcode的解釋器代碼
0x0000007f868408b4 <+14516>: nop
0x0000007f868408b8 <+14520>: nop
..
而實際 L_op_invoke_static 是由 MterpInvokeStatic 實現的:
extern "C" size_t MterpInvokeStatic(Thread* self,
ShadowFrame* shadow_frame,
uint16_t* dex_pc_ptr,
uint16_t inst_data)
SHARED_REQUIRES(Locks::mutator_lock_) {
JValue* result_register = shadow_frame->GetResultRegister();
const Instruction* inst = Instruction::At(dex_pc_ptr); //取指
return DoInvoke<kStatic, false, false>( //執行
self, *shadow_frame, inst, inst_data, result_register);
}
template<InvokeType type, bool is_range, bool do_access_check>
static inline bool DoInvoke(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst,
uint16_t inst_data, JValue* result) {
const uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c(); // 從前面傳遞過來的 instruction 指針獲取 method_idx,(取inst[0] 16 bit)
const uint32_t vregC = (is_range) ? inst->VRegC_3rc() : inst->VRegC_35c(); // 從前面傳遞過來的 instruction 指針獲取 arg vregsiter 數量
Object* receiver = (type == kStatic) ? nullptr : shadow_frame.GetVRegReference(vregC); // 如果是static,則 this爲 nullptr
ArtMethod* sf_method = shadow_frame.GetMethod(); // 這個是當前正在執行的 method
ArtMethod* const called_method = FindMethodFromCode<type, do_access_check>( // 根據 method_idx 查找 ArtMethod(invoke-static 將要調用的函數)
method_idx, &receiver, sf_method, self);
...
if (type == kVirtual || type == kInterface) {
jit->InvokeVirtualOrInterface( // 爲何只對 kVirtual 和 kInterface記錄 ProfilingInfo ?
self, receiver, sf_method, shadow_frame.GetDexPC(), called_method);
}
jit->AddSamples(self, sf_method, 1, /*with_backedges*/false); // jit AddSamples
return DoCall<is_range, do_access_check>(called_method, self, shadow_frame, inst, inst_data, // 通過 DoCall 執行
result);
}
DoCall 又會調用 DoCallCommon(),在 DoCallCommon()中,根據 bytecode (0x733f8350: 0x71 0x00 0x1d 0xbb 0x00 0x00)進行構建 shadowframe和準備參數;
然後準備執行 static函數(0xbb1d) ,其關鍵code:
// Do the call now.
if (LIKELY(Runtime::Current()->IsStarted())) {
ArtMethod* target = new_shadow_frame->GetMethod();
if (ClassLinker::ShouldUseInterpreterEntrypoint( // 準備調用的函數如果需要解釋執行,則進入 ArtInterpreterToInterpreterBridge
target,
target->GetEntryPointFromQuickCompiledCode())) {
ArtInterpreterToInterpreterBridge(self, code_item, new_shadow_frame, result);
} else {
ArtInterpreterToCompiledCodeBridge( // 準備調用的函數如果不需要解釋執行,則進入 ArtInterpreterToCompiledCodeBridge
self, shadow_frame.GetMethod(), code_item, new_shadow_frame, result);
}
} else {
UnstartedRuntime::Invoke(self, code_item, new_shadow_frame, result, first_dest_reg);
}
在 ArtInterpreterToInterpreterBridge()中,會調用 art::interpreter::Execute() 來解釋執行:
if (LIKELY(!shadow_frame->GetMethod()->IsNative())) {
result->SetJ(Execute(self, code_item, *shadow_frame, JValue()).GetJ());
}
在 Execute()中會使用 JIT記錄 MethodEnter 記錄調用次數,判斷 jit_code_cache 中是否包含對應 ArtMethod 的 entry_point_from_quick_compiled_code_ 地址,
如果包含,說明被這個java函數被 JIT編譯過,則調用 ArtInterpreterToCompiledCodeBridge(),通過 ArtMethod::Invoke() 執行 compiledcode;
貼出 Execute() 關鍵代碼:
static inline JValue Execute(
Thread* self,
const DexFile::CodeItem* code_item,
ShadowFrame& shadow_frame,
JValue result_register,
bool stay_in_interpreter = false) SHARED_REQUIRES(Locks::mutator_lock_) {
if (jit != nullptr) {
jit->MethodEntered(self, shadow_frame.GetMethod());
if (jit->CanInvokeCompiledCode(method)) {
ArtInterpreterToCompiledCodeBridge(self, nullptr, code_item, &shadow_frame, &result);
}
}
// 三種類型的解釋器,Android 7.0 默認使用 Mterp
if (kInterpreterImplKind == kMterpImplKind) {
if (transaction_active) { // ArtTransaction 只在 CompilerDriver PreCompile InitializeClasses時 Enable;
// 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())) {
return ExecuteSwitchImpl<false, false>(self, code_item, shadow_frame, result_register,
false);
} else {
while (true) {
// Mterp does not support all instrumentation/debugging.
if (MterpShouldSwitchInterpreters() != 0) {
return ExecuteSwitchImpl<false, false>(self, code_item, shadow_frame, result_register,
false);
}
bool returned = ExecuteMterpImpl(self, code_item, &shadow_frame, &result_register); //看到了我們的關鍵函數,我們開始分析的時候,就是從這個函數入手,現在又回來了,這是一個 Interpreter 調用 Interpreter循環
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 if (kInterpreterImplKind == kSwitchImplKind) {
} else {
}
接下來,根據上面的流程畫一張圖:
對圖的說明:
1.雖然我們是從 ExecuteMterpImpl 這個函數開始分析,但實際 Interpreter開始的位置是在 art::interpreter::Execute()這個函數;
它可能是被一個解釋執行的代碼從 ArtInterpreterToInterpreterBridge → Execute() 調用過來的,
也有可能是被一個在 native code執行的代碼通過 art_quick_to_interpreter_bridge → artQuickToInterpreterBridge → interpreter::EnterInterpreterFromEntryPoint → Execute()這個調用關係調用過來的;
2.一般 invoke-XXX bytecode之後都會跟着一個 move-result 或者其變種,因爲大多數情況下,我們都是需要使用函數的返回值的;
所以圖中畫了一條 從 OPCODE 0x71(invoke-static)到 OPCODE 0x0a(move-resutl)的跳轉線;
另外在結尾,我們通過dumpoat,看一下 android.os.BaseLooper.updateMessageByState 這個函數的dalvik bytecode:
15: void android.os.BaseLooper.updateMessageByState(android.os.Message, android.os.BaseLooper$MessageMonitorInfo, int) (dex_method_idx=48127)
DEX CODE:
0x0000: 7100 1dbb 0000 | invoke-static {}, boolean android.os.AnrMonitor.canMonitorAnr() // method@47901
0x0003: 0a00 | move-result v0
0x0004: 3800 0a00 | if-eqz v0, +10
0x0006: ef10 2400 | iget-boolean-quick v0, v1, thing@36
0x0008: 3800 0600 | if-eqz v0, +6
0x000a: 2b04 2200 0000 | packed-switch v4, +34
0x000d: 7300 | return-void-no-barrier
0x000e: 7300 | return-void-no-barrier
0x000f: e920 1800 4300 | invoke-virtual-quick {v3, v4}, // vtable@24
0x0012: e920 1700 2300 | invoke-virtual-quick {v3, v2}, // vtable@23
0x0015: e812 1000 | iput-object-quick v2, v1, // offset@16
0x0017: 7120 42bb 3200 | invoke-static {v2, v3}, void android.os.AnrMonitor.startMonitor(android.os.Message, android.os.BaseLooper$MessageMonitorInfo) // method@47938
0x001a: 28f3 | goto -13
0x001b: 1200 | const/4 v0, #+0
0x001c: e810 1000 | iput-object-quick v0, v1, // offset@16
0x001e: e920 1800 4300 | invoke-virtual-quick {v3, v4}, // vtable@24
0x0021: 7120 30bb 3200 | invoke-static {v2, v3}, void android.os.AnrMonitor.finishMonitor(android.os.Message, android.os.BaseLooper$MessageMonitorInfo) // method@47920
0x0024: 7120 22bb 3200 | invoke-static {v2, v3}, void android.os.AnrMonitor.checkMsgTime(android.os.Message, android.os.BaseLooper$MessageMonitorInfo) // method@47906
0x0027: 7030 f1bb 2103 | invoke-direct {v1, v2, v3}, void android.os.BaseLooper.addMessageToHistoryIfNeed(android.os.Message, android.os.BaseLooper$MessageMonitorInfo) // method@48113
...
CODE: (code_offset=0x00000000 size_offset=0x021e2330 size=0)
NO CODE!
可以看到,確實其第一條 bytecode 就是 (7100 1dbb 0000) invoke-static,
而調用的函數 canMonitorAnr() 也沒有 Native code(這裏不在貼 oatdump了),所以仍然解釋執行,通過 ArtInterpreterToInterpreterBridge跳轉執行;
緊接着的一條指令也是 move-result v0;因爲我們需要用到返回值,所以才編譯出來這條 bytecode;
Mterp 實現的 Interpreter 解釋器至此結束。