深入理解汇编指令:从C++到汇编的探索之旅

在探索Windows调试器的过程中,经常会遇到Visual C++编译器生成的汇编指令。本文将带了解这些汇编指令背后的逻辑,虽然它们可能看起来并不直接有用,但它们是x86架构中非常有趣的细节。将以32位(x86)指令为例进行讨论。

XOR EAX, EAX指令解析

首先,来看XOR EAX, EAX指令。这是一个按位异或操作,它将两个操作数进行异或操作,并将结果赋值给目标操作数。在MASM和Intel格式中,左边的操作数是目标操作数,而右边是源操作数。在AT&T格式中,它们的位置是相反的。但在例子中,由于操作数是同一个寄存器,所以格式并不重要。由于本文基于使用MASM的Visual C++,可以假设使用的是MASM/Intel格式。Intel指令集要求一个操作数必须是寄存器类型,因此不能有两个内存操作数。

现在,让猜猜这个指令的作用是什么?为了帮助猜测答案,这里是XOR操作的真值表。

答案:它将寄存器清零!Visual C++优化器生成这个代码是因为这个操作比将字面量零移动到寄存器更快。

TEST EBX, EBX指令解析

接下来是TEST指令,它执行两个操作数的按位AND操作。SF、ZF、PF标志会被修改,而AND操作的结果会被丢弃。OF和CF标志被设置为0,AF标志是未定义的。然后,根据标志调用条件跳转指令。

猜猜这个指令的作用是什么?为了帮助猜测答案,这里是按位AND操作的真值表。

答案:它用于测试操作数是否为零。之后通常会跟随JZ或JNZ跳转指令。

MOV EDI, EDI指令解析

第三个问题是MOV指令,它将源操作数的值移动到目标操作数。当在Google上搜索这个指令时,会发现许多关于这个看似无用的指令的结果,它出现在32位Windows上每个Windows API的开头。猜猜这个指令的作用是什么?

答案:实际上它没有做任何事情。它是为了热补丁使用。在热补丁过程中,这个两字节的指令被替换为一个范围在-128到127字节的两字节短跳转指令。如果被补丁函数的地址超过127字节,这个指令就没有用了。函数前面的空间被一系列NOP(无操作)指令填充。因此,短跳转指令跳转到那个位置,然后从那里开始,执行一个长跳转到热补丁函数。

不相信表面的文字,必须亲自让Visual C++生成MOV指令。按照以下步骤操作。创建一个新的Visual C++控制台项目,命名为Test,并使用以下代码。或者可以在文章顶部下载示例项目。

#include int my_func(int n) { n += 2; return n; } int main() { int n = 10; n = my_func(n); printf("Value: %d\n", n); }

要设置Visual C++生成汇编代码,请转到“项目属性”->“配置属性”->“C/C++”->“输出文件”->“汇编输出”,并选择“带源代码的汇编(/FAs)”。

要设置Visual C++生成热补丁指令,请转到“项目属性”->“配置属性”->“C/C++”->“命令行”,并在“x86/Win32平台”的“附加选项”中添加“/hotpatch”。在x64平台下,热补丁是默认的,无需指定。然而,无法让Visual C++为x64平台生成热补丁指令。如果知道发生了什么,请在下面留言。

要设置Visual C++在函数前生成NOP指令,请转到“项目属性”->“配置属性”->“链接器”->“命令行”,并在“附加选项”中添加“/FUNCTIONPADMIN”。这一步是可选的。但对生成的NOP指令类型感兴趣。

然后在x86/Win32平台下构建项目。在Release文件夹中,用喜欢的文本编辑器或Visual Studio查看Test.asm。

?my_func@@YAHH@Z PROC ; my_func, COMDAT ; 4 : { npad 2 push ebp mov ebp, esp ; 5 : n += 2; mov eax, DWORD PTR _n$[ebp] add eax, 2 mov DWORD PTR _n$[ebp], eax ; 6 : return n; mov eax, DWORD PTR _n$[ebp] ; 7 : } pop ebp ret 0 ?my_func@@YAHH@Z ENDP ; my_func NPAD is not a valid Intel x86 assembly instruction. Fire up Windows Debugger to open the Test.exe and use this command uf Test!my_func to unassemble my_func in the command window. ; Instead of mov edi, edi, xchg ax, ax is the first instruction of my_func which occupied two bytes and is equivalent to “doing nothing”. ; NPAD 2 seems like a directive to the linker to pad the function with two-byte instruction. ; Use this WinDbg command ub Test!my_func to unassemble my_func backward in the command window to show the instructions before my_func. ; The eight padded CC instruction is called interrupt 3 which is DebugBreak. If a debugger is attached to the process, it will break as if a breakpoint reached. If no debugger is present, it is effectively a NOP. The space between functions is no man’s land, meaning no process shall execute those CC instructions.
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485