为什么 Qemu 运行 与原生 运行 不同?
Why does the Qemu run differ from the native run?
我做了什么?
我 运行 qemu-x86_64 -singlestep -d nochain,cpu ./dummy
在每条指令后转储虚拟程序的所有寄存器,并使用 grep 将所有 RIP 值保存到文本文件中 (qemu_rip_dump.txt)。然后,我使用 ptrace 单步执行虚拟程序,并在每条指令后将 RIP 值转储到另一个文本文件 (ptrace_rip_dump.txt)。然后我将两个 .txt 文件与 diff
.
进行了比较
我期望的结果是什么?
我希望虚拟程序的两个 运行 执行相同的指令,因此两个转储文件是相同的(相同的 rip 值和相同数量的 rip 值)。
我实际得到了什么结果?
Ptrace 转储了大约 33.500 个 RIP 值,Qemu 转储了 29.800 个 RIP 值。两个文本文件的 RIP 值开始与 240. 指令不同,大多数 rip 值是相同的,但 ptrace 执行了大约 5500 条 qemu 不执行的指令,qemu 执行了大约 1800 条 ptrace 不执行的指令,因此导致大约 3700 条指令的差异。两个 运行 似乎在整个程序中执行不同的事情,例如,本机 运行 执行但不是 qemu 的 26.500-30.000 指令(清理?)中有一个 3500 条指令块。
我的问题是什么
为什么在整个程序执行过程中 RIP 值不相同,最重要的是:我必须做什么才能使两个 运行 相同?
额外信息
- 虚拟程序是 returns 0 的主要功能,但我跟踪的每个可执行文件都存在此问题
- 我已经尝试使用
ld-linux-x86-64.so.2
链接器和 -L /lib64/
强制 qemu - 这没有效果
- 如果我 运行 qemu 多次转储是相同的(RIP 的数量和值相等),ptrace 也是如此
对于像您正在测试的“什么都不做”的程序,大部分执行 运行 将在来宾动态链接器和 libc 中进行。在你的程序获得控制之前,它们在幕后做了很多工作,其中一些工作在“本地”运行 和“QEMU”运行 之间变化。根据您在评论中提供的一些额外细节判断,有两个主要的分歧来源:
QEMU 提供给来宾二进制文件的环境与真实主机内核提供的环境并非 100% 相同;它只是为了“足够接近正确的来宾二进制文件以合理的方式运行”。例如,有一个数据结构传递给客户,称为“ELF 辅助向量”;其中包含的信息包括“支持哪些 CPU 功能”、“您正在执行的用户 ID”等等。动态链接器在启动时遍历此数据结构,因此向量中的条目顺序的细微差别会导致来宾代码中的执行路径略有不同。
CPU QEMU 模拟不提供与您的主机 CPU 完全相同的功能。例如,不支持模拟 AVX 或 SSE2。来宾 libc 将调整其行为,以便在可用时利用 CPU 功能,因此它会在后台选择不同的优化版本的功能,如 memcpy() 或 strlen()。由于动态链接器最终会调用这些函数,这也会导致执行的分歧。
您可以通过将您查看的指令跟踪区域限制为仅从 'main' 函数的开头开始来避免跟踪所有动态链接器启动,从而解决其中的一些问题。不过,我想不出一种方法来解决主机与 QEMU 上可用的 CPU 功能的差异。
我做了什么?
我 运行 qemu-x86_64 -singlestep -d nochain,cpu ./dummy
在每条指令后转储虚拟程序的所有寄存器,并使用 grep 将所有 RIP 值保存到文本文件中 (qemu_rip_dump.txt)。然后,我使用 ptrace 单步执行虚拟程序,并在每条指令后将 RIP 值转储到另一个文本文件 (ptrace_rip_dump.txt)。然后我将两个 .txt 文件与 diff
.
我期望的结果是什么?
我希望虚拟程序的两个 运行 执行相同的指令,因此两个转储文件是相同的(相同的 rip 值和相同数量的 rip 值)。
我实际得到了什么结果?
Ptrace 转储了大约 33.500 个 RIP 值,Qemu 转储了 29.800 个 RIP 值。两个文本文件的 RIP 值开始与 240. 指令不同,大多数 rip 值是相同的,但 ptrace 执行了大约 5500 条 qemu 不执行的指令,qemu 执行了大约 1800 条 ptrace 不执行的指令,因此导致大约 3700 条指令的差异。两个 运行 似乎在整个程序中执行不同的事情,例如,本机 运行 执行但不是 qemu 的 26.500-30.000 指令(清理?)中有一个 3500 条指令块。
我的问题是什么
为什么在整个程序执行过程中 RIP 值不相同,最重要的是:我必须做什么才能使两个 运行 相同?
额外信息
- 虚拟程序是 returns 0 的主要功能,但我跟踪的每个可执行文件都存在此问题
- 我已经尝试使用
ld-linux-x86-64.so.2
链接器和-L /lib64/
强制 qemu - 这没有效果 - 如果我 运行 qemu 多次转储是相同的(RIP 的数量和值相等),ptrace 也是如此
对于像您正在测试的“什么都不做”的程序,大部分执行 运行 将在来宾动态链接器和 libc 中进行。在你的程序获得控制之前,它们在幕后做了很多工作,其中一些工作在“本地”运行 和“QEMU”运行 之间变化。根据您在评论中提供的一些额外细节判断,有两个主要的分歧来源:
QEMU 提供给来宾二进制文件的环境与真实主机内核提供的环境并非 100% 相同;它只是为了“足够接近正确的来宾二进制文件以合理的方式运行”。例如,有一个数据结构传递给客户,称为“ELF 辅助向量”;其中包含的信息包括“支持哪些 CPU 功能”、“您正在执行的用户 ID”等等。动态链接器在启动时遍历此数据结构,因此向量中的条目顺序的细微差别会导致来宾代码中的执行路径略有不同。
CPU QEMU 模拟不提供与您的主机 CPU 完全相同的功能。例如,不支持模拟 AVX 或 SSE2。来宾 libc 将调整其行为,以便在可用时利用 CPU 功能,因此它会在后台选择不同的优化版本的功能,如 memcpy() 或 strlen()。由于动态链接器最终会调用这些函数,这也会导致执行的分歧。
您可以通过将您查看的指令跟踪区域限制为仅从 'main' 函数的开头开始来避免跟踪所有动态链接器启动,从而解决其中的一些问题。不过,我想不出一种方法来解决主机与 QEMU 上可用的 CPU 功能的差异。