标 题: 【原创】C语言中的i++和++i的逆向分析
作 者: lonkil
时 间: 2008-03-15
备 注:
可以去以下地址:
http://www.vcfans.com/Article/c-parameter-increase-disassemle.aspx
查看代码着色过的版本。
本人长期潜水,今天是第一次发贴,望大家拍砖。
联 系:
lonkil_AT_GMail.Com
www.Vcfans.com
公司同事在讨论一个比较变态的问题,关于前自增和后自增的运行结果。
这种题目经常出现C语言面试试题当中。我也不知道结果,就作了一下逆向,没想到受益不小。
逆向比较简单,高手飘过.....,随便BS一下出这样面试题目的人,至于为什么我后面再作分析。
工具:Vc6.0+IDA
平台:WinXP + SP2
题目:
int i=5,j=5;
int p,q;
p=(i++)+(i++)+(i++);
q=(++j)+(++j)+(++j);
printf("i=%d,j=%d,p=%d,q=%d\n",i,j,p,q);
Vc6.0的Debug模式下输出:
i=8,j=8,p=15,q=22
下面是IDA的分析代码:
.text:00401010 _main_0 proc near ; CODE XREF: _mainj
.text:00401010
.text:00401010 var_50 = byte ptr -50h
.text:00401010 var_10 = dword ptr -10h
.text:00401010 var_C = dword ptr -0Ch
.text:00401010 var_8 = dword ptr -8
.text:00401010 var_4 = dword ptr -4
.text:00401010
.text:00401010 push ebp
.text:00401011 mov ebp, esp
.text:00401013 sub esp, 50h
.text:00401016 push ebx
.text:00401017 push esi
.text:00401018 push edi
.text:00401019 lea edi, [ebp+var_50]
.text:0040101C mov ecx, 14h
.text:00401021 mov eax, 0CCCCCCCCh
.text:00401026 rep stosd
.text:00401028 mov [ebp+var_4], 5 ; 将i赋值成5
.text:0040102F mov [ebp+var_8], 5 ; 将j赋值成5
.text:00401036 mov eax, [ebp+var_4]
.text:00401039 add eax, [ebp+var_4]
.text:0040103C add eax, [ebp+var_4]
.text:0040103F mov [ebp+var_C], eax ; 用i连加三次,将结果保存到P中。
.text:00401042 mov ecx, [ebp+var_4]
.text:00401045 add ecx, 1
.text:00401048 mov [ebp+var_4], ecx
.text:0040104B mov edx, [ebp+var_4]
.text:0040104E add edx, 1
.text:00401051 mov [ebp+var_4], edx
.text:00401054 mov eax, [ebp+var_4]
.text:00401057 add eax, 1
.text:0040105A mov [ebp+var_4], eax ; 对i连加三次1,可以看出(i++)+(i++)+(i++)是将i的三个初始值连加三次,
.text:0040105A ; 保存到结果,然后再作i自增
.text:0040105D mov ecx, [ebp+var_8]
.text:00401060 add ecx, 1
.text:00401063 mov [ebp+var_8], ecx
.text:00401066 mov edx, [ebp+var_8]
.text:00401069 add edx, 1
.text:0040106C mov [ebp+var_8], edx ; 对j先加两次1,此时的j=7
.text:0040106F mov eax, [ebp+var_8]
.text:00401072 add eax, [ebp+var_8] ; 用eax作暂存,对当前的j值进行两次相加
.text:00401075 mov ecx, [ebp+var_8]
.text:00401078 add ecx, 1
.text:0040107B mov [ebp+var_8], ecx ; j自增1
.text:0040107E add eax, [ebp+var_8]
.text:00401081 mov [ebp+var_10], eax ; 将此时的j与eax相加,并将结果保存到q中
.text:00401084 mov edx, [ebp+var_10]
.text:00401087 push edx
.text:00401088 mov eax, [ebp+var_C]
.text:0040108B push eax
.text:0040108C mov ecx, [ebp+var_8]
.text:0040108F push ecx
.text:00401090 mov edx, [ebp+var_4]
.text:00401093 push edx ; 四个参数入栈,调用printf
.text:00401094 push offset Format ; "i=%d,j=%d,p=%d,q=%d\n"
.text:00401099 call _printf
.text:0040109E add esp, 14h
.text:004010A1 xor eax, eax ; 调用者清栈
.text:004010A3 pop edi
.text:004010A4 pop esi
.text:004010A5 pop ebx ; 恢复寄存器值
.text:004010A6 add esp, 50h
.text:004010A9 cmp ebp, esp
.text:004010AB call __chkesp
.text:004010B0 mov esp, ebp
.text:004010B2 pop ebp
.text:004010B3 retn
.text:004010B3 _main_0 endp
每一步我都作了注释,应该比较简单不难理解。下面我将这段ASM代码翻译成C代码:
int main(int argc, char* argv[])
{
int i=5,j=5;
int p,q;
int eax;
eax = i;
eax += i;
eax += i;
p=eax;
/*完成P的计算*/
i += 1;
i += 1;
i += 1;
/*
完成i的自增
结束p=(i++)+(i++)+(i++);的运算
*/
j += 1;
j += 1;
eax = j;
eax += j;
j += 1;/*完成j的计算*/
eax += j;
q = eax;
/*
结束q=(++j)+(++j)+(++j);的运算
*/
printf("i=%d,j=%d,p=%d,q=%d\n",i,j,p,q);
return 0;
}
对照结果,我们分析一下前自增和后自增的过程:
前自增:
1.先用i的初始值相加三次,得到P的值。
2.然后再对i进行三次递增。
后自增:
1.先将j进行两递增
2.再对j进行两次相加,将值保存在EAX中
3.再对j作一次自增
4.再用当前的j值和EAX值相加得出q值
文章开头为什么BS出这样题的人呢?我们将编译模式设成Release模式
运行一下,发现结果和Debug模式下的不一样了。
结果是:
i=8,j=8,p=15,q=24
奇怪同样的代码在Debug和Release下怎么这么大的区别呢?
Release下的反汇编的代码:
_main proc near
push 18h
push 0Fh
push 8
push 8 ; 编译器直接将结果给优化。
push offset aIDJDPDQD ; "i=%d,j=%d,p=%d,q=%d\n"
call sub_401020
add esp, 14h
xor eax, eax
retn
_main endp
呵呵,很简洁吧。原来编译器在编译过程中作了很大的优化,直接将结果给生成好。
而且两种模式下的产生的结果不一样,这正是我为什么要BS出这类题目的人。好好的代码为什么要这样写,产生了很大的歧义。这种写法的代码真的没有实际意义。如果在Debug下产生了相要的结果,等项目发布时,发现了问题,要花多大的代价才能找出这个Bug?
所以代码不能一味的求简洁,要保证结果的正确性才是最重的。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)