虚拟机解释器与bytecode对接
心头一直有个疑问,jvm虚拟是如何对接class中的字节码的?或者说在未进入
JIT优化阶段时,解释器是如何对接的?
大概阐述
hotspot通过C++代码在堆上申请一块空间,向里面填充一组指令,然后把这块空间当成一个函数,通过函数指针去调用刚生成的代码。是不是666,是不是哇超酷毙了。
关键代码
generate_call_stub
address generate_call_stub(address& return_address) { // TODO: 需要一路仔细调试 c++方法 返回无符号char
assert((int)frame::entry_frame_after_call_words == -(int)rsp_after_call_off + 1 &&
(int)frame::entry_frame_call_wrapper_offset == (int)call_wrapper_off,
"adjust this code");
StubCodeMark mark(this, "StubRoutines", "call_stub");
// address start = __ pc(); // SimonNote: 这个方法最终返回的就是这个start 宏展开 : _masm->pc()
address start = _masm-> pc(); // SimonNote: _code_section 的end 是pc() 也是此处的start
// same as in generate_catch_exception()!
const Address rsp_after_call(rbp, rsp_after_call_off * wordSize); // 这些adress都是准备工作 真正在内存区域生成汇编指令的事情是在下面的做的
const Address call_wrapper (rbp, call_wrapper_off * wordSize);
const Address result (rbp, result_off * wordSize);
const Address result_type (rbp, result_type_off * wordSize);
const Address method (rbp, method_off * wordSize);
const Address entry_point (rbp, entry_point_off * wordSize);
const Address parameters (rbp, parameters_off * wordSize);
const Address parameter_size(rbp, parameter_size_off * wordSize);
// same as in generate_catch_exception()!
const Address thread (rbp, thread_off * wordSize);
const Address r15_save(rbp, r15_off * wordSize);
const Address r14_save(rbp, r14_off * wordSize);
const Address r13_save(rbp, r13_off * wordSize);
const Address r12_save(rbp, r12_off * wordSize);
const Address rbx_save(rbp, rbx_off * wordSize);
// stub code
__ enter(); //SimonNote: macroAssembler_x86.cpp MacroAssembler::enter() push(rbp); mov(rbp, rsp); 真正在内存区域生成汇编指令!其实就是把指令等opcode写入内存区域
__ subptr(rsp, -rsp_after_call_off * wordSize);
// save register parameters
#ifndef _WIN64
__ movptr(parameters, c_rarg5); // parameters
__ movptr(entry_point, c_rarg4); // entry_point
#endif
__ movptr(method, c_rarg3); // method
__ movl(result_type, c_rarg2); // result type
__ movptr(result, c_rarg1); // result
__ movptr(call_wrapper, c_rarg0); // call wrapper
// save regs belonging to calling function
__ movptr(rbx_save, rbx);
__ movptr(r12_save, r12);
__ movptr(r13_save, r13);
__ movptr(r14_save, r14);
__ movptr(r15_save, r15);
#ifdef _WIN64
for (int i = 6; i <= 15; i++) {
__ movdqu(xmm_save(i), as_XMMRegister(i));
}
const Address rdi_save(rbp, rdi_off * wordSize);
const Address rsi_save(rbp, rsi_off * wordSize);
__ movptr(rsi_save, rsi);
__ movptr(rdi_save, rdi);
#else
const Address mxcsr_save(rbp, mxcsr_off * wordSize);
{
Label skip_ldmx;
__ stmxcsr(mxcsr_save);
__ movl(rax, mxcsr_save);
__ andl(rax, MXCSR_MASK); // Only check control and mask bits
ExternalAddress mxcsr_std(StubRoutines::addr_mxcsr_std());
__ cmp32(rax, mxcsr_std);
__ jcc(Assembler::equal, skip_ldmx);
__ ldmxcsr(mxcsr_std);
__ bind(skip_ldmx);
}
#endif
// Load up thread register
__ movptr(r15_thread, thread);
__ reinit_heapbase();
#ifdef ASSERT
// make sure we have no pending exceptions
{
Label L;
__ cmpptr(Address(r15_thread, Thread::pending_exception_offset()), (int32_t)NULL_WORD);
__ jcc(Assembler::equal, L);
__ stop("StubRoutines::call_stub: entered with pending exception");
__ bind(L);
}
#endif
// pass parameters if any
BLOCK_COMMENT("pass parameters if any");
Label parameters_done;
__ movl(c_rarg3, parameter_size);
__ testl(c_rarg3, c_rarg3);
__ jcc(Assembler::zero, parameters_done);
Label loop;
__ movptr(c_rarg2, parameters); // parameter pointer
__ movl(c_rarg1, c_rarg3); // parameter counter is in c_rarg1
__ BIND(loop);
__ movptr(rax, Address(c_rarg2, 0));// get parameter
__ addptr(c_rarg2, wordSize); // advance to next parameter
__ decrementl(c_rarg1); // decrement counter
__ push(rax); // pass parameter
__ jcc(Assembler::notZero, loop);
// call Java function
__ BIND(parameters_done);
__ movptr(rbx, method); // get Method*
__ movptr(c_rarg1, entry_point); // get entry_point
__ mov(r13, rsp); // set sender sp
BLOCK_COMMENT("call Java function");
__ call(c_rarg1);
BLOCK_COMMENT("call_stub_return_address:");
return_address = __ pc();
// store result depending on type (everything that is not
// T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE is treated as T_INT)
__ movptr(c_rarg0, result);
Label is_long, is_float, is_double, exit;
__ movl(c_rarg1, result_type);
__ cmpl(c_rarg1, T_OBJECT);
__ jcc(Assembler::equal, is_long);
__ cmpl(c_rarg1, T_LONG);
__ jcc(Assembler::equal, is_long);
__ cmpl(c_rarg1, T_FLOAT);
__ jcc(Assembler::equal, is_float);
__ cmpl(c_rarg1, T_DOUBLE);
__ jcc(Assembler::equal, is_double);
// handle T_INT case
__ movl(Address(c_rarg0, 0), rax);
__ BIND(exit);
// pop parameters
__ lea(rsp, rsp_after_call);
#ifdef ASSERT
// verify that threads correspond
{
Label L, S;
__ cmpptr(r15_thread, thread);
__ jcc(Assembler::notEqual, S);
__ get_thread(rbx);
__ cmpptr(r15_thread, rbx);
__ jcc(Assembler::equal, L);
__ bind(S);
__ jcc(Assembler::equal, L);
__ stop("StubRoutines::call_stub: threads must correspond");
__ bind(L);
}
#endif
// restore regs belonging to calling function
#ifdef _WIN64
for (int i = 15; i >= 6; i--) {
__ movdqu(as_XMMRegister(i), xmm_save(i));
}
#endif
__ movptr(r15, r15_save);
__ movptr(r14, r14_save);
__ movptr(r13, r13_save);
__ movptr(r12, r12_save);
__ movptr(rbx, rbx_save);
#ifdef _WIN64
__ movptr(rdi, rdi_save);
__ movptr(rsi, rsi_save);
#else
__ ldmxcsr(mxcsr_save);
#endif
// restore rsp
__ addptr(rsp, -rsp_after_call_off * wordSize);
// return
__ pop(rbp);
__ ret(0);
// handle return types different from T_INT
__ BIND(is_long);
__ movq(Address(c_rarg0, 0), rax);
__ jmp(exit);
__ BIND(is_float);
__ movflt(Address(c_rarg0, 0), xmm0);
__ jmp(exit);
__ BIND(is_double);
__ movdbl(Address(c_rarg0, 0), xmm0);
__ jmp(exit);
return start;
}
上述函数是在JVM启动初始化会调用这个。函数返回的是adress,其类型定义如下:
typedef unsigned char u_char;
typedef u_char* address;
关于adress
C++中是可以将对象this转换成unsigned char指针的。adress的值就是对象this的地址。示例程序参见《C++中将对象this转换成unsigned char指针》
address start = _masm-> pc(); // SimonNote: _code_section 的end 是pc() 也是此处的start
_masm
关于_masm
__是一个宏,展开后是 _masm->
_masm变量的初始化在stubCodeGenerator.cpp中的StubCodeGenerator::StubCodeGenerator函数里
StubCodeGenerator::StubCodeGenerator(CodeBuffer* code, bool print_code) {
_masm = new MacroAssembler(code);
_first_stub = _last_stub = NULL;
_print_code = print_code;
}
贴一下到初始化_masm的调用栈
StubCodeGenerator::StubCodeGenerator() at stubCodeGenerator.cpp:72 0x7ffff69d60d8
ICacheStubGenerator::ICacheStubGenerator() at icache.hpp:91 0x7ffff65da534
AbstractICache::initialize() at icache.cpp:39 0x7ffff65da324
icache_init() at icache.cpp:105 0x7ffff65da4f0
CodeCache::initialize() at codeCache.cpp:572 0x7ffff63d09f8
codeCache_init() at codeCache.cpp:582 0x7ffff63d0a34
init_globals() at init.cpp:98 0x7ffff65e93c5
Threads::create_vm() at thread.cpp:3,424 0x7ffff6a471c8
JNI_CreateJavaVM() at jni.cpp:5,166 0x7ffff66a156d
InitializeJVM() at java.c:1,145 0x7ffff7bc1b1f
JavaMain() at java.c:371 0x7ffff7bbf9e8
start_thread() at pthread_create.c:463 0x7ffff73ab6db
clone() at clone.S:95 0x7ffff78e888f
往上翻几个栈不难看出:
_masm(MacroAssembler需要的code是CodeBuffer,
CodeBuffer又需要BufferBlob
这段逻辑在icache.cpp的 void AbstractICache::initialize()中
void AbstractICache::initialize() {
// Making this stub must be FIRST use of assembler
ResourceMark rm;
BufferBlob* b = BufferBlob::create("flush_icache_stub", ICache::stub_size);
CodeBuffer c(b);
ICacheStubGenerator g(&c);
g.generate_icache_flush(&_flush_icache_stub);
// The first use of flush_icache_stub must apply it to itself.
// The StubCodeMark destructor in generate_icache_flush will
// call Assembler::flush, which in turn will call invalidate_range,
// which will in turn call the flush stub. Thus we don't need an
// explicit call to invalidate_range here. This assumption is
// checked in invalidate_range.
}
BufferBlob* b = BufferBlob::create("flush_icache_stub", ICache::stub_size);做了空间分配,下面讲;
空间分配
分配的栈
HeapBlock::allocated_space() at heap.hpp:54 0x7ffff65c26e4
CodeHeap::allocate() at heap.cpp:219 0x7ffff65c1a25
CodeCache::allocate() at codeCache.cpp:186 0x7ffff63cf5d8
BufferBlob::operator new() at codeBlob.cpp:249 0x7ffff63c8a0b
BufferBlob::create() at codeBlob.cpp:218 0x7ffff63c884e
AbstractICache::initialize() at icache.cpp:36 0x7ffff65da2eb
icache_init() at icache.cpp:105 0x7ffff65da4f0
CodeCache::initialize() at codeCache.cpp:572 0x7ffff63d09f8
codeCache_init() at codeCache.cpp:582 0x7ffff63d0a34
init_globals() at init.cpp:98 0x7ffff65e93c5
Threads::create_vm() at thread.cpp:3,424 0x7ffff6a471c8
JNI_CreateJavaVM() at jni.cpp:5,166 0x7ffff66a156d
InitializeJVM() at java.c:1,145 0x7ffff7bc1b1f
JavaMain() at java.c:371 0x7ffff7bbf9e8
start_thread() at pthread_create.c:463 0x7ffff73ab6db
clone() at clone.S:95 0x7ffff78e888f
有一段代码一开始没读懂,在同事帮助下才看明白:
BufferBlob* BufferBlob::create(const char* name, int buffer_size) {
ThreadInVMfromUnknown __tiv; // get to VM state in case we block on CodeCache_lock
BufferBlob* blob = NULL;
unsigned int size = sizeof(BufferBlob);
// align the size to CodeEntryAlignment
size = align_code_offset(size);
size += round_to(buffer_size, oopSize);
assert(name != NULL, "must provide a name");
{
MutexLockerEx mu(CodeCache_lock, Mutex::_no_safepoint_check_flag);
blob = new (size) BufferBlob(name, size); // 这行是什么意思?怎么有这种写法?
}
// Track memory usage statistic after releasing CodeCache_lock
MemoryService::track_code_cache_memory_usage();
return blob;
}
BufferBlob* blob = new (size) BufferBlob(name, size); 这种写法是placement new的写法,在《The C++ Programming Language, 4th Edition》中11.2.4 Overloading new有讲解。按我粗浅的不严谨的写法理解成:
在创建对象时,给对象指定分配在哪个内存地址上,place object of size sz at p。
不过这里的写法,跟最简单的placement new写法还是有点差异,它做了重载,最原始的写法是:
void∗ buf = reinterpret_cast<void∗>(0xF00F); // significant address
X∗ p2 = new(buf) X; // construct an X at buf;
// invokes: operator new(sizeof(X),buf)
// The ‘‘placement’’ operator new() is the simplest such allocator. It is defined in the standard header <new>:
void∗ operator new (size_t sz, void∗ p) noexcept; // place object of size sz at p
对于operator new的第一个参数size_t sz在调用时不用传送,由编译器在编译时决定并送进来,使用者只要送void* p就可以了
上面BufferBlob的代码对operator new还做了重载,(这也是通常的做法,自定义内存分配逻辑,并返回分配的指针):
void* BufferBlob::operator new(size_t s, unsigned size, bool is_critical) throw() {
void* p = CodeCache::allocate(size, is_critical);
return p;
}
字节码翻译成汇编指令
将字节码翻译成汇编指令的调用栈:
以new为例:
TemplateTable::_new() at templateTable_x86_64.cpp:3,250 0x7ffff6a39ed6
Template::generate() at templateTable.cpp:63 0x7ffff6a267c7
TemplateInterpreterGenerator::generate_and_dispatch() at templateInterpreter.cpp:530 0x7ffff6a1c392
TemplateInterpreterGenerator::set_vtos_entry_points() at templateInterpreter_x86_64.cpp:2,039 0x7ffff6a25dfc
TemplateInterpreterGenerator::set_short_entry_points() at templateInterpreter.cpp:498 0x7ffff6a1c179
TemplateInterpreterGenerator::set_entry_points() at templateInterpreter.cpp:464 0x7ffff6a1bcb3
TemplateInterpreterGenerator::set_entry_points_for_all_bytes() at templateInterpreter.cpp:421 0x7ffff6a1b974
TemplateInterpreterGenerator::generate_all() at templateInterpreter.cpp:402 0x7ffff6a1b8d6
InterpreterGenerator::InterpreterGenerator() at templateInterpreter_x86_64.cpp:2,051 0x7ffff6a25e3b
TemplateInterpreter::initialize() at templateInterpreter.cpp:52 0x7ffff6a19aab
interpreter_init() at interpreter.cpp:118 0x7ffff664c2fe
init_globals() at init.cpp:107 0x7ffff65e93ef
Threads::create_vm() at thread.cpp:3,424 0x7ffff6a471c8
JNI_CreateJavaVM() at jni.cpp:5,166 0x7ffff66a156d
InitializeJVM() at java.c:1,145 0x7ffff7bc1b1f
JavaMain() at java.c:371 0x7ffff7bbf9e8
start_thread() at pthread_create.c:463 0x7ffff73ab6db
clone() at clone.S:95 0x7ffff78e888f
这个栈很重要! 子节码都会按这个套路生成汇编指令模板。
在解释汇编指令时再用跳转指令跳转到指令模板入口处的地址。
可以通过在调试TemplateTable::_new()
代码时,按如下路线查下_masm
对应的_code_section
对应的_start
内存地址,然后通过CDT的Disassembly视图定位到相应的内存地址,并查看生成的指令,一目了然。此办法同样适用于generate_call_stub
生成模板指令的地方的调试:
MacroAssembler MacroAssembler {...}
Assembler Assembler {...}
AbstractAssembler AbstractAssembler {...}
ResourceObj ResourceObj {...}
_code_section CodeSection * 0x7ffff7fdd5e0
_start address 0x7fffe10449e0 "Pé*"
[inside hotspot] 汇编模板解释器(Template Interpreter)和字节码执行
参考
[讨论] HotSpot 解释器是怎样执行bytecode 的
[讨论] 请教:Java 字节码如何执行的
[讨论] java_main的汇编入口在哪里
运行时对代码操纵的一个小demo
JVM 模板解释器之如何根据字节码生成汇编码?
虚拟机解释器与bytecode对接的更多相关文章
- 【译】使用 Python 编写虚拟机解释器
[译]如何使用 Python 创建一个虚拟机解释器? 原文地址:Making a simple VM interpreter in Python 更新:根据大家的评论我对代码做了轻微的改动.感谢 ro ...
- 深入理解Dalvik虚拟机- 解释器的执行机制
Dalvik的指令运行是解释器+JIT的方式,解释器就是虚拟机来对Javac编译出来的字节码,做译码.运行,而不是转化成CPU的指令集.由CPU来做译码,运行.可想而知.解释器的效率是相对较低的,所以 ...
- Java虚拟机解释器与JIT编译器
一.JAVA编译相关概念 1.动态编译(dynamic compilation)指的是“在运行时进行编译”:与之相对的是事前编译(ahead-of-time compilation,简称AOT),也叫 ...
- Javacard 解释器怎样在API类库中找到源文件调用的类、方法或者静态域?
申明:本篇非本人原创,是在阅读各种论文文献之后,对论文文献的一种梳理. 主要参考文献为: ------------------------------------------------------- ...
- [译]Python编写虚拟解释器
使用Python编写虚拟机解释器 一.实验说明 1. 环境登录 无需密码自动登录,系统用户名shiyanlou,密码shiyanlou 2. 环境介绍 本实验环境采用带桌面的Ubuntu Linux环 ...
- Android(java)学习笔记159:Dalivk虚拟机的初始化过程
1.初始化下面系统函数(调用dvmStartup函数初始化所有相关的函数) 开始学习虚拟机的初始化过程,先从dvmStartup函数开始,这个函数实现所有开始虚拟机的准备工作: dvmAllocTra ...
- Dalvik虚拟机的运行过程分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8914953 在前面一篇文章中,我们分析了Dal ...
- Dalvik虚拟机JNI方法的注册过程分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8923483 在前面一文中,我们分析了Dalvi ...
- Android虚拟机器学习总结Dalvik虚拟机创建进程和线程分析
Dalvik调用一个成员函数时,虚拟机,假设发现,该成员函数是一个JNI办法,然后,它会直接跳转到其地址来运行.也就是说.JNI方法是直接在本地操作系统上运行的.而不是由Dalvik虚拟机解释器运行. ...
随机推荐
- Redis的字符串底层是啥?为了速度和安全做了啥?
面试场景 面试官:Redis有哪些数据类型? 我:String,List,set,zset,hash 面试官:没了? 我:哦哦哦,还有HyperLogLog,bitMap,GeoHash,BloomF ...
- 从零开始一起学Blazor WebAssembly 开发(4)
登录模块基本完成了,登录主要用了以下几个点: 1.后端采用的Abp Vnext 框架,这个框架自带的IdentityServer4用户角色权限控制,这个框架登录研究了好一阵子,有几个坑这里说下: 1) ...
- 【requests库】七个主要方法
本文主要介绍requests库访问http的七个主要方法:get.head.post.put.patch.delete. requests.get()方法 get方法用于获取指定url的HTML网页, ...
- 支持向量机SMO算法实现(注释详细)
一:SVM算法 (一)见西瓜书及笔记 (二)统计学习方法及笔记 (三)推文https://zhuanlan.zhihu.com/p/34924821 (四)推文 支持向量机原理(一) 线性支持向量机 ...
- SQL 更新删除
-- 插入数据 INSERT INTO [ Salary ] VALUES(25451,4545,45 ) INSERT INTO [ Salary ] (编号,收入,支出) VALUES(25451 ...
- etcd3.0集群安装
etcd 是一个分布式一致性键值存储,用于共享配置和服务发现,Go编写,并使用 Raft 一致性算法来管理高可用复制日志. 特性 简单:支持curl方式的用户API(http+json)安全:可选ss ...
- VMware虚拟机黑屏解决
1.管理员身份运行cmd(右键->以管理员身份运行) 2.修复LSP,输入以下命令然后回车 netsh winsock reset 3.重启电脑即可
- 【扩展推荐】Intervention/image 图片处理
Intervention/image 是为 Laravel 定制的图片处理工具, 它提供了一套易于表达的方式来创建.编辑图片. 一.环境要求 二.安装及配置 下载地址:https://packagis ...
- FPAG_Microblaze_PWM_定时器
由于Xilinx底层库的定时器没有PWM例程,调试过程中费了不少劲.生产PWM需要两个定时器同时工作,一个控制频率,一个控制占空比,位数可通过硬件设置. #include "xtmrctr_ ...
- HTML <hr> 标签
高佣联盟 www.cgewang.com HTML <hr> 标签 实例 当内容的主题发生变化时,使用 <hr> 标签进行分隔: <h1>HTML</h1&g ...