程序基础
- 三大结构
- 顺序结构
- 选择结构
- 循环结构
- 函数
选择结构
- if … else if … else
int nDay = 0;scanf("%d", &nDay);if( nDay == 1){printf("星期一");}else if(nDay==2){printf("星期二");}else if(nDay==3){printf("星期三");}
汇编版本:
fun procpush ebpmov ebp,espsub esp , 4 ; 为局部变量分配栈空间.; ebp - 4 ==> nDay的位置lea eax , [ebp-4]; eax= &nDaypush eaxpush "%d"call crt_scanf ; scanf("%d",&nDay)cmp dword ptr [ebp-4] , 1; if(nDay == 1)jne _IF_1push "星期一"call crt_printfadd esp , 4jmp _ENDIF_IF_1:cmp dword ptr [ebp-4] , 2jne _IF_2push "星期二"call crt_printfadd esp , 4jmp _ENDIF_IF_2:cmp dword ptr [ebp-4] , 3jne _IF_3push "星期三"call crt_printfadd esp , 4jmp _ENDIF_IF_3:_ENDIF:mov esp, ebppop ebpfun endp
总结
- 在C语言中, 使用
{}来分隔每个语句块 - 在汇编指令是自上而下逐条执行, 没有跳转指令是不会跑到其它位置执行代码的.
在汇编构建一个语句块的方式是:
- 在语句块前加入一条条件跳转指令
- 在语句块后加入一条无条件跳转指令
_IF_2:cmp dword ptr [ebp-4] , 3 ;语句块jne _IF_3 ;的开始push "星期三"call crt_printfadd esp , 4jmp _ENDIF ;语句块的结束
switch … case ```c int nDay=0; scanf(“%d”, &nDay);
switch( nDay ){ case 1: printf(“星期一”);break; case 2: printf(“星期二”);break; case 3: printf(“星期三”);break; }
<br />汇编版本:1. 和if的版本一样.2. 使用跳转表```assembly.386.model flat,stdcalloption casemap:noneinclude msvcrt.incincludelib msvcrt.lib.constpszD db "%d",0pszTips1 db "星期一",0dh,0ah,0pszTips2 db "星期二",0dh,0ah,0pszTips3 db "星期三",0dh,0ah,0.codefun procpush ebpmov ebp,espsub esp , 4 ; 为局部变量分配栈空间.; ebp - 4 ==> nDay的位置lea eax , [ebp-4]; eax= &nDaypush eaxpush offset pszDcall crt_scanf ; scanf("%d",&nDay)cmp dword ptr [ebp-4] , 3jg _ENDIF; nday-=1;jmp jmptab[nDay];mov eax , [ebp-4]; eax = nDaydec eax ; eax = nDay - 1; 通过使用输入的值作为下标; 从数组中索引出一个地址. 然后再跳转; 到此地址上.jmp dword ptr [jmptab + eax * 4]; 在代码中定义了数据.; DwORD jmptab[3]={ _IF_0,_IF_1,_IF_2};; 编译之后, _IF_0标签就会编译成代码的地址jmptab dd _IF_0,_IF_1,_IF_2_IF_0:; if(nDay == 1)``push offset pszTips1call crt_printfadd esp , 4jmp _ENDIF_IF_1:push offset pszTips2call crt_printfadd esp , 4jmp _ENDIF_IF_2:push offset pszTips3call crt_printfadd esp , 4jmp _ENDIF_ENDIF:mov esp, ebppop ebpretfun endpmain:call funretend main
循环结构
- while
int i=0;while( i < 100) {printf("%d\n",i);++i;}
汇编版本:
通过条件跳转指令来模拟
fun proc; 因为没有形参, 所以可以不需要开辟栈帧.; 直接使用esp来索引局部变量.sub esp , 4; 局部变量位置 => [esp]mov [esp], 0 ; i=0;;while( i < 100) {_WHILE:cmp dword ptr[esp] , 100jg _ENDWHILE ; 如果大于就结束循环;printf("%d\n",i);push dword ptr[esp]push "%d\n"call crt_printfadd esp , 8;++iinc dword ptr [esp]jmp _WHILE_ENDWHILE:add esp , 4retfun endp;
do…while
int i=0;do {printf("%d\n",i);++i;}while( i < 100) ;
fun_dowhile procsub esp , 4 ; int i;mov dword ptr [esp] , 0_DOWHILE:push dword ptr [esp];push "%d\n"call crt_printfadd esp , 8inc dword ptr [esp];cmp dword ptr [esp] , 100jl _DOWHILEfun_dowhile endp
for
for( int i = 0; i<100;++i){printf("%d\n",i);}
for循环的结构和while循环一样.
fun proc; 因为没有形参, 所以可以不需要开辟栈帧.; 直接使用esp来索引局部变量.sub esp , 4; 局部变量位置 => [esp]mov [esp], 0 ; i=0;;while( i < 100) {_WHILE:cmp dword ptr[esp] , 100jg _ENDWHILE ; 如果大于就结束循环;printf("%d\n",i);push dword ptr[esp]push "%d\n"call crt_printfadd esp , 8;++iinc dword ptr [esp]jmp _WHILE_ENDWHILE:add esp , 4retfun endp;
函数
调用约定
| 调用预定 | 传参方式 | 平衡栈方式 |
|---|---|---|
| cdecl(C调用约定) | 从右往左 | 在函数外平衡(调用者平衡),通过add esp,xx的方式平衡 |
| stdcall标准调用约定 | 从右往左 | 在函数内平衡(被调用者平衡),通过ret xx的方式平衡. |
| fastcall(快速调用约定) | 前两个参依次使用ecx,edx来传递, 后续函数通过栈传递(从右往左依次入栈) | 在函数内平衡(被调用者平衡),通过ret xx的方式平衡. |
| thiscall(对象调用约定) | 通过ecx来保存this指针.参数从右往左依次入栈 | 在函数内平衡(被调用者平衡),通过ret xx的方式平衡. |
总结
- 调用约定了制约在代码中传参的方式和平衡栈的方式
- 在汇编中调用函数前需要搞清楚, 这个函数使用什么调用约定, 否则参数就无法正确传递.
函数调用总结
- 参数是通过栈来传递的. 有时候是直接push到栈中, 有时候也可以先sub esp拉高栈顶, 再使用mov [esp]的方式将实参赋值到栈中, 总之能够将实参放入到栈中合适的位置就可以了.
- esp栈顶高地址的位置一般都是被使用过的, 不应该随意修改. 而esp低地址的位置一般是没有被使用.可以用来保存新的数据.
- 函数调用完毕之后, 需要保证栈的平衡.
- 函数的返回值一般是约定使用eax来保存.
函数栈帧
实参通过栈传递之后, 在函数内部怎么定位? ```assembly ; 定义一个函数 ; void fun(int n1, int n2, int n3) fun proc push ebp ; 因为要使用ebp来保存当前栈顶的值, 因此需要先将寄存器的值备份起来. mov ebp, esp; 将栈顶位置保存到ebp中, 这样即使栈顶发生了浮动, 也不会影响通过ebp+固定偏移的方式去定位到栈内的形参了.
; 离开函数前, 需要恢复在函数内部中使用寄存器的值. ; 恢复esp和ebp的值. mov esp , ebp; 因为在函数开头,将esp的值保存到了ebp中, 而ebp在整个函数中是不会被修改的, 所以,esp的值就可以使用ebp来恢复. pop ebp ; 因为在函数开头, ebp的值被push中保存了, 因此, 可以直接从栈中pop出来恢复ebp的值. ret 0ch fun endp
push 3 push 2 push 1 call fun ```
使用局部变量
- 在打开栈帧之后, 需要给局部变量开辟栈空间
- 通过sub esp, 所有局部变量的字节数
- esp低地址的位置一般是没有被使用.可以用来保存新的数据, 所以, 没有将栈顶往上抬, 栈空间内的数据就可能被push或者call指令的操作所覆盖
- 通过
ebp-4得到第一个局部变量的偏移
易失性寄存器
一般进入函数之后, 函数内部使用寄存器前需要备份寄存器的值, 离开函数时, 会将寄存器的值恢复回去, 但有些寄存器不会被恢复, 这些不会被恢复的寄存器被成为易失性寄存器
- windows的API的易失性寄存器是eax
- C运行时库函数(crt_xxx系列函数)的易失性寄存器是eax,ecx,edx,ebx
