1) Debug与Release的区别:前者称调试版,后者称发行版。调试版基本不优化,而发行版会经过编译器的极致优化,往往与优化前的高级语言执行流程会大相径庭,但是实现的功能是等价的。
2) 如下for循环语句:
int MyFunction(int a,int b)
{
int c = a + b;
int i;
for ( i = 0 ; i < 50 ; i ++ )
{
c = c + i;
}
return c;
}
Debug版汇编后代码为:
int MyFunction(inta,int b)
{
//省略现场保护代码
int c = a + b;
0040D4B8 mov eax,dword ptr [ebp+8] ;取参数1
0040D4BB add eax,dword ptr [ebp+0Ch] ;取参数2
0040D4BE mov dword ptr [ebp-4],eax ;存放局部变量c
int i;
for ( i = 0 ; i < 50 ; i ++ )
0040D4C1 mov dword ptr [ebp-8],0 ;局部变量i
0040D4C8 jmp MyFunction+33h (0040d4d3) ;跳至第一次循环
0040D4CA mov ecx,dword ptr [ebp-8] ;改变循环变量
0040D4CD add ecx,1 ;i自增操作
0040D4D0 mov dword ptr [ebp-8],ecx ;保存
0040D4D3 cmp dword ptr [ebp-8],32h ;循环条件比较
0040D4D7 jge MyFunction+44h (0040d4e4) ;大于等于则跳
{
c = c + i;
0040D4D9 mov edx,dword ptr [ebp-4] ;读变量c到edx
0040D4DC add edx,dword ptr [ebp-8] ;加上i
0040D4DF mov dword ptr [ebp-4],edx ;保存
}
0040D4E2 jmp MyFunction+2Ah(0040d4ca) ;继续循环
return c;
0040D4E4 mov eax,dwordptr [ebp-4] ;取结果到eax
}
//省略现场恢复代码
大体结构为:
MOV <循环变量> <初始值> ;给循环变量赋初值
JMP B ;跳至第一次循环
A: (改动循环变量) ;修改循环变量
…
B: CMP <循环变量> <限制变量> ;检查循环条件
JGE 跳出循环
(循环体)
…
JMP A ;跳回去继续修改循环变量
3) 上述的for循环改为以下do循环:
int i = 0;
do{
c = c + i;
}while( ++i <50 );
Debug版本汇编:
int i = 0;
0040D4C1 mov dword ptr [ebp-8],0 ;循环变量i初始化
do{
c = c + i;
0040D4C8 mov ecx,dword ptr [ebp-4]
0040D4CB add ecx,dword ptr [ebp-8]
0040D4CE mov dword ptr [ebp-4],ecx ;相加并保存
}while( ++i<= 50 );
0040D4D1 mov edx,dword ptr [ebp-8]
0040D4D4 add edx,1
0040D4D7 mov dword ptr [ebp-8],edx ;改变循环变量
0040D4DA cmp dword ptr [ebp-8],32h ;比较循环条件
0040D4DE jl MyFunction+28h (0040d4c8) ;小于则跳,继续循环
4) 上述的do循环改为以下while循环:
int i = 0;
while( i++ < 50)
{
c= c + i;
}
Debug版本汇编:
int i = 0;
0040D4C1 mov dword ptr [ebp-8],0 ;循环变量i初始化
while( i++ < 50)
0040D4C8 mov ecx,dword ptr [ebp-8] ;自增前的循环变量
0040D4CB mov edx,dword ptr [ebp-8]
0040D4CE add edx,1
0040D4D1 mov dword ptr [ebp-8],edx ;循环变量自增并保存
0040D4D4 cmp ecx,32h ;比较(自增前的)
0040D4D7 jge MyFunction+44h (0040d4e4) ;不小于则跳转,退出循环
{
c = c + i;
0040D4D9 mov eax,dword ptr [ebp-4]
0040D4DC add eax,dword ptr [ebp-8]
0040D4DF mov dword ptr [ebp-4],eax ;相加并保存
}
0040D4E2 jmp MyFunction+28h (0040d4c8) ;绝对跳转,集训循环
5) 如下if-else判断分支:
int i = 10,j;
if ( i <= 0 )
{
j= 1;
}
else if ( i > 0 && i <= 10)
{
j= 2;
}
else
{
j= 3;
}
Debug版本汇编:
int i = 10,j;
0040D4B8 mov dword ptr [ebp-4],0Ah ;仅声明的变量不分配内存
if ( i <= 0 )
0040D4BF cmp dword ptr [ebp-4],0 ;比较
0040D4C3 jg MyFunction+2Eh (0040d4ce) ;大于则跳下一分支
{
j = 1;
0040D4C5 mov dword ptr [ebp-8],1 ;赋值
}
else if ( i > 0 && i <= 10)
0040D4CC jmp MyFunction+4Ah (0040d4ea) ;结束整个分支
0040D4CE cmp dword ptr [ebp-4],0 ;else-if分支开始
0040D4D2 jle MyFunction+43h (0040d4e3) ;不大于0则转入else
0040D4D4 cmp dword ptr [ebp-4],0Ah ;判断第二个条件
0040D4D8 jg MyFunction+43h (0040d4e3) ;大于则转入else
{
j = 2;
0040D4DA mov dword ptr [ebp-8],2 ;赋值
}
else
0040D4E1 jmp MyFunction+4Ah (0040d4ea) ;结束整个分支
{
j = 3;
0040D4E3 mov dword ptr [ebp-8],3 ;赋值
}
0040D4EA …
If-else语句使用CMP加绝对跳转指令实现(跳转到下一分支或者整个分支的结束位置),从上可知要排列好比较条件的顺序,以达到最少的比较次数的效果。
6) 如下switch-case判断分支:
int i = 10,j;
switch( i ){
case 0:
j= 0;
case 1:
j= 1;
break;
default:
j= 3;
}
Debug版本汇编:
int i = 10,j;
0040D4B8 mov dword ptr [ebp-4],0Ah ;赋初值
switch( i )
{
0040D4BF mov eax,dword ptr [ebp-4]
0040D4C2 mov dword ptr [ebp-0Ch],eax ;转移内存单元
0040D4C5 cmp dword ptr [ebp-0Ch],0 ;与0比较
0040D4C9 je MyFunction+33h (0040d4d3) ;等于则转
0040D4CB cmp dword ptr [ebp-0Ch],1 ;与1比较
0040D4CF je MyFunction+3Ah (0040d4da)
0040D4D1 jmp MyFunction+43h (0040d4e3) ;绝对跳转到default
case 0:
j = 0;
0040D4D3 mov dword ptr [ebp-8],0 ;无break转入下一分支
case 1:
j = 1;
0040D4DA mov dword ptr [ebp-8],1
break;
0040D4E1 jmp MyFunction+4Ah (0040d4ea) ;break,绝对跳转到结束
default:
j = 3;
0040D4E3 mov dword ptr [ebp-8],3
}
0040D4EA …
期间对i进行转储是Debug版本的特点,目的并不明确。每一个case对应一个cmp与跳转指令je,最后的default要是没有,则跳转到结束处。Case中有break则跳转到结束,没有则继续往下执行。
7) 如下C语言结构体和数组:
typedef struct {
float a;
char b;
int c;
}mystruct;
int MyFunction(void)
{
unsigned char *buf[100];
mystruct* strs = (mystruct *)buf;
int i;
for(i = 0 ; i < 5 ; i++)
{
strs[i].a= 1.2;
strs[i].b= 'a';
strs[i].c= 0;
}
return 0;
}
Debug版本汇编MyFunction函数如下:
int MyFunction(void)
{
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,1D8h ;1D8H = 472字节
00401029 push ebx
0040102A push esi
0040102B push edi
0040102C lea edi,[ebp-1D8h]
00401032 mov ecx,76h
00401037 mov eax,0CCCCCCCCh
0040103C rep stos dword ptr [edi] ;标准现场保护
unsigned char *buf[100];
tystruct *strs =(mystruct *)buf;
0040103E lea eax,[ebp-190h]
00401044 mov dword ptr [ebp-194h],eax ;将ebp – 190h送入ebp – 194h
;ebp– 190h为缓存区地址首址(char)
;ebp– 194h为str指针(tystruct *strs)
int i;
for (i = 0 ; i< 5 ; i++)
0040104A mov dword ptr [ebp-198h],0
00401054 jmp MyFunction+45h (00401065)
00401056 mov ecx,dword ptr [ebp-198h]
0040105C add ecx,1
0040105F mov dword ptr [ebp-198h],ecx
00401065 cmp dword ptr [ebp-198h],5
0040106C jge MyFunction+91h (004010b1) ;标准for循环结构
{
strs[i].a = 1.2;
0040106E mov edx,dword ptr [ebp-198h] ;ebp – 198h为循环变量i
00401074 imul edx,edx,0Ch ;结构体长度为常量0CH,得偏移
00401077 mov eax,dword ptr [ebp-194h] ;取str中的指针值
0040107D mov dword ptr [eax+edx],3F99999Ah
;得strs[i].a地址,并赋值
strs[i].b = 'a';
00401084 mov ecx,dword ptr [ebp-198h]
0040108A imul ecx,ecx,0Ch
0040108D mov edx,dword ptr [ebp-194h]
00401093 mov byte ptr [edx+ecx+4],61h ;相对与之上多偏移4个字节
strs[i].c = 0;
00401098 mov eax,dword ptr [ebp-198h]
0040109E imul eax,eax,0Ch
004010A1 mov ecx,dword ptr [ebp-194h]
004010A7 mov dword ptr [ecx+eax+8],0 ;相对与之上,再偏移4个字节
}
004010AF jmp MyFunction+36h (00401056)
return 0;
004010B1 xor eax,eax
}
//省略现场恢复
第一点:系统预分配的临时变量堆栈区可变且足够(暂时不知道怎么计算的);第二点:Struct的大小编译以后作为常数保存,对结构体中的变量的访问(点运算符)汇编后采用的偏移量的形式;第三点:偏移量的大小为结构体定义时各个成员变量于系统字长对齐后大小的叠加(mystruct第二个成员为char类型,理论上来说只占用一个字节,但是与4字节对齐,实际占用4个字节)
8) 如下C语言共用体和枚举类型:
typedef enum {
ENUM_1= 1,
ENUM_2= 2,
ENUM_3,
ENUM_4,
}myenum;
typedef struct{
inta;
intb;
intc;
}mystruct;
typedef union{
mystructs;
myenume[3];
}myunion;
int MyFunction(void)
{
unsigned char buf[100] = { 0 };
myunion* uns = (myunion *)buf;
int i;
for(i = 0 ; i < 5 ; i++)
{
uns[i].s.a= 0;
uns[i].s.b= 1;
uns[i].e[2]= ENUM_4;
}
return0;
}
Debug版本汇编MyFunction函数如下:
int MyFunction(void)
{
//现场保护代码省略
unsigned charbuf[100] = { 0 };
0040103E mov byte ptr [ebp-64h],0 ;ebp – 64的1个字节置0
00401042 mov ecx,18h ;循环18H(24)次
00401047 xor eax,eax ;eax置0
00401049 lea edi,[ebp-63h] ;从ebp – 63位置开始清
0040104C rep stos dword ptr [edi] ;内存清0(24*4=96字节)
0040104E stos word ptr [edi] ;继续清2个字节
00401050 stos byte ptr [edi] ;继续清1个字节
;合计100字节
myunion *uns =(myunion *)buf;
00401051 lea eax,[ebp-64h]
00401054 mov dword ptr [ebp-68h],eax ;ebp-68h为myunion *uns指针变量
int i;
for (i = 0 ; i< 5 ; i++)
00401057 mov dword ptr [ebp-6Ch],0 ; ebp-6Ch为局部变量i
0040105E jmp MyFunction+49h (00401069)
00401060 mov ecx,dword ptr [ebp-6Ch]
00401063 add ecx,1
00401066 mov dword ptr [ebp-6Ch],ecx
00401069 cmp dword ptr [ebp-6Ch],5
0040106D jge MyFunction+83h (004010a3) ;标准for循环
{
uns[i].s.a = 0;
0040106F mov edx,dword ptr [ebp-6Ch]
00401072 imul edx,edx,0Ch ;0CH为共用体大小
00401075 mov eax,dword ptr [ebp-68h]
00401078 mov dword ptr [eax+edx],0 ;偏移,赋值
uns[i].s.b = 1;
0040107F mov ecx,dword ptr [ebp-6Ch]
00401082 imul ecx,ecx,0Ch
00401085 mov edx,dword ptr [ebp-68h]
00401088 mov dword ptr [edx+ecx+4],1 ;相对上面多偏移4字节
uns[i].e[2]= ENUM_4;
00401090 mov eax,dword ptr [ebp-6Ch]
00401093 imul eax,eax,0Ch
00401096 mov ecx,dword ptr [ebp-68h]
00401099 mov dword ptr [ecx+eax+8],4 ;相对上面再偏移4字节
}
004010A1 jmp MyFunction+40h (00401060)
return 0;
004010A3 xor eax,eax
}
//省略现场恢复代码
第一:从100字节缓存区的初始化可知,Debug版本的代码没经过优化,很stupid;第二:这两种类型的汇编访问方式同数组与结构体如出一辙,并没有什么不同。