PoEdu_反汇编004_加法求值

加法求值:

      这里,主要对int,long,short,float,double五个类型做加法求值,并且,加法分为三种情况:常量+常量,长量+变量,变量+变量。

      通过观察反汇编当中的加法,来了解揭发求值过程的特性。测试代码如下:

#include <iostream>

int main(int argc, char* args[])
{
	// 常量 + 常量
	// 常量 + 变量
	// 变量 + 变量

	int nOne = 0, nTwo = 0;
	nOne = 1 + 1;			// 常量 + 常量
	nOne = nOne + 1;		// 常量 + 变量
	nTwo = nOne + nTwo;		// 变量 + 变量
	/*
	mov [nOne],0
	mov [nTwo],0
	mov eax,1
	add eax,1
	mov [nOne],eax

	mov eax,[nOne]
	add eax,1
	mov [nOne],eax

	mov eax,[nOne]
	add eax,[nTwo]
	mov [nTwo],eax
	*/

	long lOne = 0, lTwo = 0;
	lOne = 1 + 1;
	lOne = lOne + 1;
	lTwo = lOne + lTwo;

	short sOne = 0, sTwo = 0;
	sOne = 1 + 1;
	sOne = sOne + 1;
	sTwo = sOne + sOne;

	long long llOne = 0, llTwo = 0;
	llOne = 1 + 1;
	llOne = llOne + 1;
	llTwo = llOne + llTwo;

	float fOne = 0.0f, fTwo = 0.0f;
	fOne = 1 + 0.25f;
	fOne = fOne + 1.25f;
	fTwo = fOne + fTwo;

	double dOne = 0.0, dTwo = 0.0;
	dOne = 1 + 0.25;
	dOne = dOne + 1.25;
	dTwo = dOne + dTwo;

	return 0;
}

int

      上面代码注释部分,使我们对汇编代码实际操作的猜测,而实际上,对于int类型,上面代码的反汇编如下:
	int nOne = 0, nTwo = 0;
0138527E C7 45 F8 00 00 00 00 mov         dword ptr [nOne],0  
01385285 C7 45 EC 00 00 00 00 mov         dword ptr [nTwo],0  
	nOne = 1 + 1;			// 常量 + 常量
0138528C C7 45 F8 02 00 00 00 mov         dword ptr [nOne],2  
	nOne = nOne + 1;		// 常量 + 变量
01385293 8B 45 F8             mov         eax,dword ptr [nOne]  
01385296 83 C0 01             add         eax,1  
01385299 89 45 F8             mov         dword ptr [nOne],eax  
	nTwo = nOne + nTwo;		// 变量 + 变量
0138529C 8B 45 F8             mov         eax,dword ptr [nOne]  
0138529F 03 45 EC             add         eax,dword ptr [nTwo]  
013852A2 89 45 EC             mov         dword ptr [nTwo],eax  
      可以了看出,除了常量+常量与我们想的不一样,其它部分基本都一样, 而对于常量+常量,上面的结果恒定为2,因此,CPU直接计算了赋值了,而没有再使用寄存器

      实际上,long的部分和int的部分完全一样的,大家可以在反汇编窗口下对比下。

short

      下面是short的部分。short的长度是两字节,因此,不能直接使用eax等32位寄存器。反汇编如下:

short sOne = 0, sTwo = 0;
013852CC 33 C0                xor         eax,eax			// 异或操作会将eax全部清零
013852CE 66 89 45 C8          mov         word ptr [sOne],ax		// 将eax低16位赋值给sOne
013852D2 33 C0                xor         eax,eax			// 清零
013852D4 66 89 45 BC          mov         word ptr [sTwo],ax		// 16位的0赋值给sTwo
sOne = 1 + 1;
013852D8 B8 02 00 00 00       mov         eax,2				// 直接将1+1结果放到eax
013852DD 66 89 45 C8          mov         word ptr [sOne],ax		// 低16位赋值给sOne
sOne = sOne + 1;
013852E1 0F BF 45 C8          movsx       eax,word ptr [sOne]		// movsx会对第二个操作数做有符号扩展,再mov
013852E5 83 C0 01             add         eax,1				// +1
013852E8 66 89 45 C8          mov         word ptr [sOne],ax		// 赋值低16位
sTwo = sOne + sOne;
013852EC 0F BF 45 C8          movsx       eax,word ptr [sOne]		// 赋值
013852F0 0F BF 4D C8          movsx       ecx,word ptr [sOne]		// 赋值
013852F4 03 C1                add         eax,ecx			// 相加
013852F6 66 89 45 BC          mov         word ptr [sTwo],ax		// 赋值

      首先,编译器只会在意数据的地址和长度两个参数32位程序中,所有的整数均为32位长度,如果直接使用mov,则是操作32位数据,所以,上面对short的操作,指定了长度word,即16位,由此,我们也可以知道mov操作符不是只能操作32位数据,对于第二次对eax清零,我们发现实际上不清零也是可以的。对于为什么不是:mov word ptr [sOne],0,,,还是之前那句话,所有操作都是32位,最后那个0是32位的,而使用ax,因为ax是16位

      常数+常数部分,C++程序中,我们直接写的也是1+1,因此,他也是32位的1+1,也是直接使用32位的eax,只是后面只将16位的ax赋值给sOne,不妨尝试下使之超过short的范围,可以看出,也一样,多余部分直接丢失。

      变量+常数,我们发现它使用了一个新的操作符,movsx。movsx和mov类似,他会在mov之前先将后面的操作数有符号地转化为32位(做有符号扩展,符号位为0,前面全部填充0,符号位为1,则全部填充1)

      后面的变量+变量也是一样的,然而,我们两个变量都是16字节,为什么不能直接:mov ax, word ptr[sOne]呢,为什么不是:mov ax, cx呢?实际上,还是和之前一样,32位程序,运算操作都是32位的

long long

      对于long long,是8字节(64位长度),汇编源码如下:

long long llOne = 0, llTwo = 0;
00BA7F55 66 0F 57 C0          xorpd       xmm0,xmm0				// xmm寄存器
00BA7F59 66 0F 13 45 AC       movlpd      qword ptr [llOne],xmm0		// qword 64位,movlpd对64位操作
00BA7F5E 66 0F 57 C0          xorpd       xmm0,xmm0
00BA7F62 66 0F 13 45 9C       movlpd      qword ptr [llTwo],xmm0
llOne = 1 + 1;
00BA7F67 C7 45 AC 02 00 00 00 mov         dword ptr [llOne],2
00BA7F6E C7 45 B0 00 00 00 00 mov         dword ptr [ebp-50h],0
llOne = llOne + 1;
00BA7F75 8B 45 AC             mov         eax,dword ptr [llOne]
00BA7F78 83 C0 01             add         eax,1
00BA7F7B 8B 4D B0             mov         ecx,dword ptr [ebp-50h]
00BA7F7E 83 D1 00             adc         ecx,0					// 会查看是否有进位,
00BA7F81 89 45 AC             mov         dword ptr [llOne],eax
00BA7F84 89 4D B0             mov         dword ptr [ebp-50h],ecx
llTwo = llOne + llTwo;
00BA7F87 8B 45 AC             mov         eax,dword ptr [llOne]
00BA7F8A 03 45 9C             add         eax,dword ptr [llTwo]
00BA7F8D 8B 4D B0             mov         ecx,dword ptr [ebp-50h]
00BA7F90 13 4D A0             adc         ecx,dword ptr [ebp-60h]		// 会查看是否有进位
00BA7F93 89 45 9C             mov         dword ptr [llTwo],eax
00BA7F96 89 4D A0             mov         dword ptr [ebp-60h],ecx

      我们知道,32位数据,不足32位会使用movsx可以填充,当longlong这样的64位的时候,不同的编译器会使用不同的汇编指令,比如,VS2013中,类型初始化会使用SSE指令,SSE指令共包含70条指令,是MMX指令集的超级,具体可以自行百度,包括汇编指令集以及其发展等。

      操作的xmm寄存器,xmm寄存器共有8个,每个为128位,正好用来操作longlong类型,xorps是SSE指令,操作数只能是xmm寄存器,作用是异或操作

      longlong类型的操作与前面类似,只是寄存器和操作指令发生了变化,有的地方计算也是分的高低四字节分别计算,如下:


float

      对于float,反汇编代码如下:

	float fOne = 0.0f, fTwo = 0.0f;
011D7F99 F3 0F 10 05 94 CD 1D 01 movss       xmm0,dword ptr ds:[011DCD94h]  
011D7FA1 F3 0F 11 45 90       movss       dword ptr [ebp-70h],xmm0  
011D7FA6 F3 0F 10 05 94 CD 1D 01 movss       xmm0,dword ptr ds:[011DCD94h]  
011D7FAE F3 0F 11 45 84       movss       dword ptr [ebp-7Ch],xmm0  
	fOne = 1 + 0.25f;
011D7FB3 F3 0F 10 05 AC CB 1D 01 movss       xmm0,dword ptr ds:[011DCBACh]  
011D7FBB F3 0F 11 45 90       movss       dword ptr [ebp-70h],xmm0  
	fOne = fOne + 1.25f;
011D7FC0 F3 0F 10 45 90       movss       xmm0,dword ptr [ebp-70h]  
011D7FC5 F3 0F 58 05 AC CB 1D 01 addss       xmm0,dword ptr ds:[011DCBACh]  
011D7FCD F3 0F 11 45 90       movss       dword ptr [ebp-70h],xmm0  
	fTwo = fOne + fTwo;
011D7FD2 F3 0F 10 45 90       movss       xmm0,dword ptr [ebp-70h]  
011D7FD7 F3 0F 58 45 84       addss       xmm0,dword ptr [ebp-7Ch]  
011D7FDC F3 0F 11 45 84       movss       dword ptr [ebp-7Ch],xmm0  
      里面使用了xmm指令,由此可知它使用的是SSE指令,可推断出,某些不支持SSE指令集的处理器不能运行这段代码,例如奔腾3。但是当时已有vc6,同样的C++代码,在奔腾3上面的vc6编译器下,编译出来的反汇编结果也是不一样的

    VS2013中,上面代码可以看出,他是将0.0f先存起来了的,在放入mmx0,在放入fOne等,儿VS2015,会与之前类似的使用异或操作对xmm0寄存器清零,如下图:


      movss指令:讲一个单精度浮点数将源操作数移动到目标操作数中,特别注意,它使用与单精度操作数。

      cvtss2sd指令:将一个单精度浮点数转化为双进度浮点数,VS2015中,相加的时候会转化为双精度,但上面vs2013中,方式不一样。cvtsd2ss与之相反

      addsd指令:他会将第二个操作数和第一个操作数的低位双进度浮点值相加,并将双精度结果保存到目标操作数。VS2013中,使用的addss,会将第二个操作数转化为单精度浮点数,再相加。得到的结果也是单精度浮点数。

double

      双精度浮点数:

	double dOne = 0.0, dTwo = 0.0;
00F47FE1 F2 0F 10 05 88 CD F4 00 movsd       xmm0,mmword ptr ds:[00F4CD88h]  
00F47FE9 F2 0F 11 85 74 FF FF FF movsd       mmword ptr [ebp+FFFFFF74h],xmm0  
00F47FF1 F2 0F 10 05 88 CD F4 00 movsd       xmm0,mmword ptr ds:[00F4CD88h]  
00F47FF9 F2 0F 11 85 64 FF FF FF movsd       mmword ptr [ebp+FFFFFF64h],xmm0  
	dOne = 1 + 0.25;
00F48001 F2 0F 10 05 98 CD F4 00 movsd       xmm0,mmword ptr ds:[00F4CD98h]  
00F48009 F2 0F 11 85 74 FF FF FF movsd       mmword ptr [ebp+FFFFFF74h],xmm0  
	dOne = dOne + 1.25;
00F48011 F2 0F 10 85 74 FF FF FF movsd       xmm0,mmword ptr [ebp+FFFFFF74h]  
00F48019 F2 0F 58 05 98 CD F4 00 addsd       xmm0,mmword ptr ds:[00F4CD98h]  
00F48021 F2 0F 11 85 74 FF FF FF movsd       mmword ptr [ebp+FFFFFF74h],xmm0  
	dTwo = dOne + dTwo;
00F48029 F2 0F 10 85 74 FF FF FF movsd       xmm0,mmword ptr [ebp+FFFFFF74h]  
00F48031 F2 0F 58 85 64 FF FF FF addsd       xmm0,mmword ptr [ebp+FFFFFF64h]  
00F48039 F2 0F 11 85 64 FF FF FF movsd       mmword ptr [ebp+FFFFFF64h],xmm0 
      可以发现,这个与VS2015下的单精度计算类似,只是不需要转化为单精度浮点数了,movsd与movss类似,是进行双进度移动操作的。

课后:

      我们知道,不同的编译器即使在相同的计算机及系统下,都可能生成不同的汇编代码,甚至,Debug下也会有少量的优化,如果条件允许,不妨测试下,相同的代码在VC6,VC2008等编译器下生成的汇编有什么不一样,我们知道汇编结果肯定一样,但我们的分析会不会一样呢。

      我们是在Debug版本下分析的反汇编代码,如果是Release呢?结果肯定是做了大量的优化,但分析的结果是一样吗。

      例如,下面是1+1的反汇编在Debug和Release下的汇编代码:

#include <iostream>

int main()
{
	int n1 = 1, n2 = 2;
	n1 += n2;
	printf("%d", n1);
	return 0;
}
Debug反汇编:
int main()
{
013A3A70 55                   push        ebp  
013A3A71 8B EC                mov         ebp,esp  
013A3A73 81 EC D8 00 00 00    sub         esp,0D8h  
013A3A79 53                   push        ebx  
013A3A7A 56                   push        esi  
013A3A7B 57                   push        edi  
013A3A7C 8D BD 28 FF FF FF    lea         edi,[ebp+FFFFFF28h]  
013A3A82 B9 36 00 00 00       mov         ecx,36h  
013A3A87 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
013A3A8C F3 AB                rep stos    dword ptr es:[edi]  
	int n1 = 1, n2 = 2;
013A3A8E C7 45 F8 01 00 00 00 mov         dword ptr [ebp-8],1  
013A3A95 C7 45 EC 02 00 00 00 mov         dword ptr [ebp-14h],2  
	n1 += n2;
013A3A9C 8B 45 F8             mov         eax,dword ptr [ebp-8]  
013A3A9F 03 45 EC             add         eax,dword ptr [ebp-14h]  
013A3AA2 89 45 F8             mov         dword ptr [ebp-8],eax  
	printf("%d", n1);
013A3AA5 8B F4                mov         esi,esp  
013A3AA7 8B 45 F8             mov         eax,dword ptr [ebp-8]  
013A3AAA 50                   push        eax  
013A3AAB 68 1C CD 3A 01       push        13ACD1Ch  
013A3AB0 FF 15 90 01 3B 01    call        dword ptr ds:[013B0190h]  
013A3AB6 83 C4 08             add         esp,8  
013A3AB9 3B F4                cmp         esi,esp  
013A3ABB E8 15 D8 FF FF       call        013A12D5  
	return 0;
013A3AC0 33 C0                xor         eax,eax  
}

Release反汇编:

--- f:\vscode\po\reverse\adddemo\adddemo\main.cpp ------------------------------
	int n1 = 1, n2 = 2;
	n1 += n2;
	printf("%d", n1);
00C112A0 6A 03                push        3  
00C112A2 68 8C 31 C1 00       push        0C1318Ch  
00C112A7 FF 15 B4 30 C1 00    call        dword ptr ds:[00C130B4h]  
00C112AD 83 C4 08             add         esp,8  
	return 0;
00C112B0 33 C0                xor         eax,eax  
}
00C112B2 C3                   ret  
--- 无源文件 -----------------------------------------------------------------------
      可以看出, Release下,直接就是调用printf打印3,而Debug中,C/C++代码的每一步都是比较准确的展示出来了的Release版本会对程序做大量的优化



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值