初入函數時的[ESP_原] 為此函數之返回地址
PUSH EBP;
=> 這時 ESP = ESP_原 - 4 且[ESP] = EBP_原
MOV EBP, ESP;
=> 這時 [EBP] = EBP原 = [ESP]= [ESP_原 -4]
//這例子中沒有
SUB ESP, 區域變量總大小;
=> 這時 ESP = ESP_原 - 4 - 區域變量總大小,這樣做的目地是若等等還要再操作ESP值時(如調用函數), 不會洗到區域變量值
(PUSH EXX)*n;
=> 把等下會用到通用寄存器之目前值都存起來 這時 ESP = ESP_原 - 4 - 區域變量總大小- n*4
這例子中 等等會用到 ECX, EBX, 所以把它門的當前值推入ESP棧裡
EAX雖然也有用到 但EAX是用來傳送函數返回值的, 所以不用存入棧裡了
:
:
:
:
(POP EXX)*n;
=> 恢復通用寄存器的值, 這時 ESP = ESP_原 - 4 - 區域變量總大小
這例子要恢復EBX, ECX
//這例子中沒有
ADD ESP, 區域變量總大小;
=> 這時 ESP = ESP_原 - 4, 所以這行也可以用 MOV, ESP,EBP;代替
POP EBP;
=>
[ESP_原 - 4] 的值是EBP_原,所這動作後, ESP = ESP_原, 然後EBP值也恢復了
RET;
=> 依[ESP_原]的值, 返回調用此函數之所在
以上結構清處了 接下來講解傳入的參數值在哪裡
PUSH EBP;
MOV EBP, ESP;
這時的 [EBP] = EBP原 = [ESP] = [ESP_原 - 4]
而[ESP_原]是要返回的EIP值
依調用慣例(也很自然可以推論), [ESP_原 + 4] 是 a(第一個引數), [ESP_原 +8]是 b(第二個引數)...以此類推
所以 [EBP + 8] 的值是a ( 注意[EBP + 8]是佔用了 ESP + 8 ~ ESP + 11格的位置) , [EBP +12]是b
這樣就看的懂下面這幾行了
MOV EAX, DWORD PTR [EBP + 8];
// EAX = a
MOV EBX,DWORD PTR [EBP + 12];
// EBX = b
SUB EAX,EBX; // EAX -= EBX
MOV ECX,DWORD PTR [EBP + 16];
//ECX = pOut
MOV DWORDPTR [ECX], EAX;
//[ECX] =EAX
之後代碼就沒在動到 EAX值了 而EAX值確實是計算結果 所以就直接RET
而若要用VC內聯匯編 (強烈建議不要與調用此函數的C/C++代碼放在同一個檔案裡, 不然編譯器會優化掉造成運行時或結果出問題)
前面的
PUSH EBP;
MOV EBP, ESP;
以及
POP EBP;
RET
編譯器會幫忙做掉
所以不需要也
不可以添加這些代碼
當然也可以要求編譯器不要自動添加, 使用
__declspec( naked ) 關鍵字於函數之前, 就可要求編譯器不要畫蛇添足 之後文章會更詳細解釋
這時內聯匯編如下
int _cdecl cdeclSub(volatile int a, volatile int b, volatile int*pOut)
{
//
*pOut= a + b;
// return a +b;
__asm
{
// PUSHEBP;
//MOV EBP,ESP;
PUSH EBX;
PUSH ECX;
MOV EAX, DWORD PTR [EBP + 8];
MOV EBX, DWORD PTR [EBP + 12];
SUB EAX,EBX;
MOV ECX,DWORD PTR [EBP + 16];
MOV DWORDPTR [ECX], EAX;
POP ECX;
POP EBX;
//POP EBP;
//RET;
}
}
以上是 cdecl
stdcall :
被調用的函數, 負責清除之參數之堆暫空間
個人在此會覺的_stdcall有點怪怪的, 傳入的參數的暫存區是由被調用者清除? 明明就是調用者把參數壓棧的啊...這樣那邊的代碼看起來不就不對稱了?
很詭異, 不過就是這樣規定
所以同函數的匯編結果如下:
PUSH EBP;
MOV EBP, ESP;
PUSH EBX;
PUSH ECX;
MOV EAX,DWORD PTR [EBP + 8];
MOV EBX,DWORD PTR [EBP + 12];
SUB EAX,EBX;
MOV ECX, DWORD PTR [EBP + 16];
MOV DWORDPTR [ECX], EAX;
POP ECX;
POP EBX;
POP EBP;
RET 12;
只有紅色不一樣
RET 12 是這樣的 :
將當前[ESP- 12 ]值當代碼地址解釋, 轉跳到該處, ,再將ESP的值減掉該數字
也就是 RET 12 等於 RET; 再SUB ESP, 12;這兩行指令
12 這值是來自於 3*4, 三個參數(既 a, b , c), 一個佔用四字節, 現在把他們全清掉, 也不用再POP XXX了
若沒有 __declspec( naked ) 關鍵字 編譯器會自動添加:
PUSH EBP;
MOV EBP, ESP;
POP EBP;
RET12;
所以以上代碼, 不能添加於內聯匯編中
另外, 可能會覺的很怪, 直接把ESP後退12格,那不是把那三個值放棄不要? 那上面的值怎辦?
是這樣的: C是以傳值/傳址做調用, 不是傳參考, 所以用來裝填函數參數的空間本來就是一次性(免洗)的
棧上的值是什麼, 在離開函數後 已完全不重要
當然 若要搞出個自家的傳參考, 也是可以, 只是這樣不符合一般性調用慣例