zoukankan      html  css  js  c++  java
  • 虚拟机解释器与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 模板解释器之如何根据字节码生成汇编码?

  • 相关阅读:
    我们应该如何防范黑客的攻击? 有哪些棘手问题?
    德国网络安全公司Avira被收购,估值为1.8亿美元
    物联网会成为黑客攻击的目标,智慧城市如何才安全?
    因新型冠状病毒,笔记本电脑销售增长,人们寻求更好的设备进行远程工作
    从电脑维修工到互联网大佬,他是怎么做到的?解读郭盛华最真实的传奇生涯
    企业防御网络风险保护计划的5个步骤
    加载失败图片加样式
    请求接口无权限
    iview button根据条件 disabled可用或者不可用
    vue跨组件传值
  • 原文地址:https://www.cnblogs.com/simoncook/p/11141256.html
Copyright © 2011-2022 走看看