在以C为代表的高级语言中用if-then-else,switch-case等高级语句来构成程序的判断流程,
不仅条理清晰且维护性还是不错的.而汇编语言的代码则复杂得多,会看到cmp等指令的后面跟着 各类的跳转指令jz jnz等.识别关键跳转是软件破解的一个重要技能,许多软件用一个或多个跳转 来实现注册或非注册.
下面先说说if-then-else语句
将语句if-then-else语句编译代码后,整数用cmp指令比较,而浮点值则是使用fcom fcomp比 较.语句if-then-else编译后,其汇编代码形式如下:
cmp a,b ;a和b只是代表
jz(jnz) xxxx ;xxxx是一个地址
cmp指令不修改操作符,根据两个操作数的相减,影响处理的几个标志,如零标志 进位标志 符号标志和溢出标志,jz等指令就是条件跳转指令,根据a,b的值大小决定跳转方向.
实际上,许多情况下编译器都用test或or之类的较短的逻辑指令来替换cmp指令.一般形式是"test eax,eax",如eax如果为0,则逻辑与运算结果为零,否则设为0.
来看一个C反汇编的实例.
- #include <stdio.h>
- int main()
- {
- int a,b=5;
- scanf("%d",&a);
- if (a==0)
- a=8;
- return a+b;
- }
看反汇编的代码:
- push ecx ;为局部变量分配内存相当于sub esp,4
- lea eax,dword ptr ss:[esp] ;eax指向局部变量空间
- push eax
- push 00407030 ;指向字符串"%d"
- call 00401030 ;c语言的scanf函数
- mov eax,dword ptr [esp+8] ;将输入的字符传出
- add esp,8 ;由于是cdecl调用,所以在函数外调用
- test eax,eax ;若eax为0,则ZF置1,否则ZF置0
- jnz 00401020 ;若ZF=1不跳转,否则跳转
- mov eax,8
- add eax,8 ;return a+b
- pop ecx ;释放局部变量用到的内存,相当于add esp,4
- retn
好,看完if-then-else了,下面砍switch-case语句.
SWITCH语句是多分支选择语句.SWITCH语句编译后,实质就是多个IF-THEN语句嵌套组合.编译 器会将SWITCH编译成一组不同关系运算组成的语句.具体点,来看一个例子.
- #include <stdio.h>
- int main(void)
- {
- int a
- scanf("%d",&a);
- switch(a)
- {
- case1:printf("a=1");
- break;
- case2:printf("a=2");
- break;
- case3:printf("a=10");
- break;
- default:printf("a=default");
- break;
- }
- return 0;
- }
把它编译,然后反汇编看看汇编代码:
- push ebp
- mov ebp,esp
- sub esp,8 ;为局部变量分配空间
- lea eax,[ebp-04]
- push eax ;指向字符("%d")
- call 004010A2 ;scanf("%d",&a)
- add esp,8 ;
- mov ecx,[ebp-04] ;输入的结果给ecx
- mov [ebp-08],ecx
- cmp [ebp-08],1 ;如果是1
- je 00401031
- cmp [ebp-08],2 ;如果是2
- je 00401040
- cmp [ebp-08],0a ;如果是10
- je 0040104F
- jmp 0040105E
- push 00408034
- call 00401071 ;printf("a=1")
- add esp,04
- jmp 0040106B
- push 00408038
- call 00401071 ;printf("a=2")
- add esp,04
- jmp 0040106B
- push 0040803C
- call 00401071 ;printf ("a=10")
- jmp 0040106B
- push 00408044
- call 00401071 ;printf("a=default")
- add esp,04
- xor eax,eax
- mov esp,ebp
- pop ebp
- ret
上面的是未优化的反汇编,再看看优化过后的:
- push ecx ;为局部变量分配内存,相当于sub esp,4
- lea eax,[esp]
- push eax
- push 0040804c
- call 004010A1 ;scanf("%d",&a)
- mov eax,[esp+08] ;scanf输入的结果传给eax
- add esp,08
- dec eax ;检测eax是否为1,如果是下面的那句就跳转
- je 00401055 ;相当于case 1
- dec eax ;eax再减一,即eax的值是2
- je 00401044 ;相当于ease 2
- sub eax,8 ;eax两次减1后的值为8,所以值为18
- je 00401033
编译器优化后用"dec eax"指令代替cmp指令,这样指令更短,并且执行速度更快,并且优化后,编译器会合理排列switch后各case节点,有最优化方式找到所需要的节点.
如果case取值表示一个算数级数,那么编译器会利用一个跳转表来实现例如"
- switch(a)
- {
- case 1:printf("a=1");break;
- case 2:printf("a=2");break;
- case 3:printf("a=3");break;
- case 4:printf("a=3");break;
- case 5:printf("a=3");break;
- case 6:printf("a=3");break;
- case 7:printf("a=3");break;
- default :printf("a=default"):break;
- }
编译器编译后,"jmp dword ptr [4*eax+004010B0]"指令就相当于switch(a),其根据eax的值进行索引,计算出指向相应case处理代码的指针.汇编代码如下:
- lea eax,dword ptr [ecx-0]
- cmp eax,6 ;判断是否为default节点
- ja 0040109D
- jmp dword ptr [4*eax+004010B0] ;跳转表
- push 00408054 ;case1:printf("a=1")
- call 004010D0
- add esp,04
- xor eax,eax
- pop ecx
- ret
下面的我就不写了,反正大概就是这样.