【问题标题】:When we compile a source code that contains a 'main' without linking, why can't we run it?当我们编译包含'main'而没有链接的源代码时,为什么我们不能运行它?
【发布时间】:2017-07-21 07:51:58
【问题描述】:

我正在学习编译过程,我知道链接主要用于将包含“主”函数的二进制文件与其他包含在我们的主函数中使用的其他辅助函数的二进制文件链接。

但是,当我尝试使用代码运行目标文件时:

int main() {
    return 0;
}

在 Ubuntu 上使用 gcc 中的 -c 命令编译,我尝试运行它,但出现错误:

“bash: ./source.o: 无法执行二进制文件:执行格式错误”

【问题讨论】:

  • 对象文件不能直接执行。
  • 你需要标准函数的链接,比如printf,等等。并且即使你不使用它们,你也需要一个入口点,启动代码来初始化堆、栈...
  • 好吧,操作系统告诉你它没有将文件格式识别为可执行文件。这就是为什么!
  • @Jean-FrançoisFabre 这当然不是问题所在。您可以编写一个不与外部通信的程序。它没有实际用途的事实与我提出的问题完全无关。
  • @yoyo_fun: man execl 会给你一个很好的概览,了解在调用main 之前发生的事情(以及在main 返回之后发生的事情)。 execl 至关重要,因为根据 Posix,一旦 shell 确定要运行哪个可执行映像,“shell 将在单独的实用程序环境中执行实用程序……其操作相当于调用 execl() 函数…… . 将路径参数设置为搜索结果的路径名,arg0 设置为命令名称,其余 execl() 参数设置为命令参数(如果有)和空终止符。"

标签: c++ c ubuntu gcc compilation


【解决方案1】:

阅读莱文的Linkers & Loaders

了解ELF

尝试使用gcc -v 进行编译(你会看到实际使用的程序是什么:cc1compile 将 C 代码编译到某个汇编程序中,asassemble 编译到某个目标文件中,ld & collect2link)。还可以使用gcc -S -fverbose-asm -O 查看生成的汇编程序文件。请注意,gcc 知道(并专门编译main 函数。并且您的可执行文件的起点是由一些crt0 等提供的(它不是 main 而是一些在汇编程序中编码的_start 例程,它调用你的main....) .

Object filesexecutables 不同。可执行文件包含crt0C standard library 之类的东西,或者以某种方式将dynamically link 作为shared object (你需要链接你的source.o - 从你的空main 编译到source.c-因此进入可执行文件)。

在 Linux 上,使用 objdump(1)readelf(1)(在一些现有的二进制文件以及您的 source.o 目标文件上)

另请参阅elf(5)execve(2)ld-linux(8)Linux assembly howtosyscalls(2)Advanced Linux ProgrammingOperating Systems: Three Easy Pieces,以及(了解libc.so)Drepper 的How To Write Shared Libraries,@9885 @...

(你需要阅读整本书才能理解细节;我提供了一些参考)

还可以查看 Common Lisp 和 SBCL。它的编译器有一个非常不同的模型(与 C 真的不同)。

【讨论】:

  • 感谢您的回答....这里的许多人似乎对初学者程序员非常粗鲁,即使在我被否决之前没有提出我的问题。
【解决方案2】:

你没有引导程序。你是在这个鸡和蛋的问题。

代码(用于该函数)在那里,但有一些假设,首先你需要一个堆栈。例如,根据架构,您的返回地址可能在该堆栈上。返回值可能在该堆栈上。 C 语言本身并没有直接在语言中提供这一点,为了“引导”你的函数,总是至少需要一点汇编或其他一些语言。例如在 ARM 中用于 gnu:

bs.s

.globl _start
_start:
    mov sp,#0x8000
    bl main
    b .

so.c

int main ( void )
{
    return(0);
}

对于 ARM,该功能已完成,链接器无需修改指令。但没有定义地址空间,要么指定,要么反汇编程序假定零作为该对象的地址,但它是一个对象而不是可加载的二进制文件。

00000000 <main>:
   0:   e3a00000    mov r0, #0
   4:   e12fff1e    bx  lr

现在,如果我们添加引导程序并链接到某个地址,我们将得到一个真实的可执行程序

00008000 <_start>:
    8000:   e3a0d902    mov sp, #32768  ; 0x8000
    8004:   eb000000    bl  800c <main>
    8008:   eafffffe    b   8008 <_start+0x8>

0000800c <main>:
    800c:   e3a00000    mov r0, #0
    8010:   e12fff1e    bx  lr

这并不意味着不能使用编译器对象输出来制作操作系统或可以以这种方式加载函数的环境。但这就是词链、工具链的由来。编译器制作汇编语言,汇编器汇编汇编语言,结合其他必要的对象(引导程序、编译器库和 C 库等),链接器定义所有内容的地址空间,并根据需要修改代码/数据以解析外部。获得最终结果的一系列事件或事件链。

【讨论】:

  • 对于这个特定的例子,不需要初始化堆栈指针,通常是最低限度的,堆栈指针和分支到 C 语言入口点。带有 .data 和 .bss 其他内容的 REAL C 程序需要在引导程序中进行。
  • 以及适当的退出系统调用,这取决于操作系统如何发生。
【解决方案3】:

即使是像exit 这样最基本的命令也不是直接在语言中,需要链接。

http://en.cppreference.com/w/c/program/exit

【讨论】:

  • OP 不使用任何库中的任何函数。在上面引用的示例代码中,没有需要链接的函数。
  • 如果一个程序是独立的,那么你根本不需要链接任何外部函数。
  • 但是你确实需要链接最终调用main()的系统入口点。
  • @ScottMermelstein - 没有办法回答两个 cmets 但是,你们都错了 - 需要外部功能,例如环境的设置/拆除。查看装配程序中必要的机制以使其独立并与来自gcc -s source.c.s 输出进行比较
  • @bace1000 - 查看我对 Scott 的评论
猜你喜欢
  • 1970-01-01
  • 2020-02-13
  • 2021-06-27
  • 1970-01-01
  • 2016-03-30
  • 2017-12-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多