20:C語言與匯編
20.1:調(diào)用約定之匯編
x86調(diào)用約定:
- cdecl:參數(shù)從右往左依次入棧熬尺,調(diào)用者棧平衡(C語言缺省的調(diào)用約定,支持可變參數(shù))
- stdcall:參數(shù)從右往左依次入棧圆存,被調(diào)用者棧平衡
-
fastcall:前兩個參數(shù)ecx冤留、edx拓春,后面參數(shù)從右往左依次入棧,被調(diào)用者棧平衡(x86)
當調(diào)用fun函數(shù)開始時甩挫,三者的作用。
- eip寄存器:存儲的是CPU下次要執(zhí)行的指令的地址趁蕊。也就是調(diào)用完fun函數(shù)后,讓CPU知道應該執(zhí)行main函數(shù)中的printf("函數(shù)調(diào)用結束")語句了仔役。
- ebp寄存器:存儲的是是棧的棧底指針掷伙,通常叫棧基址骂因,這個是一開始進行fun()函數(shù)調(diào)用之前炎咖,由esp傳遞給ebp的。(在函數(shù)調(diào)用前你可以這么理解:esp存儲的是棧頂?shù)刂泛ǎ彩菞5椎刂烦伺巍#?/li>
- esp寄存器:存儲的是在調(diào)用函數(shù)fun()之后,棧的棧頂俄烁。并且始終指向棧頂绸栅。
20.1.1:cdecl
語法:
- int func (int x ,int y) //默認的C調(diào)用約定
- int __cdecl func (int x, int y)//明確指出C調(diào)用約定
//由于每次函數(shù)調(diào)用都要由編譯器產(chǎn)生還原棧的代碼,所以使用__cdecl方式編譯的程序比使用__stdcall方式編譯的程序要大很多页屠,但是__cdecl調(diào)用方式是由函數(shù)調(diào)用者負責清除棧中的函數(shù)參數(shù)粹胯,所以這種方式支持可變參數(shù),比如printf()和Windows的API wsprintf()就是__cdecl調(diào)用方式辰企。
//由于參數(shù)按照從右向左順序壓棧风纠,因此最開始的參數(shù)在最接近棧頂?shù)奈恢茫虼水敳捎貌欢▊€數(shù)參數(shù)時牢贸,第一個參數(shù)在棧中的位置肯定能知道竹观,只要不定的參數(shù)個數(shù)能夠根據(jù)第一個后者后續(xù)的明確的參數(shù)確定下來,就可以使用不定參數(shù)了潜索。
int __cdecl func2(int x, int y) {
return x+y;
}
//被調(diào)用者匯編代碼:
int __cdecl func2(int x, int y)//采用cdecl調(diào)用約定
{
0042D680 push ebp //ebp入棧)(ebp棾粼觯基址)
0042D681 mov ebp,esp //ebp指向esp(esp棧頂)
0042D683 sub esp,0C0h//esp增長0xC0個空間
0042D689 push ebx //ebx,esi,edi寄存器入棧,這三個寄存器操作系統(tǒng)沒有備份
0042D68A push esi
0042D68B push edi
0042D68C lea edi,[ebp-0C0h]// 將edi賦值為ebp-0C0h(lea取得偏移地址) ;[ebp-0C0h]即棧頂竹习,將cccccccc拷貝到棧的局部空間中誊抛;INT3指令的機器碼為CC,即CC是斷點指令(作用:一旦程序出了問題整陌,eip指針拗窃,如果eip中的指針指向棧上,開始執(zhí)行就執(zhí)行int 3h)
0042D692 mov ecx,30h//注意到30h * 4 = 0C0h
0042D697 mov eax,0CCCCCCCCh//0CCCCCCCCh為系統(tǒng)中斷int 3h;
0040042D69C rep stos dword ptr es:[edi]//拷貝開始
return x+y;
0042D69E mov eax,dword ptr [x]
0042D6A1 add eax,dword ptr [y] //eax最通用的用法放函數(shù)返回值
}
0042D6A4 pop edi//edi,esi,ebx出棧
0042D6A5 pop esi
0042D6A6 pop ebx
0042D6A7 mov esp,ebp//esp收縮空間
0042D6A9 pop ebp//ebp出棧
00000042D6AA ret//被調(diào)用者直接返回,不用恢復棧平衡泌辫,由調(diào)用者負責
//調(diào)用者代碼:
func2(1, 2);//采用cdecl調(diào)用約定随夸,參數(shù)從右往左依次入棧,調(diào)用者負責棧平衡
//0042D737 push 2//參數(shù)從右往左依次入棧甥郑,2入棧
//0042D739 push 1//參數(shù)從右往左依次入棧,1入棧
//0042D73B call func2 (42B3FCh)
//0042D740 add esp, 8 //調(diào)用者負責棧平衡荤西,esp+8澜搅,等于2個入棧參數(shù)的長度
20.1.2:stdcall
語法:
- int __stdcall func(int x, int y)
//函數(shù)名自動加前導的下劃線伍俘,后面緊跟一個@符號,其后緊跟著參數(shù)的尺寸勉躺。
int __stdcall func1(int x, int y) {
return x+y;
}
//被調(diào)用者代碼:
int __stdcall func1(int x, int y)//采用stdcall
{
42D640 push ebp
0042D641 mov ebp,esp
0042D643 sub esp,0C0h
0042D649 push ebx
0042D64A push esi
0042D64B push edi
0042D64C lea edi,[ebp-0C0h]
0042D652 mov ecx,30h
0042D657 mov eax,0CCCCCCCCh
0042D65C rep stos dword ptr es:[edi]
return x+y;
0042D65E mov eax,dword ptr [x]
0042D661 add eax,dword ptr [y]
}
0042D664 pop edi
0042D665 pop esi
0042D666 pop ebx
0042D667 mov esp,ebp //ebp(調(diào)用前的棧頂)放入esp中癌瘾,然后出棧,恢復老ebp
0042D669 pop ebp
0042D66A ret 8 //被調(diào)用者負責棧平衡饵溅,ret 8,esp += 8;
//調(diào)用者代碼
func1(1, 2); //采用stdcall,參數(shù)從右往左依次入棧,被調(diào)用者負責棧平衡
//0042D72E push 2 //參數(shù)從右往左依次入棧,2入棧
//0042D730 push 1 //參數(shù)從右往左依次入棧换途,1入棧
//0042D732 call func1 (42B6F4h)
20.1.3:fastcall
語法:
- int fastcall func (int x, int y)
//函數(shù)名自動加前導的下劃線路呜,后面緊跟一個@符號,其后緊跟著參數(shù)的尺寸轻掩。以fastcall聲明執(zhí)行的函數(shù)幸乒,具有較快的執(zhí)行速度,因為部分參數(shù)通過寄存器來進行傳遞的唇牧。
int __fastcall func3(int x, int y, int z) {
return x+y+z;
}
//被調(diào)用者匯編代碼:
int __fastcall func3(int x, int y, int z)//采用fastcall調(diào)用約定
{
0042D6C0 push ebp
0042D6C1 mov ebp,esp
0042D6C3 sub esp,0D8h
0042D6C9 push ebx
0042D6CA push esi
0042D6CB push edi
0042D6CC push ecx //存放參數(shù)入棧
0042D6CD lea edi,[ebp-0D8h]
0042D6D3 mov ecx,36h
0042D6D8 mov eax,0CCCCCCCCh
0042D6DD rep stos dword ptr es:[edi]
0042D6DF pop ecx
0042D6E0 mov dword ptr [ebp-14h],edx //前2個參數(shù)放在了ecx和edx中
0040042D6E3 mov dword ptr [ebp-8],ecx//前2個參數(shù)放在了ecx和edx中
return x+y+z;
0042D6E6 mov eax,dword ptr [x]
0042D6E9 add eax,dword ptr [y]
0042D6EC add eax,dword ptr [z]
}
0042D6EF pop edi
0042D6F0 pop esi
0042D6F1 pop ebx
0042D6F2 mov esp,ebp
0042D6F4 pop ebp
0040042D6F5 ret 4 //第3個參數(shù)占4個字節(jié)罕扎,從棧上傳遞,所以棧平衡是彈出4個字節(jié)
//調(diào)用者代碼:
func3(1, 2, 3);//采用fastcall丐重,前2個參數(shù)依次放入ecx和edx寄存器腔召,剩余參數(shù)從右往左依次入棧,被調(diào)用者負責棧平衡
//0042D743 push 3 //剩余參數(shù)從右往左依次入棧扮惦,3入棧
//0042D745 mov edx,2 //前2個參數(shù)臀蛛,分別送往ecx和edx寄存器,2入edx
//0042D74A mov ecx,1 //前2個參數(shù)径缅,分別送往ecx和edx寄存器掺栅,1入ecx
//0042D74F call func3 (42B023h)23h)
x64默認的調(diào)用約定是fastcall;fastcall在x64上調(diào)用規(guī)則:
- 一個函數(shù)在調(diào)用時纳猪,前四個參數(shù)是從左至右依次存放于RCX氧卧、RDX、R8氏堤、R9寄存器里面沙绝,剩下的參數(shù)從右至左順序入棧;棧的增長方向為從高地址到低地址鼠锈。
- 浮點前4個參數(shù)傳入XMM0闪檬、XMM1、XMM2 和 XMM3 中购笆。其他參數(shù)傳遞到堆棧中粗悯。
- 調(diào)用者負責在棧上分配32字節(jié)的“shadow space”,用于存放那四個存放調(diào)用參數(shù)的寄存器的值(亦即前四個調(diào)用參數(shù))同欠;小于64位(bit)的參數(shù)傳遞時高位并不填充零(例如只傳遞ecx)样傍,大于64位需要按照地址傳遞横缔;
- 調(diào)用者負責棧平衡;
- 被調(diào)用函數(shù)的返回值是整數(shù)時衫哥,則返回值會被存放于RAX茎刚;浮點數(shù)返回在xmm0中
- RAX,RCX撤逢,RDX膛锭,R8,R9蚊荣,R10初狰,R11是“易揮發(fā)”的,不用特別保護(所謂保護就是使用前要push備份)妇押,其余寄存器需要保護跷究。(x86下只有eax, ecx, edx是易揮發(fā)的)
- 棧需要16字節(jié)對齊,“call”指令會入棧一個8字節(jié)的返回值(注:即函數(shù)調(diào)用前原來的RIP指令寄存器的值)敲霍,這樣一來俊马,棧就對不齊了(因為RCX、RDX肩杈、R8柴我、R9四個寄存器剛好是32個字節(jié),是16字節(jié)對齊的扩然,現(xiàn)在多出來了8個字節(jié))艘儒。所以,所有非葉子結點調(diào)用的函數(shù)夫偶,都必須調(diào)整棧RSP的地址為16n+8界睁,來使棧對齊。比如sub rsp,28h
-
對于 R8~R15 寄存器兵拢,我們可以使用 r8, r8d, r8w, r8b 分別代表 r8 寄存器的64位翻斟、低32位、低16位和低8位说铃。
20.2:傳參之匯編
將實參數(shù)據(jù)傳遞給函數(shù)的方式访惜,分為:
- 傳值
- 傳指針
- C++中的傳引用
傳指針和傳引用都是針對實參來說的
20.2.1:傳值
傳值無法改變實參的值(傳值時存放在棧上的形參只是實參值的一個拷貝,無法改變實參)腻扇。
void func1(int x) {
x=1;
}
int a=0;
func1(a);
010414A5 mov eax,dword ptr [a] //把a的值放入eax中债热,然后把eax入棧
010414A8 push eax
010414A9 call func1 (10411D6h)
010414AE add esp,4
20.2.2:傳指針
傳指針:形參x是一個指向整數(shù)類型數(shù)據(jù)的地址。在函數(shù)內(nèi)部通過*運算符來引用實參幼苛。
void func2(int *x) {
*x=2;
}
int a=0;
func2(&a);
010414B1 lea eax,[a] //把a的地址傳入eax中
010414B4 push eax //把eax入棧
010414B5 call func2 (104100Ah)
010414BA add esp,4
20.2.3:C++中的傳引用
C++中的傳引用:形參部分使用的是&窒篱,而在函數(shù)內(nèi)部,可以直接把形參當做實參來使用,此時形參就是對實參的一個引用墙杯。
void func3(int &x) {
x=3;
}
int a=0;
func3(a);
010414BD lea eax,[a] //在匯編層與傳指針的方法完全一樣
010414C0 push eax
010414C1 call func3 (104102Dh)
010414C6 add esp,4
20.3:C語句之匯編
20.3.1:i++
C語句中的i++對應的匯編語句如下:
int i = 0;
00FC14D4 mov dword ptr [i],0
i++;
00FC14DB mov eax,dword ptr [i]
00FC14DE add eax,1
00FC14E1 mov dword ptr [i],eax
/*
i++在匯編層由多條匯編指令組成济锄。
在多線程環(huán)境下,i++不是多線程安全的霍转,
因為它不是原子操作,因為一個線程執(zhí)行了i++某一條匯編指令一汽,CPU的時間片就有可能用完了避消,發(fā)生了切換,而另外一個線程切換進來之后召夹,又開始從頭開始執(zhí)行i++的匯編指令岩喷,因此造成多線程不一致性。
*/
20.3.2:循環(huán)語句(for,while,do-while)與匯編
for(int i=0;i<10;i++) {…}
//for循環(huán)語句的匯編代碼:
匯編代碼:
i=0;
jmp A;
B:
i++
A:
cmp i, 0ah//0ah(十六進制) = 10(十進制)
jge OUT;
.....
jmp B;
OUT:
while(i<10) { i++; }
//While循環(huán)語句的匯編代碼:
A:
cmp i,0ah
jge OUT;
......
i++;
jmp A;
OUT:
后續(xù)此章節(jié)再補充