GNU 汇编程序没有生成我可以执行的程序

GNU assembler did not produce a program that I can execute

我尝试汇编一些由 gcc 生成的中间代码。我使用了命令 as -o hello hello.s,据我所知,这是正确的语法。当我尝试 运行 程序时,它显示 bash: ./hello: cannot execute binary file。汇编代码似乎没有问题,因为它是由 gcc 生成的代码,而且我调用汇编程序的方式似乎也没有任何问题,因为这似乎是正确的语法this manual。谁能帮我解决这个问题?

编译器和汇编器都不能生成可执行文件。两者都生成一个 目标文件 ,然后可以将其与其他目标 and/or 库文件链接以生成可执行文件。

例如,命令gcc -c只调用编译器;它可以将像 hello.c 这样的源文件作为输入,并生成像 hello.o 这样的目标文件作为输出。

同样,as 可以像 hello.s 这样的汇编语言源文件并生成像 hello.o.

这样的目标文件。

链接器 是一个单独的工具,可以从目标文件生成可执行文件。

恰好一步编译和链接是如此方便,以至于gcc命令默认就是这样做的; gcc hello.c -o hello 调用编译器 链接器生成可执行文件。

请注意,gcc 命令不仅仅是一个编译器。它是一个调用预处理器、编译器本身、汇编器、and/or 链接器的驱动程序。 (预处理器和汇编器,可以被认为是编译器的组件,在某些情况下它们甚至不是单独的程序,或者编译器可以生成机器目标代码 而不是 汇编代码.)

事实上,对于汇编语言,您也可以在一条命令中执行相同的多步过程:

gcc hello.s -o hello

将调用汇编器和链接器并生成一个可执行文件。

这是 gcc 特有的(也可能是大多数其他类 Unix 系统的编译器)。其他实现的组织方式可能有所不同。

使用 GNU 汇编程序

假设您的程序集文件名为 hello.s 并且看起来像(假设一个 32 位 Linux目标):

.data
msg:     .asciz "Hello World\n"
msglen = .-msg
.text
.global _start
_start:
    /* Use int [=10=]x80/eax=4 to write to STDOUT */
    /* Output Hello World */
    mov , %eax       /* write system call */
    mov [=10=], %ebx       /* File descriptor 0 = STDOUT */
    mov $msg, %ecx     /* The message to output */
    mov $msglen, %edx  /* length of message */
    int [=10=]x80          /* make the system call */

    /* Exit the program with int [=10=]x80/eax=1 */
    mov , %eax       /* 1 = exit system call */
    mov [=10=], %ebx       /* value to exit with */
    int [=10=]x80          /* make the system call */

这是一个 AT&T 语法的 32 位 Linux assembler 程序,通过 int [=21=]x80 使用 32 位系统调用将 Hello World 显示到标准输出。它不使用任何 C 函数,因此可以 assembled 与 GNU assembler as 和 linked 与GNU linker ld 生成最终的可执行文件。

as --32 hello.s -o hello.o
ld -melf_i386 hello.o -o hello

第一行assembles hello.s变成一个32位的ELF对象叫hello.o。然后 hello.o linked 为 32 位 ELF 可执行文件 hello 与第二个命令。 GNU linker 默认假定您的程序在标签 _start 处开始执行。

或者你可以使用 GCC 到 assemble 和 link 这个程序用这个命令:

gcc -nostdlib -m32 hello.s -o hello

这将生成一个名为 hello 的 32 位 ELF 可执行文件。 -nostdlib 告诉 GCC 不要在 C 运行时库中 link 并允许我们使用 _start作为我们程序的入口点。

如果您的 assembler 程序打算 linked 到 C 运行时和库,以便它可以利用像 C's printf 那么事情就有点不同了。假设您的程序需要 printf(或任何 C 库函数):

.data
msg: .asciz "Hello World\n"
.text
.global main
main:
    push %ebp          /* Setup the stack frame */
    mov %esp, %ebp     /* Stack frames make GDB debugging easier */

    push  $msg         /* Message to print */
    call  printf
    add   ,%esp      /* cleanup the stack */

    xor %eax, %eax     /* Return 0 when exiting */
    mov %ebp, %esp     /* destroy our stack frame */
    pop %ebp
    ret                /* Return to C runtime that called us
                          and allow it to do program termination */

在大多数 *nix 类型的系统上,您的入口点现在必须 main。原因是 C runtime 将有一个名为 _start 的入口点,它执行 C 运行时初始化,然后调用我们在 assembler 代码中提供的名为 main 的函数。对于 compile/assemble 和 link 我们可以使用:

gcc -m32 hello.s -o hello

注意:在 Windows 上,C 运行时调用的入口点是 _WinMain,而不是 main

与NASM

合作

在评论中您还询问了有关 NASM 的问题,因此我会在组装时提供一些信息。假设您的程序集文件名为 hello.asm 并且看起来像(它不需要 C 运行时库):

SECTION .data       ; data section
msg     db "Hello World", 13, 10
len     equ $-msg

SECTION .text       ; code section
    global _start     ; make label available to linker
_start:               ; standard  gcc  entry point

mov edx,len     ; length of string to print
mov ecx,msg     ; pointer to string
mov ebx,1       ; write to STDOUT (file descriptor 0)
mov eax,4       ; write command
int 0x80        ; interrupt 80 hex, call kernel

mov ebx,0       ; exit code, 0=normal
mov eax,1       ; exit command to kernel
int 0x80        ; interrupt 80 hex, call kernel

然后要将其构建为可执行文件,您可以使用如下命令:

nasm -f elf32 hello.asm -o hello.o 
gcc -nostdlib -m32 hello.o -o hello

第一个命令assembles hello.asmELF目标文件hello.o。第二行执行 linking。 -nostdlib 从 link 中排除了 C 运行时(像 _printf 等函数将不可用)。第二行 links hello.o 到可执行文件 hello .

或者你可以跳过使用 GCC 并直接使用 linker 像这样:

nasm -f elf32 hello.asm -o hello.o 
ld -melf_i386 hello.o -o hello

如果您需要 C 运行时和库来调用 printf 之类的东西,那么它有点不同。假设您有此 NASM 代码需要 printf:

    extern  printf

SECTION .data           ; Data section, initialized variables

    msg:      db   "Hello World", 13, 10, 0

SECTION .text           ; Code section.

    global main         ; the standard gcc entry point

main:                   ; the program label for the entry point
    push    ebp         ; Setup the stack frame
    mov     ebp, esp    ; Stack frames make GDB debugging easier

    push    msg         ; Message to print
    call    printf
    add     esp, 4      ; Cleanup the stack

    mov     eax, 0      ; Return value of 0
    mov     esp, ebp    ; Destroy our stack frame
    pop     ebp
endit:
    ret                 ; Return to C runtime that called us
                        ; and allow it to do program termination

然后要将其构建为可执行文件,您可以使用如下命令:

nasm -f elf32 hello.asm -o hello.o
gcc -m32 hello.o -o hello