揭祕java虛擬機(一)虛擬機

虛擬機

要讓CPU執行一段代碼,只需將CS:IP段寄存器指向代碼入口處即可.
CS寄存器保存段地址,IP保存偏移地址

常見彙編

數據傳輸

  • mov1
//將自然數1放入eax寄存器
mov1 1 %eax
  • pop
//將棧頂數據彈出至eax寄存器
pop %eax

算術運算指令

  • add
// 將自然數3與eax寄存器中的數累加,將結果存儲進eax
add 3 , %eax
  • inc
// 將ebx寄存器中數增1
inc %ebx

邏輯運算指令

  • sh1
//將eax中的數左移一個二進位
sh1 %eax,1
  • and
//對a1寄存器中數和操作數進行與操作
and a1,00111011B

串指令

程序轉移指令

JVM指令

數據交換

函數調用

運算指令

控制轉移

對象創建&類型轉換

方法調用

彙編

保存棧基並分配新棧

// 保存調用者(main的調用者是OS)的棧基地址
push1 %ebp
//將調用者的棧基地址指向其棧頂
mov1 %esp,%ebp
//分配棧空間,sub1表示減(因爲棧是從大向小)
sub1 $32,%esp

運行結果(64位)如下圖
在這裏插入圖片描述
後面可以發現其實棧分配的有多的,但是爲了對齊(無論32位還是64位,都需要是64字節整數倍)

初始化數據

//將數字5和3分別放入棧中,20和24表示偏移量.另外28(%esp)是預留給方法返回值
mov1 $5,20(%esp)
mov1 $3,24(%esp)

在這裏插入圖片描述

壓棧

// 藉助%eax將兩個數(被調用方法的入參)放入棧頂
mov1 24(%esp),%eax
mov1 %eax,4(%esp)
mov1 20(%esp),%eax
mov1 %eax,(%esp)

效果如下圖
在這裏插入圖片描述

調用

calladd
//將add函數返回的結果(默認在%eax中)放入自己的返回值存放處(28(%esp))
mov1 %eax,28(%esp)

返回

mov1 $0,%eax
leave
ret

eip & ebp

被調用的add代碼如下

add:
	push1 %ebp
	mov1 %esp,%ebp
	sub1 $16,%esp
	// 從main的棧頂獲取入參
	mov1 12(%ebp),%eax
	mov1 8(%ebp),%edx
	// 執行運算
	add1 %edx,%eax
	mov1 %eax,-4(%ebp)
	//返回
	mov1 -4(%ebp),%eax
	leave
	ret

運行時棧如圖所示
在這裏插入圖片描述
eip用來存放調用後返回時調用函數應該執行的指令的地址(本例中是calladd下面一條指令的地址),這個是由calladd這條指令由物理機器自動完成的,這樣就知道add函數調用完之後main函數從哪兒執行
ebp是由被調用函數的第一行推入的
leave其實它相當於以下指令

mov %ebp,%esp
pop %eip

C

對於linux平臺而言,調用者函數向被調用者函數傳遞參數時,採用逆向順序壓棧,及最後一個參數第一個壓棧,而第一個參數最後一個壓棧

函數指針

兩種定義方式

  • 直接聲明

    return_type (*func_pointer)(date_type1 arg1,date_type2 arg2...data_typen argn);
    
  • 通過類型聲明

    typedef (*func_pointer_type)(date_type1 arg1,data_type2,arg2...date_typen argn);
    

    採用下面一種方式只是聲明了函數指針,如果想初始化(實例化)一個函數指針還需要執行下列語句

    func_pointer_type func_pointer
    

兩種調用方式

  • (*funcPointer)(參數列表)
  • funcPointer(參數列表)

採用第二種方式就和普通的函數調用一致,所有寫成第一種可以更明確

CallStub

CAST_TO_FN_PTR

按照globalDefinitions.hpp中的定義如下,這個看上去是按照cpp的語法,和書中的c有所區別

// ANSI C++ does not allow casting from one pointer type to a function pointer
// directly without at best a warning. This macro accomplishes it silently
// In every case that is present at this point the value be cast is a pointer
// to a C linkage function. In some case the type used for the cast reflects
// that linkage and a picky compiler would not complain. In other cases because
// there is no convenient place to place a typedef with extern C linkage (i.e
// a platform dependent header file) it doesn't. At this point no compiler seems
// picky enough to catch these instances (which are few). It is possible that
// using templates could fix these for all cases. This use of templates is likely
// so far from the middle of the road that it is likely to be problematic in
// many C++ compilers.
//
#define CAST_TO_FN_PTR(func_type, value) (reinterpret_cast<func_type>(value))
#define CAST_FROM_FN_PTR(new_type, func_ptr) ((new_type)((address_word)(func_ptr)))

上面的reinterpret_cast是C++的關鍵字,可以看做C++的強制轉換

call_stub

按照stubRoutines.hpp中的定義call_stub定義如下(注意這個函數沒有入參):

// Calls to Java
  typedef void (*CallStub)(
    address   link,
    intptr_t* result,
    BasicType result_type,
    Method* method,
    address   entry_point,
    intptr_t* parameters,
    int       size_of_parameters,
    TRAPS
  );
  static CallStub call_stub()                              { return CAST_TO_FN_PTR(CallStub, _call_stub_entry); }

call_stub調用的地方是javaCalls.cpp的,這個方法很長,有各種if宏判斷,這裏通過調用call_stub()得到一個需要八個參數的函數並調用
其中八個參數的含義如下:

  • link
    連接器
  • result
    函數返回值地址
  • result_type
    函數返回類型
  • mothod
    jvm內部所表示的Java方法對象
  • entry_point
    java方法例程入口
  • parameters
    java方法的入參集合
  • size_of_parameters
    入參數量
  • TRAPS
    當前線程對象
// do call
  { JavaCallWrapper link(method, receiver, result, CHECK);
    { HandleMark hm(thread);  // HandleMark used by HandleMarkCleaner

      // NOTE: if we move the computation of the result_val_address inside
      // the call to call_stub, the optimizer produces wrong code.
      intptr_t* result_val_address = (intptr_t*)(result->get_value_addr());
      intptr_t* parameter_address = args->parameters();

      StubRoutines::call_stub()(
        (address)&link,
        // (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
        result_val_address,          // see NOTE above (compiler problem)
        result_type,
        method(),
        entry_point,
        parameter_address,
        args->size_of_parameters(),
        CHECK
      );

      result = link.result();  // circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
      // Preserve oop return value across possible gc points
      if (oop_result_flag) {
        thread->set_vm_result((oop) result->get_jobject());
      }
    }
  } // Exit JavaCallWrapper (can block - potential return oop must be preserved)

link

僅僅從CallStub上來看link僅僅是一個address,但是是什麼address,根據上面代碼也可以看出來是JavaCallWrapper,這個類定義在javaCalls.hpp中,基本上算是一個實體類,具體代碼如下:

// A JavaCallWrapper is constructed before each JavaCall and destructed after the call.
// Its purpose is to allocate/deallocate a new handle block and to save/restore the last
// Java fp/sp. A pointer to the JavaCallWrapper is stored on the stack.
class JavaCallWrapper: StackObj {
  friend class VMStructs;
 private:
  JavaThread*      _thread;      // the thread to which this call belongs
  JNIHandleBlock* _handles;      // the saved handle block
  Method*         _callee_method;//to be able to collect arguments if entry frame is top frame
  oop              _receiver;    // the receiver of the call (if a non-static call)
  JavaFrameAnchor  _anchor;      // last thread anchor state that we must restore
  JavaValue*       _result;      // result value
 public:
  // Construction/destruction
   JavaCallWrapper(const methodHandle& callee_method, Handle receiver, JavaValue* result, TRAPS);
  ~JavaCallWrapper();
  // Accessors
  JavaThread*      thread() const           { return _thread; }
  JNIHandleBlock*  handles() const          { return _handles; }
  JavaFrameAnchor* anchor(void)             { return &_anchor; }
  JavaValue*       result() const           { return _result; }
  // GC support
  Method*          callee_method()          { return _callee_method; }
  oop              receiver()               { return _receiver; }
  void             oops_do(OopClosure* f);
  bool             is_first_frame() const   { return _anchor.last_Java_sp() == NULL; }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章