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.asm到ELF目标文件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
我尝试汇编一些由 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.asm到ELF目标文件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