Hello World 程序 Nasm 程序集和 C 的执行指令数不同

Number of executed Instructions different for Hello World program Nasm Assembly and C

我有一个简单的调试器(使用 ptrace:http://pastebin.com/D0um3bUi)来计算给定输入可执行程序执行的指令数。它使用ptrace单步执行模式来计算指令。

为此,当程序 1) 的可执行文件(a.out 来自 gcc main.c)作为我的测试调试器的输入时,它会在执行指令时打印大约 100k。当我使用 -static 选项时,它给出了 10681 条指令。

现在 2) 我创建了一个汇编程序并使用 NASM 进行编译和链接,然后当此可执行文件作为测试调试器输入给出时,它显示 8 条指令作为计数,这是恰当的。

程序 1) 中执行的指令数很高,因为在运行时将程序与系统库链接?使用 -static 并将计数减少 1/10。我怎样才能确保指令数只是程序 1) 中主要函数的指令数,而程序 2) 是如何为调试器报告的?

1)

#include <stdio.h>

int main()
{
    printf("Hello, world!\n");
    return 0;
}    

我使用 gcc 创建可执行文件。

2)

; 64-bit "Hello World!" in Linux NASM

global _start            ; global entry point export for ld

section .text
_start:

    ; sys_write(stdout, message, length)

    mov    rax, 1        ; sys_write
    mov    rdi, 1        ; stdout
    mov    rsi, message    ; message address
    mov    rdx, length    ; message string length
    syscall

    ; sys_exit(return_code)

    mov    rax, 60        ; sys_exit
    mov    rdi, 0        ; return 0 (success)
    syscall

section .data
    message: db 'Hello, world!',0x0A    ; message and newline
    length:    equ    $-message        ; NASM definition pseudo-                             

我用:

构建
nasm -f elf64 -o main.o -s main.asm  
ld -o main main.o

The number of instructions executed in program 1) is high because of linking the program with system library's at runtime?

是的,动态链接加上 CRT(C 运行时间)启动文件。

used -static and which reduces the count by a factor of 1/10.

所以只剩下 CRT 启动文件,它在调用 main 之前和之后做一些事情。

How can I ensure that the instruction count is only that of the main function in Program 1)`

测量一个空的 main,然后从以后的测量中减去该数字。

除非您的指令计数器更智能,并查看可执行文件中的符号以了解其正在跟踪的进程,否则它将无法判断哪些代码来自何处。

and which is how Program 2) is reporting for the debugger.

那是因为该程序中没有其他代码。并不是你以某种方式帮助调试器忽略了一些指令,而是你编写了一个没有任何指令的程序,这不是你自己放的。

如果你想看看当你 运行 gcc 输出时 实际上 发生了什么,gdb a.out, b _start, r,和单步。一旦你深入调用树,你就很可能。将要使用 fin 来完成当前函数的执行,因为您不想单步执行 100 万条指令,甚至 10k。


相关How do I determine the number of x86 machine instructions executed in a C program? 显示 perf stat 将在 NASM 程序中计算 3 个用户-space 指令mov eax, 231 / syscall,链接成静态可执行文件。

Peter 给出了一个非常好的答案,我将跟进一个令人畏缩的回应,并且可能会获得一些反对票。当直接链接 LD 或间接链接 GCC 时,ELF 可执行文件的默认入口点是标签_start.

您的 NASM 代码使用全局标签 _start 所以当您的程序是 运行 时,您程序中的第一个代码将是_start 的说明。使用 GCC 时,程序的典型入口点是函数 main。对你隐藏的是你的 C 程序也有一个 _start 标签,但它是由 C 运行时间启动对象。

现在的问题是 - 有没有办法绕过 C 启动文件,从而避免启动代码?从技术上讲是的,但这是一个危险的领域,可能会产生未定义的行为。如果您喜欢冒险,您实际上可以告诉 GCC 使用 -e 命令行选项更改程序的入口点。我们可以让入口点 main 绕过 C 启动代码,而不是 _start。由于我们绕过了 C 启动代码,我们也可以免除 C 运行time 启动代码中的链接 -nostartfiles选项。

您可以使用此命令行来编译您的 C 程序:

gcc test.c -e main -nostartfiles

不幸的是,C 代码中有一些问题需要修复。通常在使用 C 运行 时间启动对象时,在环境初始化后,会对 main 进行 CALL。通常 main 执行 RET 指令,return 返回 C 运行 时间码。那时 C 运行 时间优雅地退出你的程序。当使用 -nostartfiles 选项时,RET 没有任何地方可以 return,因此它可能会出现段错误。为了解决这个问题,我们可以调用 C_exit 函数来退出我们的程序。

#include <stdio.h>

int main()
{
    printf("Hello, world!\n");
    _exit(0);  /* We exit application here, never reaching the return */

    return 0;
}   

除非您省略帧指针,否则 GCC 会发出一些额外的指令来设置堆栈帧并将其拆除,但开销很小。

特别说明

上述过程似乎不适用于使用标准 glibc C[=113= 的静态构建(GCC 中的-static 选项) ] 图书馆。 Whosebug answer. The dynamic version works because a shared object can register a function that gets called by the dynamic loader to perform initialization. When building statically this is generally done by the C runtime, but we've skipped that initialization. Because of that GLIBC functions like printf can fail. There are replacement C libraries that are standards compliant that can operate without C runtime initialization. One such product is MUSL 对此进行了讨论。

安装 MUSL 作为 GLIBC 的替代品

在 Ubuntu 64 位上,这些命令应该构建并安装 MUSL:

的 64 位版本
git clone git://git.musl-libc.org/musl
cd musl
./configure --prefix=/usr/local/musl/x86-64
make
sudo make install

然后您可以使用 MUSL 包装器 GCC 来处理 MUSL' s C 库,而不是大多数 Linux 发行版上的默认 GLIBC 库。参数就像 GCC 所以你应该能够做到:

/usr/local/musl/x86-64/bin/musl-gcc -e main -static -nostartfiles test.c

当 运行ning ./a.out 使用 GLIBC 生成时,它可能会出现段错误。 MUSL 在使用大多数 C 库函数之前不需要初始化,因此它甚至可以与 -static GCC 选项。


更公平的比较

您的比较的一个问题是您直接在 NASM[=113= 中调用 SYS_WRITE 系统调用],在 C 中,您使用的是 printf。用户 EOF 正确地评论说,您可能希望通过调用 C 中的 write 函数而不是 printf 来进行更公平的比较。 write 的开销要小得多。您可以将代码修改为:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    char *str = "Hello, world\n";
    write (STDOUT_FILENO, str, 13);
    _exit(0);
    return 0;
}

这将比 NASM 的直接 SYS_WRITE 系统调用有更多的开销,但远小于printf 会产生什么。


我要发出警告,除了一些软件开发的边缘案例外,代码审查可能不会很好地采用此类代码和技巧。