第二部分 C++反汇编揭秘
逆向分析技术重在代码的调试和分析,如果你本来就是一个技术不错的程序员,学习这部分内容就是对你“内功”的锻炼,这部分内容可以帮助你彻底掌握C/C++各种特性的底层机制,不仅能做到知其然,还能知其所以然。这个部分以C/C++语法为导向,以各编译器为例,解析每个C/C++知识点的汇编表现形式,通过整理其反汇编代码梳理流程和脉络。
2.1 整数类型
C++提供的整数数据类型有:int、long、short和long long。在Visual Studio 2019中,int类型与long类型都占4字节,short类型占2字节,long long类型占8字节。
由于二进制数不方便显示和阅读,因此内存中的数据采用十六进制数表示。1字节由2个十六进制数组成,在进制转换中,1个十六进制数可用4个二进制数表示,每个二进制数表示1位,因此1字节在内存中占8位。
在C++中,整数类型又可以分为有符号型和无符号型两种。有符号整数可用来表示负数与正数,而无符号整数则只能表示正数。它们有什么区别?在内存中又如何表示?本章我们一起揭开这些谜题。
2.1.1 无符号整数
在内存中,无符号整数的所有位都用来表示数值。以无符号整型数unsigned int为例,此类型的变量在内存中占4字节,由8个十六进制数组成,取值范围为0x00000000~0xFFFFFFFF,如果转换为十进制数,则表示范围为0~4294967295。
当无符号整型不足32位时,用0来填充剩余高位,直到占满4字节内存空间为止。例如,数字5对应的二进制数为101,只占了3位,按4字节大小保存,剩余29个高位将用0填充,填充后结果为:00000000000000000000000000000101;转换成十六进制数0x00000005之后,在内存中以“小尾方式”存放。“小尾方式”存放以字节为单位,按照数据类型长度,低数据位放在内存的低端,高数据位放在内存的高端,如0x12345678,会存储为78 56 34 12。相应地,在其他计算机体系中,也有“大尾方式”,其数据存储方式和“小尾方式”相反,高数据位放在内存的低端,低数据位放在内存的高端,如0x12345678,会存储为12 34 56 78。如果大家对此仍有疑问,可以查阅2.7节。
无符号整数不存在正负之分,都是正数,故无符号整数在内存中都是以真值的形式存放的,每一位都可以参与数据表达。无符号整数可表示的正数范围是补码的1倍。
2.1.2 有符号整数
有符号整数中用来表示符号的是最高位,即符号位。最高位为0表示正数,最高位为1表示负数。有符号整数int在内存中同样占4字节,但由于最高位为符号位,不能用来表示数值,因此有符号整数的取值范围要比无符号整数取值范围少1位,即0x80000000~0x7FFFFFFF,如果转换为十进制数,则表示范围为-2 147 483 648~2 147 483 647。
在有符号整数中,正数的表示区间为0x00000000~0x7FFFFFFF;负数的表示区间为0x80000000~0xFFFFFFFF。
负数在内存中都是以补码形式存放的,补码的规则是用0减去这个数的绝对值,也可以简单地表达为对这个数值取反加1。例如,对于-3,可以表达为0-3,而0xFFFFFFFD+3等于0(进位丢失),所以-3的补码就是0xFFFFFFFD了。相应地,0xFFFFFFFD作为一个补码,最高位为1,视为负数,转换回真值同样也可以用0-0xFFFFFFFD的方式表示,于是得到-3。为了计算方便,人们也常用取反加一的方式求补码,因为对于任何4字节的数值x,都有x+x(反)=0xFFFFFFFF,于是x+x(反)+1=0,接下来就可以推导出0-x=x(反)+1了。
在我们讨论的C/C++中,有符号整数都是以补码形式存储的,而且在几乎所有的编程语言中都是如此,这是为什么呢?因为计算机只会做加法,所以需要把减法转换为加法。
设有符号数x,y,求x-y的值,我们可以推导出x-y=x+(0-|y|),根据补码的规则,当y为负数的时候,0-|y|等价于y的补码。对于y的补码,我们记为y(补),所以x-y=x+y(补)。
例如,(3-2)可转换成(3+(-2)),运算过程为3的十六进制原码0x00000003加上-2的十六进制补码0xFFFFFFFE,从而得到0x100000001。由于存储范围为4字节大小,两数相加后产生了进位,超出了存储范围,超出的1将被舍弃。进位被舍弃后,结果为0x00000001。
值得一提的是,对于4字节补码,0x80000000所表达的意义可以是负数0,也可以是0x80000001减去1。因为0的正负值是相等的,没有必要再用负数0,所以就把这个值的意义规定为0x80000001减去1,这样0x80000000也就成为4字节负数的最小值了。这也是有符号整数的取值范围中,负数区间总是比正数区间多一个最小值的原因。
在数据分析中,如果将内存解释为有符号整数,则查看用十六进制数表示时的最高位,最高位小于8则为正数,大于8则为负数。如果是负数,则须转换成真值,从而得到对应的负数数值,如图2-1所示。

图2-1 有符号负数的内存信息
在图2-1中,地址0x010FFCC4对应的4字节为变量var的数据信息。var为一个有符号整数,在内存中的信息为0xFFFFFFFF,最高位为1,说明变量var为一个负数。按照转换规则,内存中存放的十六进制数为一个补码,须转换成真值再进行解释。0减去0xFFFFFFFF,或者对0xFFFFFFFF取反加1,都可以得到真值-1。
那么,如何判断一段数据是有符号类型还是无符号类型呢?这就需要查看指令或者已知的函数操作内存地址的方式,根据操作方式或函数相关定义得出该地址的数据类型。如API调用MessageBoxA,它有4个参数,查看帮助手册得知,第4个参数为一个无符号整数,从而可分析出这个传入数值的类型。
有符号整数在算术运算中有许多特殊之处,更多有关有符号整数的操作及识别过程的内容请参考第4章。
2.2 浮点数类型
计算机也需要运算和存储数学中的实数。在计算机的发展过程中,曾产生过多种存储实数的方式,有的现在已经很少使用了。不管如何存储,都可以将其划分为定点实数存储方式和浮点实数存储方式两种。所谓定点实数,就是约定整数位和小数位的长度,比如用4字节存储实数,我们可以约定两个高字节存放整数部分,两个低字节存储小数部分。
这样做的好处是计算效率高,缺点也显而易见:存储不灵活,比如我们想存储65536.5,由于整数的表达范围超过了2字节,就无法用定点实数存储方式了。对应地,也有浮点实数存储方式,道理很简单,就是用一部分二进制位存放小数点的位置信息,我们可以称之为“指数域”,其他的数据位用来存储没有小数点时的数据和符号,我们可以称之为“数据域”“符号域”。在访问时取得指数域,与数据域运算后得到真值,如67.625,利用浮点实数存储方式,数据域可以记录为67625,小数点的位置可以记为10的-3次方,对该数进行访问时计算一下即可。
浮点实数存储方式的优缺点和定点实数存储方式的正好相反。在80286之前,程序员常常为实数的计算伤脑筋,而后来出现的浮点协处理器,可以协助主处理器分担浮点运算,程序员计算实数的效率因此得到提升,于是浮点实数存储方式也就普及开来,成为现在主流的实数存储方式。但是,在一些条件恶劣的嵌入式开发场合,仍可看到定点实数的存储和使用。
在C/C++中,使用浮点方式存储实数,用两种数据类型来保存浮点数:float(单精度)、double(双精度)。float在内存中占4字节,double在内存中占8字节。由于占用空间大,double可描述的精度更高。这两种数据类型在内存中同样以十六进制方式存储,但与整型类型有所不同。
整型类型是将十进制转换成二进制保存在内存中,以十六进制方式显示。浮点类型并不是将一个浮点小数直接转换成二进制数保存,而是将浮点小数转换成的二进制码重新编码,再进行存储。C/C++的浮点数是有符号的。
在C/C++中,将浮点数强制转换为整数时,不会采用数学上四舍五入的方式,而是舍弃掉小数部分(第4章会提到的“向零取整”),不会进位。
浮点数的操作不会用到通用寄存器,而是会使用浮点协处理器的浮点寄存器,专门对浮点数进行运算处理。
2.2.1 浮点数的编码方式
浮点数编码转换采用的是IEEE规定的编码标准,float和double这两种类型数据的转换原理相同,但由于表示的范围不一样,编码方式有些许区别。IEEE规定的浮点数编码会将一个浮点数转换为二进制数。以科学记数法划分,将浮点数拆分为3部分:符号、指数、尾数。
1. float类型的IEEE编码
float类型在内存中占4字节(32位)。最高位用于表示符号,在剩余的31位中,从左向右取8位表示指数,其余表示尾数,如图2-2所示。

图2-2 float类型的二进制表示说明
在进行二进制转换前,需要对单精度浮点数进行科学记数法转换。例如,将float类型的12.25f转换为IEEE编码,须将12.25f转换成对应的二进制数1100.01,整数部分为1100,小数部分为01;小数点向左移动,每移动1次,指数加1,移动到除符号位的最高位为1处,停止移动,这里移动3次。对12.25f进行科学记数法转换后二进制部分为1.10001,指数部分为3。在IEEE编码中,由于在二进制情况下,最高位始终为1,为一个恒定值,故将其忽略不计。这里是一个正数,所以符号位添加0。
12.25f经IEEE转换后各位如下。
符号位:0。
指数位:十进制3+127=130,转换为二进制为10000010。
尾数位:10001 000000000000000000(当不足23位时,低位补0填充)。
由于尾数位中最高位1是固定值,故忽略不计,只要在转换回十进制数时加1即可。为什么指数位要加127呢?这是因为指数可能出现负数,十进制数127可表示为二进制数01111111,IEEE编码方式规定,当指数小于0111111时为一个负数,反之为正数,因此01111111为0。
将示例中转换后的符号位、指数位和尾数位按二进制拼接在一起,就成为一个完整的IEEE浮点编码:01000001010001000000000000000000。转换成十六进制数为0x41440000,内存中以小尾方式进行排列,故为00 00 44 41,分析结果如图2-3所示。

图2-3 单精度浮点数12.25f转换为IEEE编码
上面演示了符号位为正、指数位也为正的情况。
那么什么情况下指数位可以为负呢?根据科学记数法,小数点向整数部分移动时,指数做加法。相反,小数点向小数部分移动时,指数需要以0起始做减法。浮点数-0.125f转换IEEE编码后,将会是一个符号位为1、指数部分为负的小数。-0.125f经转换后二进制部分为0.001,用科学记数法表示为1.0,指数为-3。
-0.125fIEEE转换后各位的情况如下。
符号位:1。
指数位:十进制127+(-3),转换为二进制是01111100,如果不足8位,则高位补0。
尾数位:00000000000000000000000。
-0.125f转换后的IEEE编码二进制拼接为10111110000000000000000000000000。转换成十六进制数为0xBE000000,内存中显示为00 00 00 BE,分析结果如图2-4所示。

图2-4 单精度浮点数-0.125f转换为IEEE编码
上面的两个浮点数小数部分转换为二进制时都是有穷的,如果小数部分转换为二进制时得到一个无穷值,则会根据尾数部分的长度舍弃多余的部分。如单精度浮点数1.3f,小数部分转换为二进制就会产生无穷值,依次转换为0.3、0.6、1.2、0.4、0.8、1.6、1.2、0.4、0.8……转换后得到的二进制数为1.01001100110011001100110,到第23位终止,尾数部分无法保存更大的值。
1.3f经IEEE转换后各位的情况如下。
符号位:0。
指数位:十进制0+127,转换二进制01111111。
尾数位:01001100110011001100110。
1.3f转换后的IEEE编码二进制拼接为00111111101001100110011001100110。转换成十六进制数为0x3FA66666,在内存中显示为66 66 A6 3F。由于在转换二进制过程中产生了无穷值,舍弃了部分位数,所以进行IEEE编码转换后得到的是一个近似值,存在一定的误差。再次将这个IEEE编码值转换成十进制小数,得到的值为1.2516582,四舍五入保留一位小数之后为1.3。这就解释了为什么C++在比较浮点数值是否为0时,要做一个区间比较而不是直接进行等值比较。正确浮点数比较的代码见代码清单2-1。
代码清单2-1 正确浮点数比较
float f1 = 0.0001f; // 精确范围
if (f2 >= -f1 && f2 <= f1)
{
// f1等于0
}
2. double类型的IEEE编码
前文讲解了单精度浮点类型的IEEE编码。double类型和float类型大同小异,只是double类型表示的范围更大,占用空间更多,是float类型所占空间的两倍。当然,精准度也会更高。
double类型占8字节的内存空间,同样,最高位也用于表示符号,指数位占11位,剩余52位表示位数。
在float类型中,指数位范围用8位表示,加127后用于判断指数符号。在double类型中,由于扩大了精度,因此指数范围使用11位正数表示,加1023后可用于指数符号判断。
double类型的IEEE编码转换过程与float类型一样,读者可根据float类型的转换流程来转换double类型,此处不再赘述。
2.2.2 基本的浮点数指令
前面介绍了浮点数的编码方式,下面我们来学习浮点数指令。浮点数的操作指令与普通数据类型不同,浮点数操作是通过浮点寄存器实现的,而普通数据类型使用的是通用寄存器,它们分别使用两套不同的指令。
在早期CPU中,浮点寄存器是通过栈结构实现的,由ST(0)~ST(7)共8个栈空间组成,每个浮点寄存器占8字节。每次使用浮点寄存器都是率先使用ST(0),而不能越过ST(0)直接使用ST(1)。浮点寄存器的使用就是压栈、出栈的过程。当ST(0)中存在数据时,执行压栈操作后,ST(0)中的数据将装入ST(1)中,如无出栈操作,将按顺序向下压栈,直到将浮点寄存器占满为止。常用浮点数指令的介绍如表2-1所示,其中,IN表示操作数入栈,OUT表示操作数出栈。
表2-1 常用浮点数指令表

其他运算指令和普通指令类似,只须在前面加F即可,如FSUB和FSUBP等。
在使用浮点指令时,都要先利用ST(0)进行运算。当ST(0)中有值时,便会将ST(0)中的数据顺序向下存放到ST(1)中,然后再将数据放入ST(0)。如果再次操作ST(0),则会先将ST(1)中的数据放入ST(2),然后将ST(0)中的数据放入ST(1),最后将新的数据存放到ST(0)。以此类推,在8个浮点寄存器都有值的情况下继续向ST(0)中的存放数据,这时会丢弃ST(7)中的数据信息。
1997年开始,Intel和AMD都引入了媒体指令(MMX),这些指令允许多个操作并行,允许对多个不同的数据并行执行同一操作。近年来,这些扩展有了长足的发展。名字经过了一系列的修改,从MMX到SSE(流SIMD扩展),以及最新的AVX(高级向量扩展)。每一代都有一些不同的版本。每个扩展都用来管理寄存器中的数据,这些寄存器在MMX中被称为MM寄存器,在SSE中被称为XMM寄存器,在AVX中被称为YMM寄存器。MM寄存器是64位的,XMM是128位的,而YMM是256位的。每个YMM寄存器可以存放8个32位值或4个64位值,可以是整数,也可以是浮点数。YMM寄存器一共有16个(YMM0~YMM15),而XMM是YMM的低128位。常用SSE浮点数指令的介绍如表2-2所示。
表2-2 常用SSE浮点数指令表

下面通过一个简单的示例介绍各指令的使用流程,帮助读者熟悉浮点指令的使用方法,如代码清单2-2所示。
代码清单2-2 Debug版float指令练习
// C++源码
#include <stdio.h>
int main(int argc, char* argv[]) {
float f = (float)argc;
printf("%f", f);
argc = (int)f;
printf("%d", argc);
return 0;
}
//x86_vs对应汇编代码讲解
00401000 push ebp
00401001 mov ebp, esp
00401003 push ecx
00401004 cvtsi2ss xmm0, dword ptr [ebp+8]
00401009 movss [ebp-4], xmm0 ;f = (float)argc;
0040100E cvtss2sd xmm0, dword ptr [ebp-4] ;xmm0=(double)f
00401013 sub esp, 8
00401016 movsd qword ptr [esp], xmm0 ;参数2 xmm0入栈
0040101B push offset asc_412160 ;参数1 "%f"入栈
00401020 call sub_401090 ;调用printf函数
00401025 add esp, 0Ch ;平衡栈
00401028 cvttss2si eax, dword ptr [ebp-4]
0040102D mov [ebp+8], eax ;argc=(int)f
00401030 mov ecx, [ebp+8]
00401033 push ecx ;参数2 argc入栈
00401034 push offset aD ;参数1 "%d"入栈
00401039 call sub_401090 ;调用printf函数
0040103E add esp, 8 ;平衡栈
00401041 xor eax, eax
00401043 mov esp, ebp
00401045 pop ebp
00401046 retn
//x86_gcc对应汇编代码讲解
00401510 push ebp
00401511 mov ebp, esp
00401513 and esp, 0FFFFFFF0h ;栈对齐
00401516 sub esp, 30h
00401519 call ___main ;调用初始化函数
0040151E fild [ebp+8] ;argc转换双精度数入栈
00401521 fstp dword ptr [esp+2Ch] ;f=(float)argc
00401525 fld dword ptr [esp+2Ch]
00401529 fstp qword ptr [esp+4] ;参数2 (double)f入栈
0040152D mov dword ptr [esp], offset asc_404000 ;参数1 "%f"入栈
00401534 call _printf ;调用printf函数
00401539 fld dword ptr [esp+2Ch] ;f入栈st(0)
0040153D fnstcw word ptr [esp+1Eh]
00401541 movzx eax, word ptr [esp+1Eh]
00401546 or ah, 0Ch
00401549 mov [esp+1Ch], ax
0040154E fldcw word ptr [esp+1Ch] ;浮点异常检查代码
00401552 fistp [ebp+8] ;argc=(int)f
00401555 fldcw word ptr [esp+1Eh]
00401559 mov eax, [ebp+8]
0040155C mov [esp+4], eax ;参数2 argc入栈
00401560 mov dword ptr [esp], offset aD ;参数1 "%d"入栈
00401567 call _printf ;调用printf函数
0040156C mov eax, 0
00401571 leave
00401572 retn
//x86_clang对应汇编代码讲解
00401000 push ebp
00401001 mov ebp, esp
00401003 sub esp, 24h
00401006 mov eax, [ebp+0Ch] ;eax=argv
00401009 mov ecx, [ebp+8] ;ecx=argc
0040100C mov dword ptr [ebp-4], 0
00401013 mov edx, [ebp+8] ;edx=argc
00401016 cvtsi2ss xmm0, edx ;xmm0=(int)argc
0040101A movss dword ptr [ebp-8], xmm0 ;f=(int)argc
0040101F movss xmm0, dword ptr [ebp-8]
00401024 cvtss2sd xmm0, xmm0 ;xmm0=(double)f
00401028 lea edx, asc_412160 ;edx="%f"
0040102E mov [esp], edx ;参数1 "%f"入栈
00401031 movsd qword ptr [esp+4], xmm0 ;参数2 xmm0入栈
00401037 mov [ebp-0Ch], eax
0040103A mov [ebp-10h], ecx
0040103D call sub_401070 ;调用printf函数
00401042 cvttss2si ecx, dword ptr [ebp-8]
00401047 mov [ebp+8], ecx ;argc=(int)f
0040104A mov ecx, [ebp+8]
0040104D lea edx, aD ;edx="%d"
00401053 mov [esp], edx ;参数1 "%d"入栈
00401056 mov [esp+4], ecx ;参数2 argc入栈
0040105A mov [ebp-14h], eax
0040105D call sub_401070 ;调用printf函数
00401062 xor ecx, ecx
00401064 mov [ebp-18h], eax
00401067 mov eax, ecx
00401069 add esp, 24h
0040106C pop ebp
0040106D retn
//x64_vs对应汇编代码讲解
0000000140001000 mov [rsp+10h], rdx
0000000140001005 mov [rsp+8], ecx
0000000140001009 sub rsp, 38h
000000014000100D cvtsi2ss xmm0, dword ptr [rsp+40h] ;xmm0=(float)argc
0000000140001013 movss dword ptr [rsp+20h], xmm0 ;f=(float)argc
0000000140001019 cvtss2sd xmm0, dword ptr [rsp+20h] ;xmm0=(double)f
000000014000101F movaps xmm1, xmm0
0000000140001022 movq rdx, xmm1 ;参数2 (double)f
0000000140001027 lea rcx, asc_1400122C0 ;参数1 "%f"
000000014000102E call sub_1400010C0 ;调用printf函数
0000000140001033 cvttss2si eax, dword ptr [rsp+20h] ;eax=(int)f
0000000140001039 mov [rsp+40h], eax ;argc=(int)f
000000014000103D mov edx, [rsp+40h] ;参数2 argc
0000000140001041 lea rcx, aD ;参数1 "%d"
0000000140001048 call sub_1400010C0 ;调用printf函数
000000014000104D xor eax, eax
000000014000104F add rsp, 38h
0000000140001053 retn;
//x64_gcc对应汇编代码讲解
0000000000401550 push rbp
0000000000401551 mov rbp, rsp
0000000000401554 sub rsp, 30h
0000000000401558 mov [rbp+10h], ecx
000000000040155B mov [rbp+18h], rdx
000000000040155F call __main ;调用初始化函数
0000000000401564 cvtsi2ss xmm0, dword ptr [rbp+10h]
0000000000401569 movss dword ptr [rbp-4], xmm0 ;f=(float)argc
000000000040156E cvtss2sd xmm0, dword ptr [rbp-4] ;xmm0=(double)f
0000000000401573 movq rax, xmm0
0000000000401578 mov rdx, rax
000000000040157B movq xmm1, rdx
0000000000401580 mov rdx, rax ;参数2 (double)f
0000000000401583 lea rcx, Format ;参数1 "%f"
000000000040158A call printf ;调用printf函数
000000000040158F movss xmm0, dword ptr [rbp-4]
0000000000401594 cvttss2si eax, xmm0
0000000000401598 mov [rbp+10h], eax ;argc=(int)f
000000000040159B mov edx, [rbp+10h] ;参数1 argc
000000000040159E lea rcx, aD ;参数2 "%d"
00000000004015A5 call printf ;调用printf函数
00000000004015AA mov eax, 0
00000000004015AF add rsp, 30h
00000000004015B3 pop rbp
00000000004015B4 retn
//x64_clang对应汇编代码讲解
0000000140001000 sub rsp, 48h
0000000140001004 mov dword ptr [rsp+44h], 0
000000014000100C mov [rsp+38h], rdx ;保存argv
0000000140001011 mov [rsp+34h], ecx ;保存argc
0000000140001015 mov ecx, [rsp+34h]
0000000140001019 cvtsi2ss xmm0, ecx
000000014000101D movss dword ptr [rsp+30h], xmm0 ;f=(int)argc
0000000140001023 movss xmm0, dword ptr [rsp+30h]
0000000140001029 cvtss2sd xmm0, xmm0 ;xmm0=(double)f
000000014000102D lea rcx, asc_1400122C0 ;参数1 "%f"
0000000140001034 movaps xmm1, xmm0
0000000140001037 movq rdx, xmm0 ;参数2 (double)f
000000014000103C call sub_140001070 ;调用printf函数
0000000140001041 cvttss2si r8d, dword ptr [rsp+30h]
0000000140001048 mov [rsp+34h], r8d ;argc=(int)f
000000014000104D mov edx, [rsp+34h] ;参数2 argc
0000000140001051 lea rcx, aD ;参数1 "%d"
0000000140001058 mov [rsp+2Ch], eax
000000014000105C call sub_140001070 ;调用printf函数
0000000140001061 xor edx, edx
0000000140001063 mov [rsp+28h], eax
0000000140001067 mov eax, edx
0000000140001069 add rsp, 48h
000000014000106D retn
笔记--------------------------------------------------start

在查看反汇编的时候把【显示符号名】这个选项去掉,否则如上所示,看到的都是名,看得有点云里雾里,去掉以后就是显示的就是地址与书本的代码对照看比较明白

x86_vs_debug反汇编代码
int main(int argc, char* argv[])
{
004D1840 push ebp
004D1841 mov ebp,esp
004D1843 sub esp,0CCh
004D1849 push ebx
004D184A push esi
004D184B push edi
004D184C lea edi,[ebp-0Ch]
004D184F mov ecx,3
004D1854 mov eax,0CCCCCCCCh
004D1859 rep stos dword ptr es:[edi]
004D185B mov ecx,4DC003h
004D1860 call 004D1316
float f = (float)argc;
004D1865 cvtsi2ss xmm0,dword ptr [ebp+8] ;cvtsi2ss:将整数转换为单精度数
004D186A movss dword ptr [ebp-8],xmm0 ;传送单精度数
printf("%f", f); ;cvtss2sd:将一个单精度浮点数转换为双精度浮点数
004D186F cvtss2sd xmm0,dword ptr [ebp-8] ;xmm0=(double)f
004D1874 sub esp,8
004D1877 movsd mmword ptr [esp],xmm0 ;movsd:传送双精度浮点数, ;参数2 xmm0入栈
004D187C push 4D7B30h ;参数1 "%f" 字符串 入栈 (看下面内存地址)
004D1881 call 004D10CD ;调用printf函数
004D1886 add esp,0Ch ;平衡栈
argc = (int)f; ;cvttss2si: 用截断的方法将单精度数转为整数
004D1889 cvttss2si eax,dword ptr [ebp-8] ;eax=(int)f
004D188E mov dword ptr [ebp+8],eax ;argc = eax, => argc=(int)f
printf("%d", argc);
004D1891 mov eax,dword ptr [ebp+8]
004D1894 push eax ;参数2 argc入栈
004D1895 push 4D7B34h ;参数1 "%d" 字符串 入栈 (看下面内存地址)
004D189A call 004D10CD ;调用printf函数
004D189F add esp,8 ;平衡栈
return 0;
004D18A2 xor eax,eax
}

、
x64_vs_debug反汇编代码
int main(int argc, char* argv[])
{
00007FF7D0311850 mov qword ptr [rsp+10h],rdx
00007FF7D0311855 mov dword ptr [rsp+8],ecx
00007FF7D0311859 push rbp
00007FF7D031185A push rdi
00007FF7D031185B sub rsp,108h
00007FF7D0311862 lea rbp,[rsp+20h]
00007FF7D0311867 lea rcx,[00007FF7D0321003h]
00007FF7D031186E call 00007FF7D0311357
float f = (float)argc; ;cvtsi2ss:将整数转换为单精度数
00007FF7D0311873 cvtsi2ss xmm0,dword ptr [rbp+0000000000000100h] ;xmm0=(float)argc
00007FF7D031187B movss dword ptr [rbp+4],xmm0 ;movss:传送单精度数 f=(float)argc
printf("%f", f); ;cvtss2sd:将一个单精度浮点数转换为双精度浮点数
00007FF7D0311880 cvtss2sd xmm0,dword ptr [rbp+4] ;xmm0=(double)f
00007FF7D0311885 movaps xmm1,xmm0 ;movaps: 传送对齐的封装好的单精度数 xmm1=xmm0
00007FF7D0311888 movq rdx,xmm1 ;movq:移动四字 rdx=参数2 (double)f
00007FF7D031188D lea rcx,[00007FF7D0319C24h] ; 参数1 "%f" (看下面截图内存)
00007FF7D0311894 call 00007FF7D031118B ;调用 printf函数
argc = (int)f; ;cvttss2si: 用截断的方法将单精度数转为整数
00007FF7D0311899 cvttss2si eax,dword ptr [rbp+4] ;eax=(int)f
00007FF7D031189E mov dword ptr [rbp+0000000000000100h],eax ;argc=(int)f
printf("%d", argc);
00007FF7D03118A4 mov edx,dword ptr [rbp+0000000000000100h] ;参数2 argc
00007FF7D03118AA lea rcx,[00007FF7D0319C28h] ;参数1 "%d" (看下面截图内存)
00007FF7D03118B1 call 00007FF7D031118B ;调用 printf函数
return 0;
00007FF7D03118B6 xor eax,eax
}

笔记--------------------------------------------------end
代码清单2-2通过浮点数与整数、整数与浮点数间的互相转换演示了数据传送类型的浮点指令的使用方法。从示例中可以发现,float类型的浮点数虽然占4字节,但是使用浮点栈将以8字节方式进行处理,而使用媒体寄存器则以4字节处理。当浮点数作为参数时,并不能直接压栈,PUSH指令只能传入4字节数据到栈中,这样会丢失4字节数据。这就是使用printf函数以整数方式输出浮点数时会产生错误的原因。printf以整数方式输出时,将对应参数作为4字节数据长度,按补码方式解释,而真正压入的参数为浮点类型时,却是8字节长度,需要按浮点编码方式解释。
浮点数作为返回值的情况也是如此,在32位程序中使用浮点栈st(0)作为返回值同样需要传递8字节数据,64位程序中使用媒体寄存器xmm0作为返回值只需要传递4字节,如代码清单2-3所示。
代码清单2-3 浮点数作为返回值
// C++源码
#include <stdio.h>
float getFloat() {
return 12.25f;
}
int main(int argc, char* argv[]) {
float f = getFloat();
return 0;
}
//x86_vs对应汇编代码讲解
00401010 push ebp
00401011 mov ebp, es
00401013 push ecx ;参数1 argc入栈
00401014 call sub_401000 ;调用getFloat()函数
00401019 fstp dword ptr [ebp-4] ;f=getFloat()将st(0)的双精度数转换为单精度数
0040101C xor eax, eax
0040101E mov esp, ebp
00401020 pop ebp
00401021 retn
00401000 push ebp
00401001 mov ebp, esp
00401003 fld ds:flt_40D150 ;将返回值入栈到st(0)中,单精度数转换为双精度数入栈
00401009 pop ebp
0040100A retn ;getFloat()函数返回
//x86_gcc对应汇编代码讲解
00401517 push ebp
00401518 mov ebp, esp
0040151A and esp, 0FFFFFFF0h ;栈对齐
0040151D sub esp, 10h
00401520 call ___main ;调用初始化函数
00401525 call __Z8getFloatv ;调用getFloat()函数
0040152A fstp dword ptr [esp+0Ch] ;f=getFloat()将st(0)的双精度数转换为单精度数
0040152E mov eax, 0
00401533 leave
00401534 retn
00401510 fld ds:flt_404000 ;将返回值入栈到st(0)中,单精度数转换为双精度数入栈
00401516 retn ;getFloat()函数返回
//x86_clang对应汇编代码讲解
00401010 push ebp
00401011 mov ebp, esp
00401013 sub esp, 14h
00401016 mov eax, [ebp+0Ch] ;eax=argv
00401019 mov ecx, [ebp+8] ;ecx=argc
0040101C mov dword ptr [ebp-4], 0
00401023 mov [ebp-10h], eax
00401026 mov [ebp-14h], ecx
00401029 call sub_401000 ;调用getFloat()函数
0040102E fstp dword ptr [ebp-0Ch] ;f=getFloat()将st(0)的双精度数转换为单精度数
00401031 movss xmm0, dword ptr [ebp-0Ch]
00401036 xor eax, eax
00401038 movss dword ptr [ebp-8], xmm0
0040103D add esp, 14h
00401040 pop ebp
00401041 retn
00401000 push ebp
00401001 mov ebp, esp
00401003 fld ds:flt_40D150 ;将返回值入栈到st(0)中,单精度数转换为双精度数入栈
00401009 pop ebp
0040100A retn ;getFloat()函数返回
//x64_vs对应汇编代码讲解
0000000140001010 mov [rsp+10h], rdx
0000000140001015 mov [rsp+8], ecx
0000000140001019 sub rsp, 38h
000000014000101D call sub_140001000 ;调用getFloat()函数
0000000140001022 movss dword ptr [rsp+20h], xmm0;f=getFloat()从xmm0获取返回值
0000000140001028 xor eax, eax
000000014000102A add rsp, 38h
000000014000102E retn
0000000140001000 movss xmm0, cs:dword_14000D2C0;xmm0=12.25f
0000000140001008 retn ;getFloat()函数返回
//x64_gcc对应汇编代码讲解
0000000000401559 push rbp
000000000040155A mov rbp, rsp
000000000040155D sub rsp, 30h
0000000000401561 mov [rbp+10h], ecx
0000000000401564 mov [rbp+18h], rdx
0000000000401568 call __main ;调用初始化函数
000000000040156D call _Z8getFloatv ;调用getFloat()函数
0000000000401572 movd eax, xmm0
0000000000401576 mov [rbp-4], eax ;f=getFloat()从xmm0获取返回值
0000000000401579 mov eax, 0
000000000040157E add rsp, 30h
0000000000401582 pop rbp
0000000000401583 retn
0000000000401550 movss xmm0, cs:dword_404000;xmm0=12.25f
0000000000401558 retn ;getFloat()函数返回
//x64_clang对应汇编代码讲解
0000000140001010 sub rsp, 38h
0000000140001014 mov dword ptr [rsp+34h], 0
000000014000101C mov [rsp+28h], rdx
0000000140001021 mov [rsp+24h], ecx
0000000140001025 call sub_140001000 ; 调用getFloat()函数
000000014000102A xor eax, eax
000000014000102C movss dword ptr [rsp+20h], xmm0;f=getFloat()从xmm0获取返回值
0000000140001032 add rsp, 38h
0000000140001036 retn
0000000140001000 movss xmm0, cs:dword_14000D2C0;xmm0=12.25f
0000000140001008 retn ;getFloat()函数返回
笔记--------------------------------------------------start
x86_vs_debug反汇编代码
float getFloat() {
009E1E10 push ebp
009E1E11 mov ebp,esp
009E1E13 sub esp,0C0h
009E1E19 push ebx
009E1E1A push esi
009E1E1B push edi
009E1E1C mov edi,ebp
009E1E1E xor ecx,ecx
009E1E20 mov eax,0CCCCCCCCh
009E1E25 rep stos dword ptr es:[edi]
009E1E27 mov ecx,9EC003h
009E1E2C call 009E130C
return 12.25f; ;fld:浮点数入栈, 转换为 80位扩展精度 存入 ST(0)
009E1E31 fld dword ptr ds:[009E7B30h] ;将返回值入栈到st(0)中,单精度数转换为双精度数入栈
}
009E1E37 pop edi
009E1E38 pop esi
009E1E39 pop ebx
009E1E3A add esp,0C0h
009E1E40 cmp ebp,esp
009E1E42 call 009E1235
009E1E47 mov esp,ebp
009E1E49 pop ebp
009E1E4A ret
......
009E112C jmp 009E1E10
......
int main(int argc, char* argv[])
{
009E17A0 push ebp
009E17A1 mov ebp,esp
009E17A3 sub esp,0CCh
009E17A9 push ebx
009E17AA push esi
009E17AB push edi
009E17AC lea edi,[ebp-0Ch]
009E17AF mov ecx,3
009E17B4 mov eax,0CCCCCCCCh
009E17B9 rep stos dword ptr es:[edi]
009E17BB mov ecx,9EC003h
009E17C0 call 009E130C
float f = getFloat();
009E17C5 call 009E112C ;调用getFloat()函数 jmp 009E1E10
009E17CA fstp dword ptr [ebp-8] ;fstp:浮点数出栈
return 0;
009E17CD xor eax,eax
}

x64_vs_debug反汇编代码
float getFloat() {
00007FF7B2E71740 push rbp
00007FF7B2E71742 push rdi
00007FF7B2E71743 sub rsp,0F8h
00007FF7B2E7174A lea rbp,[rsp+30h]
00007FF7B2E7174F lea rcx,[00007FF7B2E81003h]
00007FF7B2E71756 call 00007FF7B2E71343
return 12.25f; ;movss:移动单精度浮点数
00007FF7B2E7175B movss xmm0,dword ptr [00007FF7B2E79BB0h] ;xmm0=12.25f
}
00007FF7B2E71763 lea rsp,[rbp+00000000000000C8h]
00007FF7B2E7176A pop rdi
00007FF7B2E7176B pop rbp
00007FF7B2E7176C ret
......
00007FF7B2E71140 jmp 00007FF7B2E71740
......
int main(int argc, char* argv[])
{
00007FF7B2E71790 mov qword ptr [rsp+10h],rdx
00007FF7B2E71795 mov dword ptr [rsp+8],ecx
00007FF7B2E71799 push rbp
00007FF7B2E7179A push rdi
00007FF7B2E7179B sub rsp,108h
00007FF7B2E717A2 lea rbp,[rsp+20h]
00007FF7B2E717A7 lea rcx,[00007FF7B2E81003h]
00007FF7B2E717AE call 00007FF7B2E71343
float f = getFloat();
00007FF7B2E717B3 call 00007FF7B2E71140 ;调用getFloat()函数
00007FF7B2E717B8 movss dword ptr [rbp+4],xmm0 ;f=getFloat()从xmm0获取返回值
return 0;
00007FF7B2E717BD xor eax,eax
}
00007FF7B2E717BF lea rsp,[rbp+00000000000000E8h]
00007FF7B2E717C6 pop rdi
00007FF7B2E717C7 pop rbp
00007FF7B2E717C8 ret
笔记--------------------------------------------------end
2.3 字符和字符串
字符串是由多个字符按照一定排列顺序组成的,在C++中,以'\0'作为字符串结束标记。每个字符都记录在一张表中,它们各自对应一个唯一编号,系统通过这些编号查找到对应的字符并显示。字符表格中的编号便是字符的编码格式。
2.3.1 字符的编码
在C++中,字符的编码格式分为两种:ASCII和Unicode。Unicode是ASCII的升级编码格式,它弥补了ASCII的不足,也是编码格式的发展趋势。
ASCII编码在内存中占1字节,由0~255之间的数字组成。每个数字表示一个符号,具体表示方式可查看ASCII表。由于ASCII编码也是由数字组成的,所以可以和整数互相转换,但整数不可超过ASCII的最大表示范围,因为多余部分将被舍弃。
由于ASCII原来的表示范围太小,只能表示26个英文字母和常用符号。在亚洲,ASCII的表示范围完全不够用,仅汉字就可以轻易占满ASCII编码。因此,占双字节、表示范围为0~65535的Unicode编码产生了。Unicode编码是世界通用的编码,ASCII编码也包含在其中。
使用char定义ASCII编码格式的字符,使用wchar_t定义Unicode编码格式的字符。wchar_t中保存ASCII编码,不足位补0,如字符'a'的ASCII编码为0x61,Unicode编码为0x0061。汉字的编码方式有些特殊,ASCII与Unicode都有与之匹配的编码格式。
在程序中使用中文、韩文、日文等时,经常出现显示的内容都是乱码的情况。这是因为系统缺少该语种字符表,这个字符表用于解释所需语种的字符编码,所以程序中的字符编码错误地对应到其他字符表中,显示出的文字是其他语种字符表中的信息。
ASCII编码与Unicode编码都可以用来存储汉字,但是它们对汉字的编码方式各不相同,所以存储同样的汉字,它们在内存中的编码是不同的,如图2-5所示

图2-5 汉字字符串
ASCII使用GB2312-80,又名汉字国标码,保存了6763个常用汉字编码,用两个字节表示一个汉字。在GB2312-80中用区和位来定位,第一个字节保存每个区,共94个区;第二个字节保存每个区中的位,共94位。详细信息可查看GB 2312-80编码的说明。
Unicode使用UCS-2编码格式,最多可存储65536个字符。汉字博大精深,其中有简体字、繁体字,还有网络中流行的火星文,它们的总和远远超过了UCS-2的存储范围,所以UCS-2编码格式中只保存了常用字。为了将所有的汉字都容纳进来,Unicode也采用了与ASCII类似的方式——用两个Unicode编码解释一个汉字,一般称之为UCS-4编码格式。UCS-2编码表的使用和ASCII编码表的使用是一样的,每个数字编号在表中对应一个汉字,从0x4E00到0x9520为汉字编码区。例如,在UCS-2中,“烫”字的编码为0x70EB。更多关于UCS-2编码的信息可查看随书文件的UCS-2编码表。
2.3.2 字符串的存储方式
字符串是由一系列按照一定的编码顺序线性排列的字符组成的。在图形中,两点可以确定一条直线;在程序中,只要知道字符串的首地址和结束地址就可以确定字符串的长度和大小。字符串的首地址很容易确定,因为在定义字符串的时候都会先指定好首地址。但是结束地址如何确定呢?有两种方法,一种是在首地址的4字节中保存字符串的总长度;另一种是在字符串的结尾处使用一个规定好的特殊字符,即结束符,下面分析这两种方法的优缺点。
保存总长度
优点:获取字符串长度时,不用遍历字符串中的每个字符,取得首地址的前n字节就可以得到字符串的长度(n的取值一般是1、2、4)。
缺点:字符串长度不能超过n字节的表示范围,且要多开销n字节空间保存长度。如果涉及通信,双方交互前必须事先知道通信字符串的长度。
结束符
优点:没有记录长度的开销,即不需要存储空间记录长度信息;另外,如果涉及通信,通信字符串可以根据实际情况随时结束,结束时附上结束符即可。
缺点:获取字符串长度需要遍历所有字符,寻找特殊结尾字符,在某些情况下处理效率低。
C++使用结束符'\0'作为字符串结束标志。ASCII编码使用一个字节'\0',Unicode编码使用两个字节'\0'。需要注意的是,不能使用处理ASCII编码的函数对Unicode编码进行处理,因为如果Unicode编码中出现了只占用1字节的字符,就会发生解释错误。ASCII与Unicode内存数据对比如图2-6所示。

图2-6 ASCII与Unicode内存数据对比
在程序中,一般都会使用一个变量来存放字符串中第一个字符的地址,以便于查找使用字符串。在程序中使用字符型指针char*、wchar_t*来保存字符串首地址。
图2-6为ASCII字符串ch与Unicode字符串wch的内存数据对比。ch所在地址0x01177b30以ASCII字符进行组合;wch所在地址0x01177bf4以Unicode字符进行组合,两个字节为一个字符。
字符串的识别也相对简单,同样是结合上下文,查看调用地址处对该地址的处理过程。在通常情况下,OllyDbg和IDA都会自动识别出程序中的字符串。在使用IDA的过程中,有时会无法识别字符串,此时可进行手动修改,如图2-7所示。

图2-7 未识别的字符串数据
在图2-7中,这段数据明显为一个字符串,但是IDA并没有分析出来,这时可以选中要分析的字符串的首地址,使用快捷键A,便可将分析地址到'\0'解释为字符串。图2-8所示为识别后的字符串数据。
![]()
图2-8 识别后的字符串数据
2.4 布尔类型
布尔类型用于判断执行结果,它的判断比较值只有两种情况:0与非0。C++中定义0为假,非0为真。使用bool定义布尔类型变量。布尔类型在内存中占1字节。因为布尔类型只比较两个结果值,真、假,所以实际上任何一种数据类型都可以将其代替,如整型、字符型,甚至可以用位代替。在实际案例中也是难以将布尔类型数据还原成源码的,但是可以将其还原成等价代码。布尔类型出现的场合都是在做真假判断,有了这个特性,还原成等价代码还是相对简单的。
2.5 地址、指针和引用
在C++中,地址标号使用十六进制表示,取一个变量的地址使用“&”符号,只有变量才存在内存地址,常量(见2.6节)没有地址(不包括const定义的伪常量)。例如,对于数字100,我们无法取出它的地址。取出的地址是一个常量值,无法再对其取地址了。
指针的定义使用“TYPE*”格式,TYPE为数据类型,任何数据类型都可以定义指针。指针本身也是一种数据类型,用于保存各种数据类型在内存中的地址。指针变量同样可以取出地址,所以会出现多级指针。
引用的定义格式为“TYPE&”,TYPE为数据类型。在C++中是不可以单独定义的,并且在定义时就要进行初始化。引用表示一个变量的别名,对它的任何操作本质上都是在操作它所表示的变量。详细讲解见2.5.3节。
2.5.1 指针和地址的区别
在32位应用程序中,地址是一个由32位二进制数字组成的值;在64位应用程序中,地址是一个由64位二进制数字组成的值。为了便于查看,转换成十六进制数字显示出来,用于标识内存编号。指针是用于保存这个编号的一种变量类型,它包含在内存中,所以可以取出指针类型变量在内存中的位置——地址。由于指针保存的数据都是地址,所以无论什么类型的指针,32位程序都占据4字节的内存空间,64位程序都占据8字节的内存空间,如图2-9所示。

图2-9 地址和指针
指针可以根据指针类型对地址对应的数据进行解释。而一个地址值无法单独解释数据,对于图2-9中0x0135FE04这个地址值,仅凭借它本身无法说明该地址处对应数据的信息。如果是在一个int类型的指针中保存这个地址,就可以将其看作int类型数据的起始地址,向后数4字节到0x0135FE08处,将0x0135FE04~0x0135FE08中的数据按整型存储方式解释,详见2.5.2节。
指针和地址之间的不同点如表2-3所示。
表2-3 指针和地址之间的不同点

指针和地址之间的共同点如表2-4所示。
表2-4 指针和地址之间的共同点

2.5.2 各类型指针的工作方式
在C++中,任何数据类型都有对应的指针类型。我们从前面的学习中了解到,指针保存的都是地址,为什么还需要类型作为修饰呢?因为我们需要用类型去解释这个地址中的数据。每种数据类型所占的内存空间不同,指针只保存了存放数据的首地址,而没有指明该在哪里结束。这时就需要根据对应的类型来寻找解释数据的结束地址。同一地址使用不同类型的指针进行访问,取出的内容就会不一样,如代码清单2-4所示。
代码清单2-4 不同类型指针访问同一地址
// C++源码
#include <stdio.h>
int main(int argc, char* argv[]) {
int n = 0x12345678;
int *p1 = &n;
char *p2 = (char*)&n;
short *p3 = (short*)&n;
printf("%08x \r\n", *p1);
printf("%08x \r\n", *p2);
printf("%08x \r\n", *p3);
return 0;
}
//x86_vs对应汇编代码讲解
00401000 push ebp
00401001 mov ebp, esp
00401003 sub esp, 10h ;申请局部变量
00401006 mov dword ptr [ebp-4], 12345678h ;n=0x12345678
0040100D lea eax, [ebp-4]
00401010 mov [ebp-8], eax ;p1=&n
00401013 lea ecx, [ebp-4]
00401016 mov [ebp-0Ch], ecx ;p2=&n
00401019 lea edx, [ebp-4]
0040101C mov [ebp-10h], edx ;p3=&n
0040101F mov eax, [ebp-8] ;取出地址
00401022 mov ecx, [eax] ;取4字节内容
00401024 push ecx ;参数2 *p1
00401025 push offset a08x ;参数1 "%08x \r\n"
0040102A call sub_4010A0 ;调用printf函数
0040102F add esp, 8 ;平衡栈
00401032 mov edx, [ebp-0Ch] ;取出地址
00401035 movsx eax, byte ptr [edx] ;取1字节内容,高位符号扩展成4字节
00401038 push eax ;参数2 *p2
00401039 push offset a08x_0 ;参数1 "%08x \r\n"
0040103E call sub_4010A0 ;调用printf函数
00401043 add esp, 8 ;平衡栈
00401046 mov ecx, [ebp-10h] ;取出地址
00401049 movsx edx, word ptr [ecx] ;取2字节内容,高位符号扩展成4字节
0040104C push edx ;参数2 *p3
0040104D push offset a08x_1 ;参数1 "%08x \r\n"
00401052 call sub_4010A0 ;调用printf函数
00401057 add esp, 8 ;平衡栈
0040105A xor eax, eax
0040105C mov esp, ebp
0040105E pop ebp
0040105F retn
//x86_gcc对应汇编代码讲解
00401510 push ebp
00401511 mov ebp, esp
00401513 and esp, 0FFFFFFF0h ;对齐栈
00401516 sub esp, 20h
00401519 call ___main ;调用初始化函数
0040151E mov dword ptr [esp+10h], 12345678h;n=0x12345678
00401526 lea eax, [esp+10h]
0040152A mov [esp+1Ch], eax ;p1=&n
0040152E lea eax, [esp+10h]
00401532 mov [esp+18h], eax ;p2=&n
00401536 lea eax, [esp+10h]
0040153A mov [esp+14h], eax ;p3=&n
0040153E mov eax, [esp+1Ch] ;取出地址
00401542 mov eax, [eax] ;取4字节内容
00401544 mov [esp+4], eax ;参数2 *p1
00401548 mov dword ptr [esp], offset a08x ;参数1 "%08x \r\n"
0040154F call _printf ;调用printf函数
00401554 mov eax, [esp+18h] ;取出地址
00401558 movzx eax, byte ptr [eax] ;取1字节内容
0040155B movsx eax, al ;高位符号扩展成4字节
0040155E mov [esp+4], eax ;参数2 *p2
00401562 mov dword ptr [esp], offset a08x ;参数1 "%08x \r\n"
00401569 call _printf ;调用printf函数
0040156E mov eax, [esp+14h] ;取出地址
00401572 movzx eax, word ptr [eax] ;取2字节内容,高位0扩展成4字节
00401575 cwde ;高位符号扩展成4字节
00401576 mov [esp+4], eax ;参数2 *p3
0040157A mov dword ptr [esp], offset a08x ;参数1 "%08x \r\n"
00401581 call _printf ;调用printf函数
00401586 mov eax, 0
0040158B leave
0040158C retn
//x86_clang对应汇编代码讲解
00401000 push ebp
00401001 mov ebp, esp
00401003 push esi
00401004 sub esp, 30h
00401007 mov eax, [ebp+0Ch]
0040100A mov ecx, [ebp+8]
0040100D mov dword ptr [ebp-8], 0
00401014 mov dword ptr [ebp-0Ch], 12345678h;n=0x12345678
0040101B lea edx, [ebp-0Ch]
0040101E mov [ebp-10h], edx ;p1=&n
00401021 mov esi, edx
00401023 mov [ebp-14h], esi ;p2=&n
00401026 mov [ebp-18h], edx ;p3=&n
00401029 mov edx, [ebp-10h] ;取出地址
0040102C mov edx, [edx] ;取4字节内容
0040102E lea esi, a08x
00401034 mov [esp], esi ;参数1 "%08x \r\n"
00401037 mov [esp+4], edx ;参数2 *p1
0040103B mov [ebp-1Ch], eax
0040103E mov [ebp-20h], ecx
00401041 call sub_401090 ;调用printf函数
00401046 mov ecx, [ebp-14h] ;取出地址
00401049 movsx ecx, byte ptr [ecx] ;取1字节内容,高位符号扩展成4字节
0040104C lea edx, a08x
00401052 mov [esp], edx ;参数1 "%08x \r\n"
00401055 mov [esp+4], ecx ;参数2 *p2
00401059 mov [ebp-24h], eax
0040105C call sub_401090 ;调用printf函数
00401061 mov ecx, [ebp-18h] ;取出地址
00401064 movsx ecx, word ptr [ecx] ;取出2字节的内容,高位符号扩展成4字节
00401067 lea edx, a08x
0040106D mov [esp], edx; ;参数1 "%08x \r\n"
00401070 mov [esp+4], ecx ;参数2 *p3
00401074 mov [ebp-28h], eax
00401077 call sub_401090 ;调用printf函数
0040107C xor ecx, ecx
0040107E mov [ebp-2Ch], eax
00401081 mov eax, ecx
00401083 add esp, 30h
00401086 pop esi
00401087 pop ebp
00401088 retn
//x64_vs对应汇编代码讲解
0000000140001000 mov [rsp+10h], rdx
0000000140001005 mov [rsp+8], ecx
0000000140001009 sub rsp, 48h
000000014000100D mov dword ptr [rsp+20h], 12345678h; n=0x12345678
0000000140001015 lea rax, [rsp+20h]
000000014000101A mov [rsp+28h], rax ;p1=&n
000000014000101F lea rax, [rsp+20h]
0000000140001024 mov [rsp+30h], rax ;p2=&n
0000000140001029 lea rax, [rsp+20h]
000000014000102E mov [rsp+38h], rax ;p3=&n
0000000140001033 mov rax, [rsp+28h] ;取出地址
0000000140001038 mov edx, [rax] ;参数2取出4字节内容 *p1
000000014000103A lea rcx, a08x ;参数1 "%08x \r\n"
0000000140001041 call sub_1400010E0 ;调用printf函数
0000000140001046 mov rax, [rsp+30h] ;取出地址
000000014000104B movsx eax, byte ptr [rax] ;取出1字节的内容,高位符号扩展成4字节
000000014000104E mov edx, eax ;参数2 *p2
0000000140001050 lea rcx, a08x_0 ;参数1 "%08x \r\n"
0000000140001057 call sub_1400010E0 ;调用printf函数
000000014000105C mov rax, [rsp+38h] ;取出地址
0000000140001061 movsx eax, word ptr [rax] ;取出2字节内容,高位符号扩展成4字节
0000000140001064 mov edx, eax ;参数2 *p3
0000000140001066 lea rcx, a08x_1 ;参数1 "%08x \r\n"
000000014000106D call sub_1400010E0 ;调用printf函数
0000000140001072 xor eax, eax
0000000140001074 add rsp, 48h
0000000140001078 retn
//x64_gcc对应汇编代码讲解
0000000000401550 push rbp
0000000000401551 mov rbp, rsp
0000000000401554 sub rsp, 40h
0000000000401558 mov [rbp+10h], ecx
000000000040155B mov [rbp+18h], rdx
000000000040155F call __main ;调用初始化函数
0000000000401564 mov dword ptr [rbp-1Ch], 12345678h;n=0x12345678
000000000040156B lea rax, [rbp-1Ch]
000000000040156F mov [rbp-8], rax ;p1=&n
0000000000401573 lea rax, [rbp-1Ch]
0000000000401577 mov [rbp-10h], rax ;p2=&n
000000000040157B lea rax, [rbp-1Ch]
000000000040157F mov [rbp-18h], rax ;p3=&n
0000000000401583 mov rax, [rbp-8] ;取出地址
0000000000401587 mov eax, [rax] ;取出4字节内容
0000000000401589 mov edx, eax ;参数2 *p1
000000000040158B lea rcx, Format ;参数1 "%08x \r\n"
0000000000401592 call printf ;调用printf函数
0000000000401597 mov rax, [rbp-10h] ;取出地址
000000000040159B movzx eax, byte ptr [rax] ;取出1字节内容,高位0扩展成4字节
000000000040159E movsx eax, al ;高位符号扩展成4字节
00000000004015A1 mov edx, eax ;参数2 *p2
00000000004015A3 lea rcx, Format ;参数1 "%08x \r\n"
00000000004015AA call printf ;调用printf函数
00000000004015AF mov rax, [rbp-18h] ;取出地址
00000000004015B3 movzx eax, word ptr [rax] ;取出2字节内容,高位0扩展成4字节
00000000004015B6 cwde ;高位符号扩展成4字节
00000000004015B7 mov edx, eax ;参数2 *p3
00000000004015B9 lea rcx, Format ;参数1 "%08x \r\n"
00000000004015C0 call printf ;调用printf函数
00000000004015C5 mov eax, 0
00000000004015CA add rsp, 40h
00000000004015CE pop rbp
00000000004015CF retn
//x64_clang对应汇编代码讲解
0000000140001000 sub rsp, 68h
0000000140001004 mov dword ptr [rsp+64h], 0
000000014000100C mov [rsp+58h], rdx
0000000140001011 mov [rsp+54h], ecx
0000000140001015 mov dword ptr [rsp+50h], 12345678h; n=0x12345678
000000014000101D lea rdx, [rsp+50h]
0000000140001022 mov [rsp+48h], rdx ;p1=&n
0000000140001027 mov rax, rdx
000000014000102A mov [rsp+40h], rax ;p2=&n
000000014000102F mov [rsp+38h], rdx ;p3=&n
0000000140001034 mov rax, [rsp+48h] ;取出地址
0000000140001039 mov edx, [rax] ;参数2取出4字节内容 *p1
000000014000103B lea rcx, a08x ;参数1 "%08x \r\n"
0000000140001042 call sub_140001090 ;调用printf函数
0000000140001047 mov rcx, [rsp+40h] ;取出地址
000000014000104C movsx edx, byte ptr [rcx];参数2取出1字节内容,高位符号扩展成4字节
000000014000104F lea rcx, a08x ;参数1 "%08x \r\n"
0000000140001056 mov [rsp+34h], eax
000000014000105A call sub_140001090 ;调用printf函数
000000014000105F mov rcx, [rsp+38h] ;取出地址
0000000140001064 movsx edx, word ptr [rcx] ;参数2取出2字节内容,高位符号扩展成4字节
0000000140001067 lea rcx, a08x ;参数1 "%08x \r\n"
000000014000106E mov [rsp+30h], eax
0000000140001072 call sub_140001090 ;调用printf函数
0000000140001077 xor edx, edx
0000000140001079 mov [rsp+2Ch], eax
000000014000107D mov eax, edx
000000014000107F add rsp, 68h
0000000140001083 retn
笔记--------------------------------------------------start
//x86_vs_debug对应的汇编代码
int main(int argc, char* argv[])
{
00A71840 push ebp
00A71841 mov ebp,esp
00A71843 sub esp,0F4h ;申请局部变量
00A71849 push ebx
00A7184A push esi
00A7184B push edi
00A7184C lea edi,[ebp-34h]
00A7184F mov ecx,0Dh
00A71854 mov eax,0CCCCCCCCh
00A71859 rep stos dword ptr es:[edi]
00A7185B mov eax,dword ptr ds:[00A7A004h]
00A71860 xor eax,ebp
00A71862 mov dword ptr [ebp-4],eax
00A71865 mov ecx,0A7C003h
00A7186A call 00A71316
int n = 0x12345678;
00A7186F mov dword ptr [ebp-0Ch],12345678h ;n=0x12345678
int* p1 = &n;
00A71876 lea eax,[ebp-0Ch]
00A71879 mov dword ptr [ebp-18h],eax ;p1=&n
char* p2 = (char*)&n;
00A7187C lea eax,[ebp-0Ch]
00A7187F mov dword ptr [ebp-24h],eax ;p2=&n
short* p3 = (short*)&n;
00A71882 lea eax,[ebp-0Ch]
00A71885 mov dword ptr [ebp-30h],eax ;p3=&n
printf("%08x \r\n", *p1);
00A71888 mov eax,dword ptr [ebp-18h] ;取出地址
00A7188B mov ecx,dword ptr [eax] ;取4字节内容
00A7188D push ecx ;参数2 *p1 入栈
00A7188E push 0A77B30h ;参数1 "%08x \r\n" 栈
00A71893 call 00A710CD ;调用printf函数
00A71898 add esp,8 ;平衡栈
printf("%08x \r\n", *p2);
00A7189B mov eax,dword ptr [ebp-24h] ;取出地址
00A7189E movsx ecx,byte ptr [eax] ;取1字节内容,高位符号扩展成4字节
00A718A1 push ecx ;参数2 *p2 入栈
00A718A2 push 0A77B30h ;参数1 "%08x \r\n" 入栈
00A718A7 call 00A710CD ;调用printf函数
00A718AC add esp,8 ;平衡栈
printf("%08x \r\n", *p3);
00A718AF mov eax,dword ptr [ebp-30h] ;取出地址
00A718B2 movsx ecx,word ptr [eax] ;取2字节内容,高位符号扩展成4字节
00A718B5 push ecx ;参数2 *p3 入栈
00A718B6 push 0A77B30h ;参数1 "%08x \r\n" 入栈
00A718BB call 00A710CD ;调用printf函数
00A718C0 add esp,8 ;平衡栈
return 0;
00A718C3 xor eax,eax
}

//x64_vs_debug对应的汇编代码
int main(int argc, char* argv[])
{
00007FF7DDC31850 mov qword ptr [rsp+10h],rdx
00007FF7DDC31855 mov dword ptr [rsp+8],ecx
00007FF7DDC31859 push rbp
00007FF7DDC3185A push rdi
00007FF7DDC3185B sub rsp,168h
00007FF7DDC31862 lea rbp,[rsp+20h]
00007FF7DDC31867 lea rdi,[rsp+20h]
00007FF7DDC3186C mov ecx,22h
00007FF7DDC31871 mov eax,0CCCCCCCCh
00007FF7DDC31876 rep stos dword ptr [rdi]
00007FF7DDC31878 mov ecx,dword ptr [rsp+0000000000000188h]
00007FF7DDC3187F mov rax,qword ptr [00007FF7DDC3C008h]
00007FF7DDC31886 xor rax,rbp
00007FF7DDC31889 mov qword ptr [rbp+0000000000000138h],rax
00007FF7DDC31890 lea rcx,[00007FF7DDC41003h]
00007FF7DDC31897 call 00007FF7DDC31357
int n = 0x12345678;
00007FF7DDC3189C mov dword ptr [rbp+4],12345678h ;n=0x12345678
int* p1 = &n;
00007FF7DDC318A3 lea rax,[rbp+4]
00007FF7DDC318A7 mov qword ptr [rbp+28h],rax ;p1=&n
char* p2 = (char*)&n;
00007FF7DDC318AB lea rax,[rbp+4]
00007FF7DDC318AF mov qword ptr [rbp+48h],rax ;p2=&n
short* p3 = (short*)&n;
00007FF7DDC318B3 lea rax,[rbp+4]
00007FF7DDC318B7 mov qword ptr [rbp+68h],rax ;p3=&n
printf("%08x \r\n", *p1);
00007FF7DDC318BB mov rax,qword ptr [rbp+28h] ;取出地址
00007FF7DDC318BF mov edx,dword ptr [rax] ;参数2取出4字节内容 *p1
00007FF7DDC318C1 lea rcx,[00007FF7DDC39CA8h] ;参数1 "%08x \r\n"
00007FF7DDC318C8 call 00007FF7DDC3118B ;调用printf函数
printf("%08x \r\n", *p2);
00007FF7DDC318CD mov rax,qword ptr [rbp+48h] ;取出地址
00007FF7DDC318D1 movsx eax,byte ptr [rax] ;取出1字节的内容,高位符号扩展成4字节
00007FF7DDC318D4 mov edx,eax ;参数2 *p2
00007FF7DDC318D6 lea rcx,[00007FF7DDC39CA8h] ;参数1 "%08x \r\n"
00007FF7DDC318DD call 00007FF7DDC3118B ;调用printf函数
printf("%08x \r\n", *p3);
00007FF7DDC318E2 mov rax,qword ptr [rbp+68h] ;取出地址
00007FF7DDC318E6 movsx eax,word ptr [rax] ;取出2字节内容,高位符号扩展成4字节
00007FF7DDC318E9 mov edx,eax ;参数2 *p3
00007FF7DDC318EB lea rcx,[00007FF7DDC39CA8h] ;参数1 "%08x \r\n"
00007FF7DDC318F2 call 00007FF7DDC3118B ;调用printf函数
return 0;
00007FF7DDC318F7 xor eax,eax
}
笔记--------------------------------------------------end
代码清单2-4中使用了3种方式对变量n的地址进行解释。变量n在内存中的数据为“78 56 34 12”,首地址从“78”开始。指针p1为int类型,以int类型在内存中占用的空间大小和排列方式对地址进行解释,然后取出数据。int类型占4字节内存空间,以小尾方式排列,取出内容为“12345678”,是一个十六进制的数字。同理,p2、p3将会按照它们的指针类型对地址数据进行解释。指针取内容的操作分为两个步骤:先取出指针中保存的地址信息,然后针对这个地址取内容,这是一个间接寻址的过程,也是识别指针的重要依据。该示例运行结果如图2-10所示。

图2-10 各类型指针解释地址的结果
通过代码清单2-4中指针取内容的过程可得出结论,不同类型的指针对地址的解释都取自其自身指针类型。
指针都支持哪些运算符号呢?在C++中,所有指针类型都只支持加法和减法。指针是用来保存数据地址、解释地址的,因此只有加法与减法才有意义,其他运算对于指针而言没有任何意义。
指针加法用于地址偏移,但并不像数学中的加法那样简单。指针加1后,指针内保存的地址值并不一定会加1,运算结果取决于指针类型,如指针类型为int,地址值将会加4,这个4是根据类型大小所得到的值。C++为什么要用这种烦琐的地址偏移方法呢?因为当指针中保存的地址为数组首地址时,为了能够利用指针加1后访问到数组内下一成员,所以加的是类型长度,而非数字1,如代码清单2-5所示。
代码清单2-5 各类型指针的寻址方式
// C++ 源码
#include <stdio.h>
int main(int argc, char* argv[]) {
char ary[5] = {(char)0x01, (char)0x23, (char)0x45, (char)0x67, (char)0x89};
int *p1 = (int*)ary;
char *p2 = (char*)ary;
short *p3 = (short*)ary;
p1 += 1;
p2 += 1;
p3 += 1;
return 0;
}
//x86_vs对应汇编代码讲解
00401000 push ebp
00401001 mov ebp, esp
00401003 sub esp, 18h
00401006 mov eax, ___security_cookie
0040100B xor eax, ebp
0040100D mov [ebp-4], eax ;缓冲区溢出检查代码
00401010 mov byte ptr [ebp-0Ch], 1 ;ary[0]=1
00401014 mov byte ptr [ebp-0Bh], 23h ;ary[1]=0x23
00401018 mov byte ptr [ebp-0Ah], 45h ;ary[2]=0x45
0040101C mov byte ptr [ebp-9], 67h ;ary[3]=0x67
00401020 mov byte ptr [ebp-8], 89h ;ary[4]=0x89
00401024 lea eax, [ebp-0Ch]
00401027 mov [ebp-10h], eax ;p1=(int*)ary
0040102A lea ecx, [ebp-0Ch]
0040102D mov [ebp-14h], ecx ;p2=(char*)ary
00401030 lea edx, [ebp-0Ch]
00401033 mov [ebp-18h], edx ;p3=(short*)ary
00401036 mov eax, [ebp-10h]
00401039 add eax, 4
0040103C mov [ebp-10h], eax ;p1 += 1
0040103F mov ecx, [ebp-14h]
00401042 add ecx, 1
00401045 mov [ebp-14h], ecx ;p2 += 1
00401048 mov edx, [ebp-18h]
0040104B add edx, 2
0040104E mov [ebp-18h], edx ;p3 += 1
00401051 xor eax, eax
00401053 mov ecx, [ebp-4]
00401056 xor ecx, ebp
00401058 call @__security_check_cookie@4 ;缓冲区溢出检查代码
0040105D mov esp, ebp
0040105F pop ebp
00401060 retn
//x86_gcc对应汇编代码讲解
00401510 push ebp
00401511 mov ebp, esp
00401513 and esp, 0FFFFFFF0h ;对齐栈
00401516 sub esp, 20h
00401519 call ___main ;调用初始化函数
0040151E mov byte ptr [esp+0Fh], 1 ;ary[0]=1
00401523 mov byte ptr [esp+10h], 23h ;ary[1]=0x23
00401528 mov byte ptr [esp+11h], 45h ;ary[2]=0x45
0040152D mov byte ptr [esp+12h], 67h ;ary[3]=0x67
00401532 mov byte ptr [esp+13h], 89h ;ary[4]=0x89
00401537 lea eax, [esp+0Fh]
0040153B mov [esp+1Ch], eax ;p1 = (int*)ary
0040153F lea eax, [esp+0Fh]
00401543 mov [esp+18h], eax ;p2 = (char*)ary
00401547 lea eax, [esp+0Fh]
0040154B mov [esp+14h], eax ;p3 = (short*)ary
0040154F add dword ptr [esp+1Ch], 4 ;p1 += 1
00401554 add dword ptr [esp+18h], 1 ;p2 += 1
00401559 add dword ptr [esp+14h], 2 ;p3 += 1
0040155E mov eax, 0
00401563 leave
00401564 retn
//x86_clang对应汇编代码讲解
00401000 push ebp
00401001 mov ebp, esp
00401003 push ebx
00401004 push edi
00401005 push esi
00401006 sub esp, 20h
00401009 mov eax, [ebp+0Ch]
0040100C mov ecx, [ebp+8]
0040100F xor edx, edx
00401011 lea esi, [ebp-15h]
00401014 mov dword ptr [ebp-10h], 0
0040101B mov edi, ds:dword_40D150
00401021 mov [ebp-15h], edi ;ary[]= {1, 0x23, 0x45, 0x67}
00401024 mov bl, ds:byte_40D154
0040102A mov [ebp-11h], bl ;ary[4] = 0x89
0040102D mov edi, esi
0040102F mov [ebp-1Ch], edi ;p1 = (int*)ary
00401032 mov [ebp-20h], esi ;p2 = (char*)ary
00401035 mov [ebp-24h], esi ;p3 = (short)*ary
00401038 mov esi, [ebp-1Ch]
0040103B add esi, 4
0040103E mov [ebp-1Ch], esi ;p1 += 4;
00401041 mov esi, [ebp-20h]
00401044 add esi, 1
00401047 mov [ebp-20h], esi ;p2 += 1;
0040104A mov esi, [ebp-24h]
0040104D add esi, 2
00401050 mov [ebp-24h], esi ;p3 += 2;
00401053 mov [ebp-28h], eax
00401056 mov eax, edx
00401058 mov [ebp-2Ch], ecx
0040105B add esp, 20h
0040105E pop esi
0040105F pop edi
00401060 pop ebx
00401061 pop ebp
00401062 retn
//x64_vs对应汇编代码讲解
0000000140001000 mov [rsp+10h], rdx ;保存argv到预留栈空间
0000000140001005 mov [rsp+8], ecx ;保存argc到预留栈空间
0000000140001009 sub rsp, 38h
000000014000100D mov rax, cs:__security_cookie
0000000140001014 xor rax, rsp
0000000140001017 mov [rsp+20h], rax ;缓冲区溢出检查代码
000000014000101C mov byte ptr [rsp+18h], 1 ;ary[0] = 1
0000000140001021 mov byte ptr [rsp+19h], 23h ;ary[1] = 0x23
0000000140001026 mov byte ptr [rsp+1Ah], 45h ;ary[2] = 0x45
000000014000102B mov byte ptr [rsp+1Bh], 67h ;ary[3] = 0x67
0000000140001030 mov byte ptr [rsp+1Ch], 89h ;ary[4] = 0x89
0000000140001035 lea rax, [rsp+18h]
000000014000103A mov [rsp], rax ;p1 = (int*)ary
000000014000103E lea rax, [rsp+18h]
0000000140001043 mov [rsp+8], rax ;p2 = (char*)ary
0000000140001048 lea rax, [rsp+18h]
000000014000104D mov [rsp+10h], rax ;p3 = (short*)ary
0000000140001052 mov rax, [rsp]
0000000140001056 add rax, 4
000000014000105A mov [rsp], rax ;p1 += 1
000000014000105E mov rax, [rsp+8]
0000000140001063 inc rax
0000000140001066 mov [rsp+8], rax ;p2 += 1
000000014000106B mov rax, [rsp+10h]
0000000140001070 add rax, 2
0000000140001074 mov [rsp+10h], rax ;p3 += 1
0000000140001079 xor eax, eax
000000014000107B mov rcx, [rsp+20h]
0000000140001080 xor rcx, rsp ; StackCookie
0000000140001083 call __security_check_cookie ;缓冲区溢出检查代码
0000000140001088 add rsp, 38h
000000014000108C retn
//x64_gcc对应汇编代码讲解
0000000000401550 push rbp
0000000000401551 mov rbp, rsp
0000000000401554 sub rsp, 40h
0000000000401558 mov [rbp+10h], ecx
000000000040155B mov [rbp+18h], rdx
000000000040155F call __main ;调用初始化函数
0000000000401564 mov byte ptr [rbp-1Dh], 1 ;ary[0] = 1
0000000000401568 mov byte ptr [rbp-1Ch], 23h ;ary[1] = 0x23
000000000040156C mov byte ptr [rbp-1Bh], 45h ;ary[2] = 0x45
0000000000401570 mov byte ptr [rbp-1Ah], 67h ;ary[3] = 0x67
0000000000401574 mov byte ptr [rbp-19h], 89h ;ary[4] = 0x89
0000000000401578 lea rax, [rbp-1Dh]
000000000040157C mov [rbp-8], rax ;p1 = (int*)ary
0000000000401580 lea rax, [rbp-1Dh]
0000000000401584 mov [rbp-10h], rax ;p2 = (char*)ary
0000000000401588 lea rax, [rbp-1Dh]
000000000040158C mov [rbp-18h], rax ;p3 = (short*)ary
0000000000401590 add qword ptr [rbp-8], 4 ;p1 += 1
0000000000401595 add qword ptr [rbp-10h], 1 ;p2 += 1
000000000040159A add qword ptr [rbp-18h], 2 ;p3 += 1
000000000040159F mov eax, 0
00000000004015A4 add rsp, 40h
00000000004015A8 pop rbp
00000000004015A9 retn
//x64_clang对应汇编代码讲解
0000000140001000 sub rsp, 38h
0000000140001004 xor eax, eax
0000000140001006 lea r8, [rsp+1Fh]
000000014000100B mov dword ptr [rsp+34h], 0
0000000140001013 mov [rsp+28h], rdx
0000000140001018 mov [rsp+24h], ecx
000000014000101C mov ecx, cs:dword_14000D2C0
0000000140001022 mov [rsp+1Fh], ecx ;ary[]= {1, 0x23, 0x45, 0x67}
0000000140001026 mov r9b, cs:byte_14000D2C4
000000014000102D mov [rsp+23h], r9b ;ary[4] = 0x89
0000000140001032 mov rdx, r8
0000000140001035 mov [rsp+10h], rdx ;p1 = (int*)ary
000000014000103A mov [rsp+8], r8 ;p2 = (char*)ary
000000014000103F mov [rsp], r8 ;p3 = (short*)ary
0000000140001043 mov rdx, [rsp+10h]
0000000140001048 add rdx, 4
000000014000104C mov [rsp+10h], rdx ;p1 += 1
0000000140001051 mov rdx, [rsp+8]
0000000140001056 add rdx, 1
000000014000105A mov [rsp+8], rdx ;p2 += 1
000000014000105F mov rdx, [rsp]
0000000140001063 add rdx, 2
0000000140001067 mov [rsp], rdx ;p3 += 1
000000014000106B add rsp, 38h
000000014000106F ret
笔记--------------------------------------------------start
显示符号名的汇编

//x86_vs对应汇编代码讲解
int main(int argc, char* argv[])
{
00841760 push cs
00841761 add byte ptr [eax],al
00841763 add byte ptr [eax+CCCCCCCCh],bh
00841769 rep stos dword ptr es:[edi]
0084176B mov eax,dword ptr ds:[0084A004h]
00841770 xor eax,ebp
00841772 mov dword ptr [ebp-4],eax ;缓冲区溢出检查代码
00841775 mov ecx,84C003h
0084177A call 00841307
char ary[5] = { (char)0x01, (char)0x23, (char)0x45, (char)0x67, (char)0x89 };
0084177F mov byte ptr [ebp-10h],1 ;ary[0]=1
00841783 mov byte ptr [ebp-0Fh],23h ;ary[1]=0x23
00841787 mov byte ptr [ebp-0Eh],45h ;ary[2]=0x45
0084178B mov byte ptr [ebp-0Dh],67h ;ary[3]=0x67
0084178F mov byte ptr [ebp-0Ch],89h ;ary[4]=0x89
int *p1 = (int*)ary;
00841793 lea eax,[ebp-10h]
00841796 mov dword ptr [ebp-1Ch],eax ;p1=(int*)ary
char* p2 = (char*)ary;
00841799 lea eax,[ebp-10h]
0084179C mov dword ptr [ebp-28h],eax ;p2=(char*)ary
short* p3 = (short*)ary;
0084179F lea eax,[ebp-10h]
008417A2 mov dword ptr [ebp-34h],eax ;p3=(short*)ary
p1 += 1;
008417A5 mov eax,dword ptr [ebp-1Ch]
008417A8 add eax,4
008417AB mov dword ptr [ebp-1Ch],eax ;p1 += 1
p2 += 1;
008417AE mov eax,dword ptr [ebp-28h]
008417B1 add eax,1
008417B4 mov dword ptr [ebp-28h],eax ;p2 += 1
p3 += 1;
008417B7 mov eax,dword ptr [ebp-34h]
008417BA add eax,2
008417BD mov dword ptr [ebp-34h],eax ;p3 += 1
return 0;
008417C0 xor eax,eax
}
......
008417DB call @__security_check_cookie@4 (084113Bh) ;缓冲区溢出检查代码

//x64_vs对应汇编代码讲解
int main(int argc, char* argv[])
{
00007FF70D0A1770 mov qword ptr [rsp+10h],rdx ;保存argv到预留栈空间
00007FF70D0A1775 mov dword ptr [rsp+8],ecx ;保存argc到预留栈空间
00007FF70D0A1779 push rbp
00007FF70D0A177A push rdi
00007FF70D0A177B sub rsp,168h
00007FF70D0A1782 lea rbp,[rsp+20h]
00007FF70D0A1787 lea rdi,[rsp+20h]
00007FF70D0A178C mov ecx,22h
00007FF70D0A1791 mov eax,0CCCCCCCCh
00007FF70D0A1796 rep stos dword ptr [rdi]
00007FF70D0A1798 mov ecx,dword ptr [rsp+188h]
00007FF70D0A179F mov rax,qword ptr [__security_cookie (07FF70D0AC008h)]
00007FF70D0A17A6 xor rax,rbp
00007FF70D0A17A9 mov qword ptr [rbp+138h],rax ;缓冲区溢出检查代码
00007FF70D0A17B0 lea rcx,[__88278C72_list2_5@cpp (07FF70D0B1003h)]
00007FF70D0A17B7 call __CheckForDebuggerJustMyCode (07FF70D0A1348h)
char ary[5] = { (char)0x01, (char)0x23, (char)0x45, (char)0x67, (char)0x89 };
00007FF70D0A17BC mov byte ptr [rbp+4],1 ;ary[0] = 1
00007FF70D0A17C0 mov byte ptr [rbp+5],23h ;ary[1] = 0x23
00007FF70D0A17C4 mov byte ptr [rbp+6],45h ;ary[2] = 0x45
00007FF70D0A17C8 mov byte ptr [rbp+7],67h ;ary[3] = 0x67
00007FF70D0A17CC mov byte ptr [rbp+8],89h ;ary[4] = 0x89
int *p1 = (int*)ary;
00007FF70D0A17D0 lea rax,[rbp+4]
00007FF70D0A17D4 mov qword ptr [rbp+28h],rax ;p1 = (int*)ary
char* p2 = (char*)ary;
00007FF70D0A17D8 lea rax,[rbp+4]
00007FF70D0A17DC mov qword ptr [rbp+48h],rax ;p2 = (char*)ary
short* p3 = (short*)ary;
00007FF70D0A17E0 lea rax,[rbp+4]
00007FF70D0A17E4 mov qword ptr [rbp+68h],rax ;p3 = (short*)ary
p1 += 1;
00007FF70D0A17E8 mov rax,qword ptr [rbp+28h]
00007FF70D0A17EC add rax,4
00007FF70D0A17F0 mov qword ptr [rbp+28h],rax ;p1 += 1
p2 += 1;
00007FF70D0A17F4 mov rax,qword ptr [rbp+48h]
00007FF70D0A17F8 inc rax
00007FF70D0A17FB mov qword ptr [rbp+48h],rax ;p2 += 1
p3 += 1;
00007FF70D0A17FF mov rax,qword ptr [rbp+68h]
00007FF70D0A1803 add rax,2
00007FF70D0A1807 mov qword ptr [rbp+68h],rax ;p3 += 1
return 0;
00007FF70D0A180B xor eax,eax
}
00007FF70D0A180D mov edi,eax
00007FF70D0A180F lea rcx,[rbp-20h]
00007FF70D0A1813 lea rdx,[__xt_z+160h (07FF70D0A9C00h)]
00007FF70D0A181A call _RTC_CheckStackVars (07FF70D0A12E4h)
00007FF70D0A181F mov eax,edi
00007FF70D0A1821 mov rcx,qword ptr [rbp+138h]
00007FF70D0A1828 xor rcx,rbp
00007FF70D0A182B call __security_check_cookie (07FF70D0A119Ah) ;缓冲区溢出检查代码
00007FF70D0A1830 lea rsp,[rbp+148h]
00007FF70D0A1837 pop rdi
00007FF70D0A1838 pop rbp
00007FF70D0A1839 ret
笔记--------------------------------------------------end
代码清单2-5演示了对不同类型指针进行加1偏移得到的结果。它们偏移后的地址都是由指针类型决定的,以指针保存的地址作为寻址[首地址],加上[偏移量],最终得到[目标地址]。偏移量的计算方式为指针类型长度乘以移动次数,因此得出指针寻址公式如下所示。
type *p; // 这里用 type 泛指某类型的指针
// 省略指针赋值代码
p+n 的目标地址 = 首地址 + sizeof( 指针类型 type) * n
对于偏移量为负数的情况,此公式同样适用。套用公式,得到的地址值会小于首地址,这时指针是在向后寻址。所以指针可以做减法操作,但乘法与除法对于指针寻址而言是没有意义的。两指针做减法操作是在计算两个地址之间的元素个数,结果为有符号整数,进行减法操作的两指针必须是同类指针。可用于两指针中的地址比较,也可用于其他场合,比如求数组元素个数,其计算公式如下所示。
type *p, *q; // 这里用type泛指某类型的指针
// 省略指针赋值代码
p-q = ((int)p - (int)q) / sizeof(指针类型type)
另外,两指针相加也是没有意义的。将指针访问公式与指针寻址公式结合后,可针对所有类型的指针进行操作。在实际运用中要灵活使用,同时也要谨慎操作,以免将指针指向意料之外的地址,错误地修改地址中的数据,造成程序崩溃。
在代码清单2-5中,指针p1加1后取出的内容就是数组ary以外的数据。ary数组只有5字节数据长度,而p1将访问到数组的第4项,对其取内容后得到的数据是以ary数组的第4项为起始地址的4字节数据。分析出的结果如图2-11所示。

图2-11 字符数组ary的内存信息
2.5.3 引用
引用类型在C++中被描述为变量的别名。C++为了简化操作,对指针的操作进行了封装,产生了引用类型。引用类型实际上就是指针类型,只不过用于存放地址的内存空间对使用者而言是隐藏的。下面通过示例来揭开这个谜底,如代码清单2-6所示。
代码清单2-6 引用类型揭秘
// C++ 源码
#include <stdio.h>
void add(int &ref){
ref++;
}
int main(int argc, char* argv[]) {
int n = 0x12345678;
int &ref = n;
add(ref);
return 0;
}
//x86_vs对应汇编代码讲解
00401020 push ebp
00401021 mov ebp, esp
00401023 sub esp, 8
00401026 mov dword ptr [ebp-4], 12345678h;n=0x12345678
0040102D lea eax, [ebp-4] ;eax=&n
00401030 mov [ebp-8], eax ;[ebp-8]存放n的地址,int& ref = n
00401033 mov ecx, [ebp-8]
00401036 push ecx ;参数1传递n的地址
00401037 call sub_401000 ;调用add函数
0040103C add esp, 4 ;平衡栈
0040103F xor eax, eax
00401041 mov esp, ebp
00401043 pop ebp
00401044 retn
//x86_gcc对应汇编代码讲解
00401523 push ebp
00401524 mov ebp, esp
00401526 and esp, 0FFFFFFF0h ;对齐栈
00401529 sub esp, 20h
0040152C call ___main ;调用初始化函数
00401531 mov dword ptr [esp+18h], 12345678h;n=0x12345678
00401539 lea eax, [esp+18h] ;eax=&n
0040153D mov [esp+1Ch], eax ;[esp+1Ch]存放n的地址,int& ref = n
00401541 mov eax, [esp+1Ch]
00401545 mov [esp], eax ;参数1传递n的地址int *
00401548 call __Z3addRi ;调用函数add(int &)
0040154D mov eax, 0
00401552 leave
00401553 retn
//x86_clang对应汇编代码讲解
00401020 push ebp
00401021 mov ebp, esp
00401023 sub esp, 18h
00401026 mov eax, [ebp+0Ch]
00401029 mov ecx, [ebp+8]
0040102C mov dword ptr [ebp-4], 0
00401033 mov dword ptr [ebp-8], 12345678h;n=0x12345678
0040103A lea edx, [ebp-8] ;edx=&n
0040103D mov [ebp-0Ch], edx ;[ebp-0Ch]存放n的地址,int& ref = n
00401040 mov edx, [ebp-0Ch]
00401043 mov [esp], edx ;参数1传递n的地址
00401046 mov [ebp-10h], eax
00401049 mov [ebp-14h], ecx
0040104C call sub_401000 ;调用add函数
00401051 xor eax, eax
00401053 add esp, 18h
00401056 pop ebp
00401057 retn
//x64_vs对应汇编代码讲解
0000000140001020 mov [rsp+10h], rdx
0000000140001025 mov [rsp+8], ecx
0000000140001029 sub rsp, 38h
000000014000102D mov dword ptr [rsp+20h], 12345678h;n=0x12345678
0000000140001035 lea rax, [rsp+20h] ;rax=&n
000000014000103A mov [rsp+28h], rax ;[rsp+28h]存放n的地址,int& ref = n
000000014000103F mov rcx, [rsp+28h] ;参数1传递n的地址
0000000140001044 call sub_140001000 ;调用add函数
0000000140001049 xor eax, eax
000000014000104B add rsp, 38h
000000014000104F retn
//x64_gcc对应汇编代码讲解
000000000040156A push rbp
000000000040156B mov rbp, rsp
000000000040156E sub rsp, 30h
0000000000401572 mov [rbp+10h], ecx
0000000000401575 mov [rbp+18h], rdx
0000000000401579 call __main ;调用初始化函数
000000000040157E mov dword ptr [rbp-0Ch], 12345678h;n=0x12345678
0000000000401585 lea rax, [rbp-0Ch] ;rax=&n
0000000000401589 mov [rbp-8], rax ;[rbp-8]存放n的地址,int& ref = n
000000000040158D mov rax, [rbp-8]
0000000000401591 mov rcx, rax ;参数1传递n的地址,int *
0000000000401594 call _Z3addRi ;调用函数add(int &)
0000000000401599 mov eax, 0
000000000040159E add rsp, 30h
00000000004015A2 pop rbp
00000000004015A3 retn
//x64_clang对应汇编代码讲解
0000000140001020 sub rsp, 48h
0000000140001024 mov dword ptr [rsp+44h], 0
000000014000102C mov [rsp+38h], rdx
0000000140001031 mov [rsp+34h], ecx
0000000140001035 mov dword ptr [rsp+30h], 12345678h;n=0x12345678
000000014000103D lea rdx, [rsp+30h] ;rdx=&n
0000000140001042 mov [rsp+28h], rdx ;[rsp+28h]存放n的地址,int& ref = n
0000000140001047 mov rcx, [rsp+28h] ;参数1传递n的地址
000000014000104C call sub_140001000 ;调用add函数
0000000140001051 xor eax, eax
0000000140001053 add rsp, 48h
0000000140001057 retn
笔记--------------------------------------------------start
//x86_vs对应汇编代码讲解
int main(int argc, char* argv[])
{
000217B0 push ebp
000217B1 mov ebp,esp
000217B3 sub esp,0DCh
000217B9 push ebx
000217BA push esi
000217BB push edi
000217BC lea edi,[ebp-1Ch]
000217BF mov ecx,7
000217C4 mov eax,0CCCCCCCCh
000217C9 rep stos dword ptr es:[edi]
000217CB mov eax,dword ptr ds:[0002A004h]
000217D0 xor eax,ebp
000217D2 mov dword ptr [ebp-4],eax
000217D5 mov ecx,2C003h
000217DA call 0002130C
int n = 0x12345678;
000217DF mov dword ptr [ebp-0Ch],12345678h ;n=0x12345678
int& ref = n;
000217E6 lea eax,[ebp-0Ch] ;eax=&n
000217E9 mov dword ptr [ebp-18h],eax ;[ebp-8]存放n的地址,int& ref = n
add(ref);
000217EC mov eax,dword ptr [ebp-18h]
000217EF push eax ;参数1传递n的地址
000217F0 call 0002122B ;调用add函数
000217F5 add esp,4 ;平衡栈
return 0;
000217F8 xor eax,eax
}


int main(int argc, char* argv[])
{
00007FF6C65417C0 mov qword ptr [rsp+10h],rdx
00007FF6C65417C5 mov dword ptr [rsp+8],ecx
00007FF6C65417C9 push rbp
00007FF6C65417CA push rdi
00007FF6C65417CB sub rsp,128h
00007FF6C65417D2 lea rbp,[rsp+20h]
00007FF6C65417D7 lea rdi,[rsp+20h]
00007FF6C65417DC mov ecx,12h
00007FF6C65417E1 mov eax,0CCCCCCCCh
00007FF6C65417E6 rep stos dword ptr [rdi]
00007FF6C65417E8 mov ecx,dword ptr [rsp+0000000000000148h]
00007FF6C65417EF mov rax,qword ptr [00007FF6C654C008h]
00007FF6C65417F6 xor rax,rbp
00007FF6C65417F9 mov qword ptr [rbp+00000000000000F8h],rax
00007FF6C6541800 lea rcx,[00007FF6C6551003h]
00007FF6C6541807 call 00007FF6C654134D
int n = 0x12345678;
00007FF6C654180C mov dword ptr [rbp+4],12345678h ;n=0x12345678
int& ref = n;
00007FF6C6541813 lea rax,[rbp+4] ;rax=&n
00007FF6C6541817 mov qword ptr [rbp+28h],rax ;[rsp+28h]存放n的地址,int& ref = n
add(ref);
00007FF6C654181B mov rcx,qword ptr [rbp+28h] ;参数1传递n的地址
00007FF6C654181F call 00007FF6C6541307 ;调用add函数
return 0;
00007FF6C6541824 xor eax,eax
}
00007FF6C6541826 mov edi,eax
00007FF6C6541828 lea rcx,[rbp-20h]
00007FF6C654182C lea rdx,[00007FF6C6549C00h]
00007FF6C6541833 call 00007FF6C65412E4
00007FF6C6541838 mov eax,edi
00007FF6C654183A mov rcx,qword ptr [rbp+00000000000000F8h]
00007FF6C6541841 xor rcx,rbp
00007FF6C6541844 call 00007FF6C654119A
00007FF6C6541849 lea rsp,[rbp+0000000000000108h]
00007FF6C6541850 pop rdi
00007FF6C6541851 pop rbp
00007FF6C6541852 ret
笔记--------------------------------------------------end
在图2-10中可以看出,引用类型的存储方式和指针是一样的,都是使用内存空间存放地址值。所以,在C++中,除了引用是通过编译器实现寻址,而指针需要手动寻址外,引用和指针没有太大区别。指针虽然灵活,但如果操作失误将产生严重的后果,而使用引用则不存在这种问题。因此,C++极力提倡使用引用类型,而非指针。
引用类型也可以作为函数的参数类型和返回类型使用。因为引用实际上就是指针,所以它同样会在参数传递时产生一份备份,如代码清单2-7所示。
代码清单2-7 引用类型作为函数参数
//Add函数实现
void add(int &ref){
ref++;
}
//x86_vs对应汇编代码讲解
00401000 push ebp
00401001 mov ebp, esp
00401003 mov eax, [ebp+8] ;取出参数ref的内容放入eax
00401006 mov ecx, [eax]
00401008 add ecx, 1
0040100B mov edx, [ebp+8]
0040100E mov [edx], ecx ;对eax做取内容操作,间接访问实参
00401010 pop ebp
00401011 retn
//x86_gcc对应汇编代码讲解
00401510 push ebp
00401511 mov ebp, esp
00401513 mov eax, [ebp+8] ;取出参数ref的内容放入eax
00401516 mov eax, [eax]
00401518 lea edx, [eax+1]
0040151B mov eax, [ebp+8]
0040151E mov [eax], edx ;对eax做取内容操作,间接访问实参
00401520 nop
00401521 pop ebp
00401522 retn
//x86_clang对应汇编代码讲解
00401000 push ebp
00401001 mov ebp, esp
00401003 push eax
00401004 mov eax, [ebp+8]
00401007 mov ecx, [ebp+8] ;取出参数ref的内容放入ecx
0040100A mov edx, [ecx]
0040100C add edx, 1
0040100F mov [ecx], edx ;对ecx做取内容操作,间接访问实参
00401011 mov [ebp-4], eax
00401014 add esp, 4
00401017 pop ebp
00401018 retn
//x64_vs对应汇编代码讲解
0000000140001000 mov [rsp+8], rcx
0000000140001005 mov rax, [rsp+8] ;取出参数ref的内容放入rax
000000014000100A mov eax, [rax]
000000014000100C inc eax
000000014000100E mov rcx, [rsp+8]
0000000140001013 mov [rcx], eax ;对rax做取内容操作,间接访问实参
0000000140001015 retn
//x64_gcc对应汇编代码讲解
0000000000401550 push rbp
0000000000401551 mov rbp, rsp
0000000000401554 mov [rbp+10h], rcx
0000000000401558 mov rax, [rbp+10h] ;取出参数ref的内容放入rax
000000000040155C mov eax, [rax]
000000000040155E lea edx, [rax+1]
0000000000401561 mov rax, [rbp+10h]
0000000000401565 mov [rax], edx ;对rax做取内容操作,间接访问实参
0000000000401567 nop
0000000000401568 pop rbp
0000000000401569 retn
//x64_clang对应汇编代码讲解
0000000140001000 push rax
0000000140001001 mov [rsp], rcx ;保存参数1 ref到局部变量空间
0000000140001005 mov rcx, [rsp] ;取出参数ref的内容放入rcx
0000000140001009 mov eax, [rcx]
000000014000100B add eax, 1
000000014000100E mov [rcx], eax ;对rcx做取内容操作,间接访问实参
0000000140001010 pop rax
0000000140001011 retn
笔记--------------------------------------------------start

//x86_vs对应汇编代码讲解
void add(int& ref)
{
00101740 push ebp
00101741 mov ebp,esp
00101743 sub esp,0C0h
00101749 push ebx
0010174A push esi
0010174B push edi
0010174C mov edi,ebp
0010174E xor ecx,ecx
00101750 mov eax,0CCCCCCCCh
00101755 rep stos dword ptr es:[edi]
00101757 mov ecx,10C003h
0010175C call 0010130C
ref++;
00101761 mov eax,dword ptr [ebp+8] ;取出参数ref的内容放入eax
00101764 mov ecx,dword ptr [eax]
00101766 add ecx,1
00101769 mov edx,dword ptr [ebp+8]
0010176C mov dword ptr [edx],ecx ;对eax做取内容操作,间接访问实参
}
0010176E pop edi
0010176F pop esi
00101770 pop ebx
00101771 add esp,0C0h
00101777 cmp ebp,esp
00101779 call 00101235
0010177E mov esp,ebp
00101780 pop ebp
00101781 ret

x64编译的代码干净很多
//x64_vs对应汇编代码讲解
void add(int& ref)
{
00007FF6A42A1760 mov qword ptr [rsp+8],rcx
00007FF6A42A1765 push rbp
00007FF6A42A1766 push rdi
00007FF6A42A1767 sub rsp,0E8h
00007FF6A42A176E lea rbp,[rsp+20h]
00007FF6A42A1773 lea rcx,[00007FF6A42B1003h]
00007FF6A42A177A call 00007FF6A42A134D
ref++;
00007FF6A42A177F mov rax,qword ptr [rbp+00000000000000E0h] ;取出参数ref的内容放入rax
00007FF6A42A1786 mov eax,dword ptr [rax]
00007FF6A42A1788 inc eax
00007FF6A42A178A mov rcx,qword ptr [rbp+00000000000000E0h]
00007FF6A42A1791 mov dword ptr [rcx],eax ;对rax做取内容操作,间接访问实参
}
00007FF6A42A1793 lea rsp,[rbp+00000000000000C8h]
00007FF6A42A179A pop rdi
00007FF6A42A179B pop rbp
00007FF6A42A179C ret
笔记--------------------------------------------------end
在代码清单2-7中,通过对参数加1的方式修改实参数据。从汇编代码中可以看出,引用类型的参数也占用内存空间,其中保存的数据是一个地址值。取出这个地址中的数据并加1,再将加1后的结果放回地址,如果没有源码对照,指针和引用都一样难以区分。在反汇编下,没有引用这种数据类型。
2.6 常量
前几节介绍的数据类型都是以变量形式进行演示的,在程序运行中可以修改其保存的数据。从字面上理解,常量是一个恒定不变的值,它在内存中也是不可修改的。在程序中出现的1、2、3这样的数字或“Hello”这样的字符串,以及数组名称,都属于常量,程序在运行中不可修改这类数据。
常量数据在程序运行前就已经存在,它们被编译到可执行文件中,当程序启动后,它们便会被加载进来。这些数据通常都会保存在常量数据区中,该区的属性没有写权限,所以在对常量进行修改时,程序会报错。试图修改常量数据都将引发异常,导致程序崩溃。
2.6.1 常量的定义
在C++中,可以使用宏机制#define来定义常量,也可以使用const将变量定义为一个常量。#define定义常量名称,编译器在对其进行编译时,会将代码中的宏名称替换成对应信息。宏的使用可以增加代码的可读性。const是为了增加程序的健壮性而存在的。常用字符串处理函数strcpy的第二个参数被定义为一个常量,这是为了防止该参数在函数内被修改,对原字符串造成破坏,宏与const的使用如代码清单2-8所示。
代码清单2-8 宏与const的使用
//C++源码
#include <stdio.h>
#define NUMBER_ONE 1 //定义NUMBER_ONE为常量1
int main(int argc, char* argv[]) {
const int n = NUMBER_ONE; //将常量NUMBER_ONE赋值给const常量n
printf("const = %d #define = %d \r\n", n, NUMBER_ONE); //显示两者结果
return 0;
}
代码清单2-8中,使用#define定义了常量1,并赋值给const的常量n。编译后,宏名称NUMBER_ONE将被替换成1。使用VS编译此段代码,依次选择菜单“项目”→“属性”→“C/C++”→“命令行”,添加“/P”选项,如图2-12所示。

图2-12 添加编译选项
VS也可以使用命令行添加编译器选项编程,gcc和clang可以使用-E编译选项生成预处理文件,命令如下。
cl /Fe:vs.i /P test.cpp
clang -E -o clang.i test.cpp
gcc -E -o gcc.i test.cpp
此编译选项的功能是将预处理文件生成到文件中,编译后,在对应的CPP文件夹中会产生一个“文件名.i”的文件。编译代码清单2-8中的代码,生成.i文件,打开该文件查看main函数中的代码信息。添加“/P”选项后,在连接过程中会产生错误,这是由于没有生成OBJ文件,而是将预处理信息写入了.i文件中,编译器找到不OBJ,无法进行连接。查看.i文件中的信息,如代码清单2-9所示。
笔记--------------------------------------------------start

如果不想看到错误,也可以选中.cpp源文件,右键,弹出菜单,选【编译】选项即可生成.i的文件


笔记--------------------------------------------------end
代码清单2-9 预处理文件信息
//VS对应预处理文件
int main(int argc, char* argv[]) {
const int n = 1;
printf("const = %d #define = %d \r\n", n, 1);
return 0;
}
//GCC对应预处理文件
int main(int argc, char* argv[]) {
const int n = 1;
printf("const = %d #define = %d \r\n", n, 1);
return 0;
}
//Clang对应预处理文件
int main(int argc, char* argv[]) {
const int n = 1;
printf("const = %d #define = %d \r\n", n, 1);
return 0;
}
笔记--------------------------------------------------start
在VS编译器中 /P 参数表示只对文件进行预处理
在gcc和clang编译器中,对文件进行预处理则是-E参数
cl /Fe:vs.i /P list2_9.cpp
clang -E -o clang.i list2_9.cpp
gcc -E -o gcc.i list2_9.cpp

笔记--------------------------------------------------end
2.6.2 #define和const的区别
#define修饰的符号名称是一个真量数值,而const修饰的栈常量,是一个“假”常量。在实际中,使用const定义的栈变量,最终还是一个变量,只是在编译期间对语法进行了检查,发现代码有对const修饰的变量存在直接修改行为则报错。
被const修饰过的栈变量本质上是可以被修改的。我们可以利用指针获取const修饰过的栈变量地址,强制将const属性修饰去掉,就可以修改对应的数据内容,如代码清单2-10所示。
代码清单2-10 修改const常量
// C++ 源码
#include <stdio.h>
int main(int argc, char* argv[]) {
const int n1 = 5;
int *p = (int*)&n1;
*p = 6;
int n2 = n1;
return 0;
}
//x86_vs对应汇编代码讲解
00401000 push ebp
00401001 mov ebp, esp
00401003 sub esp, 0Ch
00401006 mov dword ptr [ebp-4], 5 ;n1 = 5
0040100D lea eax, [ebp-4]
00401010 mov [ebp-8], eax ;p = (int*)&n1
00401013 mov ecx, [ebp-8]
00401016 mov dword ptr [ecx], 6 ;*p = 6
0040101C mov dword ptr [ebp-0Ch], 5 ;n2 = n1
00401023 xor eax, eax
00401025 mov esp, ebp
00401027 pop ebp
00401028 retn
//x86_gcc对应汇编代码讲解
00401510 push ebp
00401511 mov ebp, esp
00401513 and esp, 0FFFFFFF0h ;对齐栈
00401516 sub esp, 10h
00401519 call ___main ;调用初始化函数
0040151E mov dword ptr [esp+4], 5 ;n1 = 5
00401526 lea eax, [esp+4]
0040152A mov [esp+0Ch], eax ;p = (int*)&n1
0040152E mov eax, [esp+0Ch]
00401532 mov dword ptr [eax], 6 ;*p = 6
00401538 mov dword ptr [esp+8], 5 ;n2 = n1
00401540 mov eax, 0
00401545 leave
00401546 retn
//x86_clang对应汇编代码讲解
00401000 push ebp
00401001 mov ebp, esp
00401003 push esi
00401004 sub esp, 18h
00401007 mov eax, [ebp+0Ch]
0040100A mov ecx, [ebp+8]
0040100D xor edx, edx
0040100F mov dword ptr [ebp-8], 0
00401016 mov dword ptr [ebp-0Ch], 5 ;n1 = 5
0040101D lea esi, [ebp-0Ch]
00401020 mov [ebp-10h], esi ;p = (int*)&n1
00401023 mov esi, [ebp-10h]
00401026 mov dword ptr [esi], 6 ;*p = 6
0040102C mov dword ptr [ebp-14h], 5 ;n2 = n1
00401033 mov [ebp-18h], eax
00401036 mov eax, edx
00401038 mov [ebp-1Ch], ecx
0040103B add esp, 18h
0040103E pop esi
0040103F pop ebp
00401040 retn
//x64_vs对应汇编代码讲解
0000000140001000 mov [rsp+10h], rdx
0000000140001005 mov [rsp+8], ecx
0000000140001009 sub rsp, 18h
000000014000100D mov dword ptr [rsp], 5 ;n1 = 5
0000000140001014 lea rax, [rsp]
0000000140001018 mov [rsp+8], rax ;p = (int*)&n1
000000014000101D mov rax, [rsp+8]
0000000140001022 mov dword ptr [rax], 6 ;*p = 6
0000000140001028 mov dword ptr [rsp+4], 5 ;n2 = n1
0000000140001030 xor eax, eax
0000000140001032 add rsp, 18h
0000000140001036 retn
//x64_gcc对应汇编代码讲解
0000000000401550 push rbp
0000000000401551 mov rbp, rsp
0000000000401554 sub rsp, 30h
0000000000401558 mov [rbp+10h], ecx
000000000040155B mov [rbp+18h], rdx
000000000040155F call __main ;调用初始化函数
0000000000401564 mov dword ptr [rbp-10h], 5 ;n1 = 5
000000000040156B lea rax, [rbp-10h]
000000000040156F mov [rbp-8], rax ;p = (int*)&n1
0000000000401573 mov rax, [rbp-8]
0000000000401577 mov dword ptr [rax], 6 ;*p = 6
000000000040157D mov dword ptr [rbp-0Ch], 5 ;n2 = n1
0000000000401584 mov eax, 0
0000000000401589 add rsp, 30h
000000000040158D pop rbp
000000000040158E retn
//x64_clang对应汇编代码讲解
0000000140001000 sub rsp, 28h
0000000140001004 xor eax, eax
0000000140001006 mov dword ptr [rsp+24h], 0
000000014000100E mov [rsp+18h], rdx
0000000140001013 mov [rsp+14h], ecx
0000000140001017 mov dword ptr [rsp+10h], 5 ;n1 = 5
000000014000101F lea rdx, [rsp+10h]
0000000140001024 mov [rsp+8], rdx ;p = (int*)&n1
0000000140001029 mov rdx, [rsp+8]
000000014000102E mov dword ptr [rdx], 6 ;*p = 6
0000000140001034 mov dword ptr [rsp+4], 5 ;n2 = n1
000000014000103C add rsp, 28h
0000000140001040 retn
笔记--------------------------------------------------start

//x86_vs对应汇编代码讲解
int main(int argc, char* argv[])
{
008F1750 push ebp
008F1751 mov ebp,esp
008F1753 sub esp,0E8h
008F1759 push ebx
008F175A push esi
008F175B push edi
008F175C lea edi,[ebp-28h]
008F175F mov ecx,0Ah
008F1764 mov eax,0CCCCCCCCh
008F1769 rep stos dword ptr es:[edi]
008F176B mov eax,dword ptr ds:[008FA004h]
008F1770 xor eax,ebp
008F1772 mov dword ptr [ebp-4],eax
008F1775 mov ecx,8FC003h
008F177A call 008F1307
const int n1 = 5;
008F177F mov dword ptr [ebp-0Ch],5 ;n1 = 5
int* p = (int*)&n1;
008F1786 lea eax,[ebp-0Ch] ;将局部变量(地址 ebp-0C)的地址存入 eax
008F1789 mov dword ptr [ebp-18h],eax ;p = (int*)&n1
*p = 6;
008F178C mov eax,dword ptr [ebp-18h] ;;将局部变量(地址 ebp-18)的地址存入 eax
008F178F mov dword ptr [eax],6 ;把值赋给p指向的地址 *p = 6
int n2 = n1;
008F1795 mov dword ptr [ebp-24h],5
return 0;
008F179C xor eax,eax
}

//x64_vs对应汇编代码讲解
int main(int argc, char* argv[])
{
00007FF7D2A11774 adc byte ptr [rcx+5508244Ch],cl
00007FF7D2A1177A push rdi
00007FF7D2A1177B sub rsp,148h
00007FF7D2A11782 lea rbp,[rsp+20h]
00007FF7D2A11787 lea rdi,[rsp+20h]
00007FF7D2A1178C mov ecx,1Ah
00007FF7D2A11791 mov eax,0CCCCCCCCh
00007FF7D2A11796 rep stos dword ptr [rdi]
00007FF7D2A11798 mov ecx,dword ptr [rsp+168h]
00007FF7D2A1179F mov rax,qword ptr [__security_cookie (07FF7D2A1C008h)]
00007FF7D2A117A6 xor rax,rbp
00007FF7D2A117A9 mov qword ptr [rbp+118h],rax
00007FF7D2A117B0 lea rcx,[__EA178E5D_list2_10@cpp (07FF7D2A21003h)]
00007FF7D2A117B7 call __CheckForDebuggerJustMyCode (07FF7D2A11348h)
const int n1 = 5;
00007FF7D2A117BC mov dword ptr [rbp+4],5 ;n1 = 5
int* p = (int*)&n1;
00007FF7D2A117C3 lea rax,[rbp+4] ;将局部变量(地址 rbp+4)的地址存入 eax
00007FF7D2A117C7 mov qword ptr [rbp+28h],rax ;p = (int*)&n1
*p = 6;
00007FF7D2A117CB mov rax,qword ptr [rbp+28h] ;将64位地址 rbp+28h 赋给rax
00007FF7D2A117CF mov dword ptr [rax],6 ;*p = 6
int n2 = n1;
00007FF7D2A117D5 mov dword ptr [rbp+44h],5 ;n2 = n1
return 0;
00007FF7D2A117DC xor eax,eax
}
00007FF7D2A117DE mov edi,eax
00007FF7D2A117E0 lea rcx,[rbp-20h]
00007FF7D2A117E4 lea rdx,[__xt_z+160h (07FF7D2A19C00h)]
00007FF7D2A117EB call _RTC_CheckStackVars (07FF7D2A112E4h)
00007FF7D2A117F0 mov eax,edi
00007FF7D2A117F2 mov rcx,qword ptr [rbp+118h]
00007FF7D2A117F9 xor rcx,rbp
00007FF7D2A117FC call __security_check_cookie (07FF7D2A1119Ah)
00007FF7D2A11801 lea rsp,[rbp+128h]
00007FF7D2A11808 pop rdi
00007FF7D2A11809 pop rbp
00007FF7D2A1180A ret
笔记--------------------------------------------------end
在代码清单2-10中,由于const修饰的变量n1被赋值一个数字常量5,编译器在编译过程中发现n1的初始值是可知的,并且被修饰为const。之后所有使用n1的地方都替换为这个可预知值,故int n2 = n1;对应的汇编代码没有将n1赋值给n2,而是用常量值5代替。如果n1的值为一个未知值,则编译器不会做此优化。在示例中使用指针能否将n1中的数据修改为6呢?我们先来看看图2-13。

图2-13 const常量的修改结果
图2-13中演示了const修饰的变量被修改后的情况。被const修饰后,变量本质上并没有改变,还是可以修改的。#define与const两者之间还是不同的,如表2-5所示。
表2-5 #define与const的区别

这两者在连接生成可执行文件后将不复存在,在二进制编码中也没有这两种类型存在。在实际分析中,读者需要根据自身的经验进行还原。
2.7 本章小结
计算机的工作流程归根结底是输入→处理→输出的过程,而数据正是被处理的对象。作为逆向工作者,需要正确考察数据。对数据的考察有以下两点。
1. 在何处
数据是代码加工处理的对象,而代码本身也是以二进制形式存放的,对于处理器而言,代码的本质也是数据。我们在分析的时候,会看到不同指令对数据的处理,这时首先要确定数据的存储位置,对于内存中的数据,要查看地址。有了内存地址,才能得到内存属性。我们需要了解的属性有可读、可写、可执行。藉此,可以知道此数据是否为变量(可读写)、是否为常量(只读)、是否为代码(可执行)等。除了知道属性以外,我们还可以考察进程在内存的布局,如栈区、堆区、全局区、代码区等,藉此,又可以知道数据的作用域。
到底是代码还是数据?程序员认为是代码,那就是代码;程序员认为是数据,那就是数据。其中滋味,留待读者在后面的学习中逐步体会。
2. 如何解释
得到了内存地址,还是无法得到数据的正确内容,因为缺少解释方式。如“无鸡鸭也可无鱼肉也可无银钱也可”,可以解释为:“无鸡鸭也可,无鱼肉也可,无银钱也可。”也可以解释为:“无鸡,鸭也可;无鱼,肉也可;无银,钱也可。”
本章归纳的各类数据的解释方式和特点,是我们学习后面内容的基础,读者应重点掌握。下面补充介绍一下“大尾方式”和“小尾方式”。
这两种方式又分别称为“大端方式”和“小端方式”,出自某个西方童话,内容大意是:有个小人国,争论吃鸡蛋的时候应该是先把鸡蛋的大头敲开,还是应该先把小头敲开;为此国内引发了激烈的讨论,最后导致国家分裂、爆发战争,在这场战争中,国王和一些大臣丧命。
计算机的数据存储也是这样的道理,如果约定了存储的顺序,大家就都能正确写入和读出了,没必要在意当初为什么制定这样的存储顺序。制定字节存储顺序的人可能就是想避免别人问他为什么选择这个方向,故以此典故封堵闲人之口。

1178

被折叠的 条评论
为什么被折叠?



