在运行由`ld`生成的可执行文件时出现`bash: ./a.out: No such file or directory`错误。

17

这是C语言的“Hello World”代码:

// a.c
#include <stdio.h>

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

我使用gcc a.c进行编译,得到了预期的a.out文件,然后运行./a.out,输出了Hello world

现在如果我分别进行编译和链接:gcc -c a.c; ld -lc a.o,然后运行由a.out生成的./a.out,会得到以下信息:

bash: ./a.out: No such file or directory

我谷歌搜索了这个错误,似乎是由于生成的可执行文件是32位ELF格式,并且计算机架构是64位导致的。

我正在运行一台64位机器,并且运行file a.out命令给出以下结果:

a.out: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), not stripped

为什么会发生这种情况?

编辑:

uname -m 的输出结果

$ uname -m
x86_64

ldd a.out 的输出结果

$ ldd a.out
    linux-vdso.so.1 =>  (0x00007ffeeedfb000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa13a7b8000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fa13abab000)

gcc a.c生成可执行文件a.out并且运行正确。


一个64位的机器并不一定意味着它运行的是64位操作系统。uname -m命令的输出是什么? - cadaniluk
@MichaelBurr 同样的错误,/bin/bash: /home/pratyaksh/temp/a.out: 没有那个文件或目录 - piggs_boson
4
您可以执行命令:gcc -v a.c,并检查链接阶段(通常是否需要与启动对象crt0.o进行链接)。 - Joan Esteban
@arrowd 我试过了,没有改变任何事情 :-) - piggs_boson
2
为什么你不想让gcc处理链接呢? - Ignacio Vazquez-Abrams
显示剩余3条评论
3个回答

11
这个命令行存在几个问题:
  1. 一般来说,用户级代码应该永远不要直接使用ld,而应该始终使用适当的编译器前端(这里是gcc)执行链接操作。如您所发现的那样,gcc构造的链接命令行非常复杂,并且Joan Esteban答案中接受的命令行是错误的。如果想查看实际的链接命令,请检查gcc -v a.o的输出。还要注意的是,当你稍微改变gcc命令时,链接命令会有很大的变化(例如,一些操作系统需要不同的crt1.o,具体取决于你是否链接多线程可执行文件),并且命令行始终是特定于操作系统的(这是永远不要直接使用ld的另一个原因)。
  2. 库文件应该在命令行上跟随目标文件。因此,ld -lc a.o永远不正确的,应该是(某种变体的)ld a.o -lc解释

2
解释存档链接 https://web.archive.org/web/20180627210132/http://webpages.charter.net/ppluzhnikov/linker.html - shibormot

9
使用gcc foo.o来链接动态可执行文件(以使用正确的CRT和libc路径以及动态链接器/ELF解释器ld-linux-x86-64.so.2)。或者使用gcc -nostartfiles foo.o来链接libc但不链接CRT _start,如果您有一个手写的_start
(对于没有libc或CRT的静态可执行文件,可以直接使用ldgcc -nostdlib -static。) gcc -v foo.o将向您显示GCC在您的系统上使用的实际路径。
其他答案只涉及如何避免这种情况1,而不是实际问题发生了什么。
您提供的gcc -c a.c; ld -lc a.o命令会产生一个非常明显的警告:
ld: warning: cannot find entry symbol _start; defaulting to 0000000000400260

即使这个文件能够被执行,它也很可能会立即崩溃。请参考@EmployedRussian的答案,了解您应该采取的措施。


为什么甚至不能执行这个文件仍然是一个有趣的问题:

$ strace ./a.out 
execve("./a.out", ["./a.out"], [/* 72 vars */]) = -1 ENOENT (No such file or directory)

execve(2)返回ENOENT,因为它找不到解释器(我从下面的file等中发现了这一点)。如果尝试运行以此开头的文件,您将收到相同的错误。

#!/usr/non-existant-path/bin/bash

正如你所发现的,这个错误信息通常出现在没有正确安装动态链接器和动态库的系统上运行ELF二进制文件时(例如,在没有安装32位支持���64位系统上)。 在你的情况下,是因为你使用了错误的链接命令,并且使用了错误的解释器路径创建了一个动态可执行文件。


我使用的是Ubuntu 15.10,GNU file版本5.22报告:

a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld64.so.1, not stripped

我的系统上没有 /lib/ld64.so.1ldd 的输出很令人困惑,因为 ldd 使用其默认的 ELF 解释器,而不是二进制文件指定的解释器。

$ ldd a.out
        linux-vdso.so.1 =>  (0x00007ffc18d2b000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0e0a79f000)
        /lib/ld64.so.1 => /lib64/ld-linux-x86-64.so.2 (0x0000559dbc9d2000)

所以它假设二进制文件中的运行时解释器与ldd本身使用的解释器相同,我猜测是这样的。

你的ldd输出也可能来自一个旧版本,因为该行仅显示/lib64/ld-linux-x86-64.so.2。对于这种奇怪的情况,不进行猜测可能是更好的行为,但是这并不能帮助你看到你的二进制文件具有奇怪的解释器路径。

readelf -l a.out

它将为您解码ELF头部信息,包括解释器路径。(感谢@EmployedRussian评论中指出这一点。)


2
查看给定二进制文件的解释器的标准方法是 readelf -l a.out | grep interpreter - Employed Russian

5

使用这个:

  ld -o a.out -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o -lc c.o /usr/lib/crtn.o

首先,在我的机器上/usr/lib/x86_64-linux-gnu/中存在crt1.ocrti.ocrtn.o。更改后运行ld以及a.out后,出现错误:bash: ./a.out: 访问损坏的共享库 - piggs_boson
2
@pratyaksh 你还需要将链接器适配到 /lib64/ld-linux-x86-64.so.2 - Marc Glisse
@piggs_boson:这是使用gcc c.ogcc -nostartfiles c.o动态链接可执行文件的好理由,而不是手动使用ld - 您的发行版将配置为使用正确的路径适用于您的系统。 使用gcc -v查看您的系统上的这些路径,而不是互联网上其他人的路径。 - Peter Cordes

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