首页 > 代码库 > C/C++流程与反汇编

C/C++流程与反汇编

众所周知,任何程序都可以由三种基本控制结构组成,分别是循序结构,选择结构,循环结构。


这三种结构翻译成汇编语言又是怎样的呢?这里主要考虑的是debug版本。对于release版本经过各种优化后结果不一样,不作考虑。这里的编译器采用的是Visual Studio 2008


顺序结构没什么悬念,这里就不提了,首先看下选择结构。


选择结构,主要有两种表现方式:if{ }else if{ } else{ }与 switch{case : case : default:}


首先来看下


if判断分支

	if (a > 0 && b < 0)
00182DCC  cmp         dword ptr [a],0                 ;两个判断,不合规范就跳到下一个else if处
00182DD0  jle         foo+51h (182DF1h) 
00182DD2  cmp         dword ptr [b],0 
00182DD6  jge         foo+51h (182DF1h) 
	{                                              ;没跳走,执行代码块的内容
		printf("if (a > 0 && b < 0)");
00182DD8  mov         esi,esp 
00182DDA  push        offset string "if (a > 0 && b < 0)" (185974h) 
00182DDF  call        dword ptr [__imp__printf (1882B4h)] 
00182DE5  add         esp,4 
00182DE8  cmp         esi,esp 
00182DEA  call        @ILT+450(__RTC_CheckEsp) (1811C7h) 
00182DEF  jmp         foo+87h (182E27h)            ;执行完,跳出if
	}
	else if (a < 0)                                    ;还是判断,不合规范就跳到下一个else
00182DF1  cmp         dword ptr [a],0 
00182DF5  jge         foo+70h (182E10h) 
	{
		printf("else if (a < 0)");
00182DF7  mov         esi,esp 
00182DF9  push        offset string "else if (a < 0)" (1857A8h) 
00182DFE  call        dword ptr [__imp__printf (1882B4h)] 
00182E04  add         esp,4 
00182E07  cmp         esi,esp 
00182E09  call        @ILT+450(__RTC_CheckEsp) (1811C7h) 
	}
	else
00182E0E  jmp         foo+87h (182E27h)       ;我这得这条语句放在前一个else if里头更合适
	{
		printf("else");
00182E10  mov         esi,esp 
00182E12  push        offset string "else" (1857A0h) 
00182E17  call        dword ptr [__imp__printf (1882B4h)] 
00182E1D  add         esp,4 
00182E20  cmp         esi,esp 
00182E22  call        @ILT+450(__RTC_CheckEsp) (1811C7h) 
	}

随便写的一个if循环,合理安排if的比较会让代码更少,但为了演示,也无所谓了。代码分析完也觉得简单,就是一开始看有点麻烦。


	cmp <条件>			;多少个条件就多少个判断跳转
	jle <下一个分支>	    ;这里通常与C/C++里的判断相反
	……
	cmp <条件>
	jle <下一个分支>

	(代码块)

	jmp <if外>			;最后一个if(else)代码块没有这条

switch - case 判断分支

	switch(a)
00E82DC5  mov         eax,dword ptr [a] 
00E82DC8  mov         dword ptr [ebp-0D0h],eax 
00E82DCE  cmp         dword ptr [ebp-0D0h],0      ;判断跳转很频繁,首先考虑是switch
00E82DD5  je          foo+4Bh (0E82DEBh) 
00E82DD7  cmp         dword ptr [ebp-0D0h],1 
00E82DDE  je          foo+52h (0E82DF2h)        ;这些是符合条件的就跳转到对应的代码块
00E82DE0  cmp         dword ptr [ebp-0D0h],2 
00E82DE7  je          foo+5Bh (0E82DFBh) 
00E82DE9  jmp         foo+64h (0E82E04h)        ;没有符合条件的,跳到 default
	{
	case 0:
		a = 0;
00E82DEB  mov         dword ptr [a],0            ;这里没有break,继续往下执行
	case 1:
		a =1;
00E82DF2  mov         dword ptr [a],1 
		break;
00E82DF9  jmp         foo+6Bh (0E82E0Bh)        ;break,跳出switch
	case 2:
		a =2;
00E82DFB  mov         dword ptr [a],2 
		break;
00E82E02  jmp         foo+6Bh (0E82E0Bh) 
	default:
		a =3;
00E82E04  mov         dword ptr [a],3 
	}


连续的比较与条件跳转,容易让人联想到switch
对于代码块也比较简单
有break会增加一个无条件跳转

接下来看下循环结构,


循环结构主要有三种:For循环,While循环,Do-While循环。


至于其他语言的一些repeat until等不作考虑,请自行分析。


For循环

	for (int i = 0;i< 5;i++)
00DB17CE  mov         dword ptr [i],0 
00DB17D5  jmp         foo+30h (0DB17E0h) 
00DB17D7  mov         eax,dword ptr [i] 
00DB17DA  add         eax,1 
00DB17DD  mov         dword ptr [i],eax 
00DB17E0  cmp         dword ptr [i],5 
00DB17E4  jge         foo+53h (0DB1803h) 
	{
		printf("%d",i);
00DB17E6  mov         esi,esp 
00DB17E8  mov         eax,dword ptr [i] 
00DB17EB  push        eax  
00DB17EC  push        offset string "%d" (0DB573Ch) 
00DB17F1  call        dword ptr [__imp__printf (0DB82B4h)] 
00DB17F7  add         esp,8 
00DB17FA  cmp         esi,esp 
00DB17FC  call        @ILT+450(__RTC_CheckEsp) (0DB11C7h) 
00DB1801  jmp         foo+27h (0DB17D7h) 
	}

代码很简单,翻译成汇编之后,就得转换下思维了,这里总结个规律出来后就会变得简单了。下面是一个基本的框架:

for(第一部分;第二部分;第三部分)
{
	循环体;
}

	mov <循环变量>,<初始值>				;第一部分。给循环变量赋初值
	jmp B								;跳到第一次循环处,执行第二部分

A:	(改动循环变量)						;第三部分。修改循环变量

B:	cmp <循环变量>,<限制变量>			;第二部分。检查循环变量
	jge 跳出循环							;这里的判断条件通常与for中看到的相反
	……
	(循环体)
	……
	jmp A								;跳回去第三部分,修改变量循环

While循环

	while(a > 0)
00852DC5  cmp         dword ptr [a],0            ;首先判断,不合条件跳出while代码块
00852DC9  jle         foo+36h (852DD6h) 
	{
		a--;
00852DCB  mov         eax,dword ptr [a] 
00852DCE  sub         eax,1 
00852DD1  mov         dword ptr [a],eax 
	}
00852DD4  jmp         foo+25h (852DC5h)              ;强制跳转会开头的判断

框架很简单,先判断,不符合条件就跳出代码块,否则继续执行下去,代码块最后跳回来继续判断。

A:	cmp <循环变量>,<限制变量>
	jge  B         ;跳出代码块
	
	(循环体)
	
	……
	jmp  A        ;往回跳

	
B:			;循环结束

Do - While循环

	do
	{
		a--;
01362DC5  mov         eax,dword ptr [a] 
01362DC8  sub         eax,1 
01362DCB  mov         dword ptr [a],eax 
	}while(a > 0);
01362DCE  cmp         dword ptr [a],0 
01362DD2  jg          foo+25h (1362DC5h) 
		

Do - while就更加简单了,直接把判断挪到代码块后面。

A:	cmp <循环变量>,<限制变量>
	jge B
	
	(循环体)
	
	……
	jmp A
	
B:			;循环结束

好了,基本介绍完了,此次只是为了告诫我们,在汇编里面,代码的理解跟高级语言是有点出入的,得转换下思维。另外,也说明了同一种逻辑可以通过多种方式来表达。


另外,这只是debug版本下的反汇编代码,在release版本下,代码将千变万化,比如,switch将可能会使用跳转表等来实现,部分if将直接被优化掉,毕竟使用流水线速度将会大大加快,忽然一个跳转将会打断流水线。