我尝试组装一些由gcc生成的中间代码。我使用了命令as -o hello hello.s
,据我所知,这是正确的语法。当我试图运行该程序时,它说bash: ./hello: cannot execute binary file
。组装代码似乎没有问题,因为它是由gcc生成的代码,并且看起来我调用汇编程序的方式似乎没有任何问题,因为这似乎是正确的语法根据this manual。任何人都可以帮我这个吗?
答案 0 :(得分:3)
编译器和汇编器都不会生成可执行文件。两者都生成目标文件,然后可以与其他对象和/或库文件链接以生成可执行文件。
例如,命令gcc -c
只调用编译器;它可以将hello.c
之类的源文件作为输入,并生成像hello.o
这样的目标文件作为输出。
同样,as
可以使用汇总语言源文件hello.s
,并生成像hello.o
这样的对象文件。
链接器是一个单独的工具,可以从目标文件生成可执行文件。
恰好在一步中编译和链接非常方便,这就是gcc
命令默认执行的操作; gcc hello.c -o hello
调用编译器和链接器生成可执行文件。
请注意,gcc
命令不仅仅是一个编译器。它是一个驱动程序,它调用预处理程序,编译器本身,汇编程序和/或链接程序。 (预处理器和汇编器,可以被认为是编译器的组件,在某些情况下它们甚至不是单独的程序,或者编译器可以生成机器对象代码而不是汇编代码。)< / p>
实际上,您也可以在汇编语言的一个命令中执行相同的多步骤过程:
gcc hello.s -o hello
将调用汇编程序和链接器并生成可执行文件。
这是特定于gcc(可能对于类Unix系统的大多数其他编译器)。其他实现可能以不同的方式组织。
答案 1 :(得分:2)
假设您的程序集文件名为 hello.s ,看起来像(假设32位 Linux 目标):
.data
msg: .asciz "Hello World\n"
msglen = .-msg
.text
.global _start
_start:
/* Use int $0x80/eax=4 to write to STDOUT */
/* Output Hello World */
mov $4, %eax /* write system call */
mov $0, %ebx /* File descriptor 0 = STDOUT */
mov $msg, %ecx /* The message to output */
mov $msglen, %edx /* length of message */
int $0x80 /* make the system call */
/* Exit the program with int $0x80/eax=1 */
mov $1, %eax /* 1 = exit system call */
mov $0, %ebx /* value to exit with */
int $0x80 /* make the system call */
这是一个采用AT&amp; T语法的32位Linux汇编程序,通过Hello World
使用32位系统调用向标准输出显示int $0x80
。它不使用任何 C 函数,因此可以与GNU汇编程序as
汇编并与GNU链接器ld
链接以生成最终的可执行文件。
as --32 hello.s -o hello.o
ld -melf_i386 hello.o -o hello
第一行将 hello.s 组装成一个名为 hello.o 的32位 ELF 对象。然后使用第二个命令将 hello.o 链接到名为 hello 的32位 ELF 可执行文件。 GNU链接器默认情况下假定您的程序在标签_start
处开始执行。
或者,您可以使用 GCC 使用此命令汇编和链接此程序:
gcc -nostdlib -m32 hello.s -o hello
这将生成一个名为 hello 的32位 ELF 可执行文件。 -nostdlib
告诉 GCC 不要在 C 运行时库中链接,并允许我们使用_start
作为我们程序的入口点。
如果要将汇编程序链接到 C 运行时和库,以便它可以使用 C 的 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 $4,%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 运行时将有一个名为_start
的入口点,它执行 C 运行时初始化,然后调用名为{{1}的函数我们在汇编代码中提供的。要编译/汇编和链接,我们可以使用:
main
注意:在Windows上, C 运行时调用的入口点为gcc -m32 hello.s -o hello
,而不是_WinMain
。
在评论中,您还询问了 NASM ,因此我将在汇总时提供一些信息。假设您的程序集文件名为 hello.asm ,看起来像(它不需要 C 运行时库):
main
然后将其构建为可执行文件,您可以使用以下命令:
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
第一个命令将 hello.asm 汇编到 ELF 目标文件 hello.o 。第二行是链接。 nasm -f elf32 hello.asm -o hello.o
gcc -nostdlib -m32 hello.o -o hello
排除了 C 运行时的链接(像-nostdlib
等函数不可用)。第二行将 hello.o 链接到可执行文件 hello 。
或者,您可以跳过使用 GCC 并直接使用链接器:
_printf
如果您需要 C 运行时和库来调用nasm -f elf32 hello.asm -o hello.o
ld -melf_i386 hello.o -o hello
之类的内容,那么它会有所不同。假设您拥有需要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