链接 Linux x86-64 汇编“Hello World”程序失败

3

最近我一直在玩 x86 64位汇编语言,运行一个看似简单的程序后,我却感到困惑 :P

尽管我已经编译和链接了它,也没有报错并生成了一个Linux ELF文件,但当我尝试运行它时,却出现了以下错误:

.:[ h4unt3r@sp3ctr4l-h0st asm ]:.
#(0)> ./hello 
bash: ./hello: No such file or directory

我猜测这是因为生成了一个无效的ELF文件,所以它报告hello不存在,尽管它确实存在。不确定原因 - 我可能会继续尝试解决它,只是好奇是否可以用简单的方法解决 ^_^
这是我的编译/链接命令行:
nasm -f elf64 hello.s -g
ld -o hello hello.o -lc

代码如下:

section .data
    msg: db "Hello, world!",0xa,0

section .text
    extern printf
    global main

main:
    push rbp
    mov rbp, rsp

    mov rdi, msg
    xor rax, rax
    call printf
    xor rax, rax

    pop rbp
    ret

编辑--我不想使用gcc :)

我不想使用gcc。

如果有价值的话,如果您删除-lc标志并注释掉printf的extern,则该文件可以正常工作。 - zxcdw
你怎么看? :) 然后 printf 是未定义的符号... - h4unt3r
实际上,我也将printf调用注释掉了,但我的意思是,如果文件没有与libc链接,它就能正常工作。 - zxcdw
再问一遍,你是怎么想到的? :) 你在使用ld吗?因为gcc会为您完成所有正确的链接...包括库文件,所以如果您使用-nostdlib或-nodefaultlibs,它将无法解析未定义的符号。 - h4unt3r
这是我用来链接的命令。可能链接不完整导致程序无法正确加载。也许有更好知识的人可以解释一下。ld -o hello /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o hello.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o -lc --no-as-needed /usr/lib/gcc/x86_64-linux-gnu/5/crtend.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o -dynamic-linker /lib64/ld-linux-x86-64.so.2 - Den
3个回答

4
我正在运行32位硬件, 无法测试64位的东西。我在32位代码中看到过这个“没有这个文件”的错误。ld默认使用“/lib/ld-linux.so.1” - 您可以在可执行文件中以纯文本形式看到该字符串。这就是不存在的文件(显然“hello”就在那里!)。解决方法是告诉ld -I/lib/ld-linux.so.2。我猜想类似的解决方案也适用于64位,但我不知道您需要什么“解释器”或“动态链接器”。尝试在可执行文件中查找类似的字符串,并在您的库中查找类似的.so文件。您不应该“需要”使用gcc...但是gcc知道在哪里找到这些东西!使用它可能更容易。这真是一个令人困惑的错误,不是吗?
(如果您要以这种方式进行操作,我希望您的入口点为_start而不是main。你将无法从中ret。请使用sys_exit或exit()。)
我对nrz提到的关于“符号表0”的错误不熟悉。肯定不是Nasm的故意更改!Nasm的开发人员在http://www.nasm.us或其周围活动,并很高兴在那里听取反馈和错误报告。(好吧,也许不会“很高兴”收到错误报告。)我会看看能否找出更多信息...
顺便说一句,Nasm默认使用“stabs”调试信息,只需使用-g开关即可。要启用“dwarf”调试信息,请使用-F dwarf...应该效果更好...

谢谢!你完全正确 :) - h4unt3r

3

首先,要使用printf,你需要使用gcc进行链接,而不是ld

gcc -o hello hello.o

然后,另一个问题可能与我自己遇到的相同。我不确定它是NASM中的错误还是有意的更改:
user@computer:~/code/asm$ nasm -f elf64 hello.asm -g; gcc -o hello hello.o
/usr/bin/ld: error: relocation section 9 uses unexpected symbol table 0
collect2: error: ld returned 1 exit status
我通过转向YASM并以以下方式进行汇编和链接来解决了该问题:
yasm -f elf64 hello.s -g dwarf2
gcc -o hello hello.o

这将生成一个具有预期输出的可执行文件:

./hello
Hello, world!

1
嗯,这就是问题所在...我不想使用gcc进行链接,我想使用ld :) 如果我理解正确的话,gcc会在之后调用ld... - h4unt3r
@h4unt3r 你是对的,问题在于标准库丢失了。 - stdcall
不是的,因为printf符号在stdlib中,我使用ld标志-lc显式链接它。 - h4unt3r

2

稍微修改一下代码。

section .data
        msg:    db "Hello, world!",0xa,0
section .text
        extern  printf
 global _start
_start:
        ; RSP already 16-byte aligned, ready for a function call.
        mov     rdi,msg           ; or better, lea rdi, [rel msg]
        xor     eax, eax          ; AL=0  - no XMM args
        call    printf

        mov     rax,60  ; use _exit syscall
        mov     rdi,0   ; exit code 0
        syscall         ; call kernel

使用这个链接来设置正确的现代 ld.so 路径,而不是默认的 ld,后者在现代 GNU/Linux 系统上不存在。

ld hello.o -o hello -lc --dynamic-linker /lib64/ld-linux-x86-64.so.2

请注意,这仅适用于动态链接的可执行文件。如果链接静态文件,则需要手动调用 glibc 的 init 函数以初始化其数据结构(如 stdio 缓冲区),然后才能使诸如 printf 之类的函数正常工作。


在64位代码中,不推荐使用int 0x80(可以生成没有32位代码兼容性的Linux内核)。首选方法是通过_SYSCALL_指令(注意,系统调用号和数据传递方式与int 0x80不同)。如果与_C_运行时链接,则使用ret不是问题,因为main是一个函数,并且堆栈上有一个返回地址,该地址返回到调用它的_C_运行时函数(只需确保遵循哪些寄存器是调用者/被调用者保存的规则)。 - Michael Petch
@MichaelPetch 根据您的建议修改为基于系统调用。谢谢! - Den

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接