文章目錄
虛擬機
要讓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; }
};