C语言10-C语言和汇编语言

电脑杂谈  发布时间:2020-04-04 04:12:03  来源:网络整理

c语言嵌入汇编_c语言对应汇编语句_汇编语言和c语言哪个难

x86调用约定:

cdecl: 参数从右到左堆叠在堆栈上,调用方堆栈平衡(C语言默认调用约定,支持可变参数)stdcall: 参数从右到左堆叠在堆栈上,被调用方堆栈平衡

fastcall: 前两个参数ecx和edx,后两个参数从右到左压入堆栈,被调用者堆栈余额(x86)

image.png

当调用fun函数时,这三个函数.

c语言对应汇编语句_汇编语言和c语言哪个难_c语言嵌入汇编

eip寄存器: 存储下一次要由CPU执行的指令的地址. 也就是说,在调用fun函数之后,让CPU知道应执行主函数中的printf(“函数调用结束”)语句. ebp寄存器: 存储堆栈的底部指针,通常称为堆栈基地址. 在开始fun()函数调用之前,此命令由esp传递给ebp. (您可以在函数调用之前理解这一点: esp存储了最高地址和最低地址. )Esp寄存器: 在调用fun()函数之后存储了栈顶. 并始终指向堆栈的顶部.

语法:

int func(int x,int y)//默认的C调用约定int __cdecl func(int x,int y)//明确指出C调用约定

//由于每次函数调用都要由编译器产生还原栈的代码,所以使用__cdecl方式编译的程序比使用__stdcall方式编译的程序要大很多,但是__cdecl调用方式是由函数调用者负责清除栈中的函数参数,所以这种方式支持可变参数,比如printf()和Windows的API wsprintf()就是__cdecl调用方式。
//由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来,就可以使用不定参数了。
int __cdecl func2(int x, int y) {
    return x+y;
}
//被调用者汇编代码:
int __cdecl func2(int x, int y)//采用cdecl调用约定 
{
0042D680 push ebp //ebp入栈)(ebp栈基址)
0042D681 mov ebp,esp //ebp指向esp(esp栈顶)
0042D683 sub esp,0C0h//esp增长0xC0个空间
0042D689 push ebx //ebx,esi,edi寄存器入栈,这三个寄存器操作系统没有备份
0042D68A push esi 
0042D68B push edi
0042D68C lea edi,[ebp-0C0h]// 将edi赋值为ebp-0C0h(lea取得偏移地址) ;[ebp-0C0h]即栈顶,将cccccccc拷贝到栈的局部空间中;INT3指令的机器码为CC,即CC是断点指令(作用:一旦程序出了问题,eip指针,如果eip中的指针指向栈上,开始执行就执行int 3h)
0042D692 mov ecx,30h//注意到30h * 4 = 0C0h 
0042D697 mov eax,0CCCCCCCCh//0CCCCCCCCh为系统中断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最通用的用法放函数返回值
}
0042D6A4 pop edi//edi,esi,ebx出栈
0042D6A5 pop esi
0042D6A6 pop ebx 
0042D6A7 mov esp,ebp//esp收缩空间
0042D6A9 pop ebp//ebp出栈
00000042D6AA ret//被调用者直接返回,不用恢复栈平衡,由调用者负责
//调用者代码:
func2(1, 2);//采用cdecl调用约定,参数从右往左依次入栈,调用者负责栈平衡 
//0042D737 push 2//参数从右往左依次入栈,2入栈 
//0042D739 push 1//参数从右往左依次入栈,1入栈 
//0042D73B call func2 (42B3FCh)
//0042D740 add esp, 8 //调用者负责栈平衡,esp+8,等于2个入栈参数的长度

语法:

int __stdcall函数(int x,int y)

汇编语言和c语言哪个难_c语言嵌入汇编_c语言对应汇编语句

//函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。
int __stdcall func1(int x, int y) {
    return x+y;
}
//被调用者代码:
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(调用前的栈顶)放入esp中,然后出栈,恢复老ebp
0042D669 pop ebp 
0042D66A ret 8 //被调用者负责栈平衡,ret 8,esp += 8; 
//调用者代码
func1(1, 2); //采用stdcall,参数从右往左依次入栈,被调用者负责栈平衡
//0042D72E push 2 //参数从右往左依次入栈,2入栈
//0042D730 push 1 //参数从右往左依次入栈,1入栈
//0042D732 call func1 (42B6F4h)

语法:

int fastcall函数(int x,int y)

//函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。以fastcall声明执行的函数,具有较快的执行速度,因为部分参数通过寄存器来进行传递的。
int __fastcall func3(int x, int y, int z) {
    return x+y+z;
}
//被调用者汇编代码:
int __fastcall func3(int x, int y, int z)//采用fastcall调用约定 
{
0042D6C0 push ebp 
0042D6C1 mov ebp,esp 
0042D6C3 sub esp,0D8h 
0042D6C9 push ebx 
0042D6CA push esi 
0042D6CB push edi
0042D6CC push ecx //存放参数入栈
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个参数放在了ecx和edx中
0040042D6E3 mov dword ptr [ebp-8],ecx//前2个参数放在了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个参数占4个字节,从栈上传递,所以栈平衡是弹出4个字节
//调用者代码:
func3(1, 2, 3);//采用fastcall,前2个参数依次放入ecx和edx寄存器,剩余参数从右往左依次入栈,被调用者负责栈平衡
//0042D743 push 3 //剩余参数从右往左依次入栈,3入栈
//0042D745 mov edx,2 //前2个参数,分别送往ecx和edx寄存器,2入edx
//0042D74A mov ecx,1 //前2个参数,分别送往ecx和edx寄存器,1入ecx
//0042D74F call func3 (42B023h)23h)

x64的默认调用约定是fastcall; x64上的fastcall调用规则:

调用函数时,前四个参数从左到右存储在RCX,RDX,R8,R9寄存器中,其余参数从右到左依次压入堆栈;堆栈的增长方向是从高地址到低地址. 浮点的前四个参数传递到XMM0,XMM1,XMM2和XMM3. 其他参数传递到堆栈. 调用方负责在堆栈上分配32个字节的“影子空间”,用于存储存储调用参数的四个寄存器(即前四个调用参数)的值;当传递的参数少于64位时,高位不填充零(例如,仅传递ecx)c语言对应汇编语句,并且根据地址需要传递64位以上;调用方负责堆栈平衡;当被调用函数的返回值是整数时,该返回值将存储在RAX中;浮点数以xmm0的形式返回. RAX,RCXc语言对应汇编语句,RDX,R8,R9,R10,R11是“易失性的”,不需要特殊的保护(所谓的保护是在使用前将其推回),其余寄存器需要受到保护. (在x86下,只有xax,ecx和edx易失)堆栈需要16字节对齐,并且“ call”指令将以8字节的返回值压入堆栈(注意: 原始RIP指令的值)函数调用之前的寄存器),这样,堆栈就未对齐(因为四个寄存器RCX,RDX,R8,R9只是32个字节,对齐了16个字节,现在又有8个字节). 因此,非叶节点调用的所有函数必须将堆栈的RSP地址调整为16n + 8,以对齐堆栈. 例如sub rsp,28h

c语言嵌入汇编_c语言对应汇编语句_汇编语言和c语言哪个难

对于R8〜R15寄存器,我们可以使用r8,r8d,r8w和r8b分别表示r8寄存器的64位,低32位,低16位和低8位.

image.png

将实际参数数据传递给函数的方式分为:

通过引用传递在C ++中通过引用传递

传递指针和传递引用均用于实际参数

c语言嵌入汇编_汇编语言和c语言哪个难_c语言对应汇编语句

不能通过传递值来更改实际参数值(传递值时存储在堆栈上的形式参数只是实际参数值的副本,不能更改实际参数).

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

传递指针: 形式参数x是指向整数类型数据的地址. 使用函数内的*运算符可引用实际参数.

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

在C ++中通过引用进行引用: 形式参数部分使用&,并且在函数内部,您可以直接使用形式参数作为实际参数. 在这种情况下,形式参数是对实际参数的引用.

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
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++在汇编层由多条汇编指令组成。
在多线程环境下,i++不是多线程安全的,
因为它不是原子操作,因为一个线程执行了i++某一条汇编指令,CPU的时间片就有可能用完了,发生了切换,而另外一个线程切换进来之后,又开始从头开始执行i++的汇编指令,因此造成多线程不一致性。
*/
for(int i=0;i<10;i++) {…}
//for循环语句的汇编代码:
汇编代码:
i=0;
jmp A;
B:
i++
A:
cmp i, 0ah//0ah(十六进制) = 10(十进制)
jge OUT;
.....
jmp B;
OUT:
while(i<10) { i++; }
//While循环语句的汇编代码:
A:
cmp i,0ah
jge OUT;
......
i++;
jmp A;
OUT:

按照本章添加


本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-163236-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      热点图片
      拼命载入中...