为什么 qemu 有时比 ptrace 计算的指令多有时少?

Why does qemu sometimes count more and sometimes less instructions than ptrace?

我想在每次执行指令后将寄存器与 qemu 生成的寄存器转储进行比较。 因此,我编写了一个程序,它使用 ptrace 遍历程序的每条执行指令,并能够在每条指令之后转储寄存器。我已将程序简化为仅适用于 /bin/ls,而不是转储寄存器,它只计算执行的指令数。

剧透:qemu 和 ptrace 的指令数不匹配,相差几千条指令。

这是我写的代码:

#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/user.h>
#include <sys/reg.h>    
#include <sys/syscall.h>

int main()
{   
    pid_t child;
    child = fork(); //create child
    
    if(child == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        char* child_argv[] = {"/bin/ls", NULL};
        execv("/bin/ls", child_argv);
    }
    else {
        int status;
        long long ins_count = 0;
        while(1)
        {
            //stop tracing if child terminated successfully
            wait(&status);
            if(WIFEXITED(status))
                break;

                ins_count++;
                ptrace(PTRACE_SINGLESTEP, child, NULL, NULL);
        }

    printf("\n%lld Instructions executed.\n", ins_count);

    }
    
    return 0;
}

运行 这段代码让我执行了 492611 条指令。我知道这些指令中的大部分来自执行其工作的动态链接器。如果我在每条指令后转储寄存器,/bin/ls 的第一个寄存器转储将准备就绪。

现在我想在 qemu 的每条指令后转储寄存器。我使用以下命令单步执行来自 /bin/ls 的每条指令,并在进入每个翻译块之前转储寄存器状态。我禁用了 qemu 的翻译块链接以在每个实际指令之前转储寄存器。

qemu-x86_64 -singlestep -D logfile -d nochain,cpu /bin/ls

查看日志文件,每条指令的寄存器转储由20行组成,例如:

RAX=0000000000000000 RBX=0000000000000000 RCX=0000000000000000 RDX=0000000000000000
RSI=0000000000000000 RDI=0000000000000000 RBP=0000000000000000 RSP=0000004000805180
R8 =0000000000000000 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=0000004000807100 RFL=00000202 [-------] CPL=3 II=0 A20=1 SMM=0 HLT=0
ES =0000 0000000000000000 00000000 00000000
CS =0033 0000000000000000 ffffffff 00effb00 DPL=3 CS64 [-RA]
SS =002b 0000000000000000 ffffffff 00cff300 DPL=3 DS   [-WA]
DS =0000 0000000000000000 00000000 00000000
FS =0000 0000000000000000 00000000 00000000
GS =0000 0000000000000000 00000000 00000000
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT=     0000004000837000 0000007f
IDT=     0000004000836000 000001ff
CR0=80010001 CR2=0000000000000000 CR3=0000000000000000 CR4=00000220
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000000 CCD=0000000000000000 CCO=EFLAGS  
EFER=0000000000000500

我计算了日志文件的行数:

wc -l logfile

这给了我 9.873.540 行,结果是 9.873.540/20 = 493.677 条指令的寄存器转储。

所以对于 /bin/ls,qemu 比我的 ptrace 程序多计算了 1.066 条指令。 我为“return null”程序和打印数字 0-9 的程序做了同样的事情。结果如下:

returnnull:
qemu counts 105.351 instructions vs ptrace counts 109.308 -> qemu counts 3.957 instructions less than ptrace

printf 0-9:
qemu counts 2.188.344 instructions vs ptrace counts 2.194.793 -> qemu counts 6.449 instructions less than ptrace

为什么 qemu 和 ptrace 计算的指令数不完全相同。为什么 qemu 有时比 ptrace 计算的指令多有时少?我该怎么做才能拥有相同指令的寄存器转储 并且能够比较这些?

如果你在使用 ptrace 单步执行时实际转储了指令地址,你应该能够自己回答这个问题,做一些基本的文本处理,运行 diff -u(一定要也关闭 ASLR,例如 运行ning 在 setarch linux64 -R ...).

一种可能是由于不同的入口点状态(auxv 等),不同的代码实际上在启动序列中执行(在动态链接器中或通过 __libc_start_main 或等效的东西到达)该程序由内核与 qemu 加载。减少这种情况的一种快速方法是使用静态链接进行测试。如果那消除了差异,那可能是唯一的原因;如果它只是改变了它,那么可能涉及多种原因。