ELF文件和bin文件有什么区别?

111

编译器生成的最终图像包含bin文件和扩展式加载程序格式ELF文件,这两者有什么区别,特别是ELF文件的功用。


这是NASM的说法。虽然不是针对ARM的,但可能是相同的概念。例如,如果您编译一个只包含NOP的文件而没有使用-f(或-fbin),它将编译为单个字节的0x90,而不是带有-felf32的400字节ELF容器。因此只有原始代码,没有容器元数据。NASM表示它主要用于MS-DOS .COM和.SYS文件。section指令大多被忽略,只生成对齐。 - Ciro Santilli OurBigBook.com
这是bin文件有用的一种方式:制作引导扇区以部署操作系统:https://dev59.com/HWEh5IYBdhLWcg3wwly8#32483545 - Ciro Santilli OurBigBook.com
6个回答

111

Bin文件是纯二进制文件,没有内存修复或重定位,很可能有明确的指令要求在特定的内存地址加载。而...

ELF文件是可执行可链接格式,它由符号查找表和可重定位表组成,也就是说,它可以被内核加载到任何内存地址,并自动地将所使用的所有符号调整到该内存地址的偏移量上。通常,ELF文件有许多节,如“数据”、“文本”、“bss”等等......就是在这些节中,运行时可以计算在运行时动态调整符号的内存引用的位置。


2
据我所了解,bin文件就像从偏移量0运行程序一样,并且数据段嵌入其中。如果这是错误的,请纠正我。 - Martin Kersten
1
@MartinKersten 正确,bin文件从偏移量0开始。 - t0mm13b
2
@t0mm13b 所以,.elf文件可以像普通的.hex文件一样烧录到微控制器上,但需要更多的闪存,并且每次微控制器重置时,部分地址会发生变化? - Aelgawad
@BlackyDucky,我不认为这是可能的。如果微控制器尝试直接执行ELF数据,它会将头文件和其他数据误解为指令,对吗? - jacobq
1
@gawad gdb将仅使用“load”命令将.elf的二进制部分烧录到微控制器上。它需要相同数量的闪存,但具有主机上gdb的调试信息。 elf元数据还指示在微控制器内存中加载每个部分的位置。 Elf不必是可重定位的,在微控制器上,每个部分将加载到固定位置。 - joeforker
显示剩余2条评论

48

Bin文件就是程序存储在ROM或者特定地址上的二进制数据,你可以直接加载这些数据,但是需要知道基地址,因为通常不包含在其中。

Elf文件包含了Bin信息,但是周围还有很多其他信息,如调试信息、符号等。它可以区分二进制中的代码和数据,并允许存在多个二进制数据块(将其转换成Bin文件时,会得到一个大的Bin文件,并使用填充数据来将其填充到下一个块)。它告诉你有多少二进制数据以及有多少要初始化为零的bss数据(gnu工具在创建正确的Bin文件方面存在问题)。

Elf文件格式是一种标准,ARM发布了其对标准的增强/变化。我建议每个人都编写一个Elf解析程序来了解其中的内容,不要使用库,只需使用规范中的信息和结构即可。这有助于解决GNU在创建.bin文件时出现问题以及调试链接脚本和其他问题,避免损坏您的Bin或Elf输出。


1
0x7C00听起来像是一个引导加载程序的东西,它不一定使用elf格式。这是一个通用的问题。操作系统会有虚拟地址空间的规则,工具链需要针对该操作系统的规则进行目标设置,然后文件格式将指示可加载的项目及其地址以及一旦加载后的入口点和其他内容。elf只是一个容器,就像一个盒子,你必须为目标用例正确打包它。 - old_timer
1
如果你想要将一些ASCII字符打印到VGA屏幕上,你需要编写一个程序来完成这个任务。这个程序可以包含一些数据或者动态生成数据的数学计算,或者两者的组合。然后,你需要将这个程序加载到操作系统定义的代码空间中,并运行它。通常情况下,你不会直接将数据传输到物理外设上,而且只有极少数的操作系统允许你这样做,或者允许其加载器这样做。 - old_timer
1
对于裸机编程,特别是如果这个 ELF 文件是引导加载程序和/或第一个运行的程序,则入口点和 _start 不相关,因为您使用 ELF 文件作为跳板到任何像 OpenOCD over JTAG 这样的工具,或通过任何-whatever-objcopy -O 二进制文件.elf file.bin,然后该文件以某种方式加载到闪存中。我没有在 x86 上尝试过引导加载程序,但假设 BIOS 无法解析 ELF 文件,因此它也需要是内存映像。因此,需要一个 -O 二进制类型的 bin 文件。 - old_timer
1
独立实体可以是硬件、逻辑或其他设计。对于操作系统来说,操作系统制定规则;对于微控制器来说,芯片/处理器设计制定规则。例如,如果有一个向量表,然后向量指向处理程序,您必须将所有这些内容都包含在链接器脚本中,以便可加载的数据被定位到设备启动时引导的闪存中。 - old_timer
1
为了扩大你的目标,无论是操作系统、处理器还是多级引导程序等,都需要遵循其规则来构建基于这些规则的“二进制文件”,其中引导程序和链接脚本最为重要。然后,每个目标如何应用该二进制文件以及支持哪些文件格式都非常广泛。假设在许多主机开发平台上使用GNU,则ELF文件格式是默认输出,然后根据需要使用工具(如果有针对目标特定的实用程序/加载程序)从ELF提取或转换到其他格式。 - old_timer
显示剩余5条评论

33

一些资源:

  1. ARM架构的ELF格式文档
    http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044d/IHI0044D_aaelf.pdf
  2. wiki上的ELF介绍
    http://en.wikipedia.org/wiki/Executable_and_Linkable_Format

ELF格式通常是编译输出的默认格式。如果使用GNU工具链,可以使用objcopy将其转换为二进制格式,例如:

  arm-elf-objcopy -O binary [elf-input-file] [binary-output-file]

或者使用fromELF工具(大多数IDE都内置,例如ADS):

 fromelf -bin -o [binary-output-file] [elf-input-file]

6
在 bin 文件细节得到回答后,这个内容被添加了进来,确实增加了一种实用的技巧。对此要点赞。 - erbdex
有没有人知道,使用arm-none-eabi-objcopy命令和参数--strip-all是否与使用参数-O binary的效果相同?我认为不是。第一个选项仍然会生成一个剥离了符号的ELF文件,而第二个选项会生成一个已经映射到适当位置的二进制文件。 - undefined

4

"bin"是CPU执行前内存中的最终形式。

"ELF"是对其进行切割/压缩后的版本,因此CPU/MCU不能直接运行。

动态链接器首先必须充分地反转它(并将偏移量修改回到正确的位置)。但是MCU上没有链接器/操作系统,因此您必须将"bin"烧录进去。

此外,Ahmed Gamal 是正确的。编译和链接是两个单独的阶段;整个过程称为"构建",因此GNU编译器集合有单独的可执行文件:

一个用于编译器(技术上输出汇编代码),另一个用于汇编程序(输出ELF格式的目标代码),然后是用于链接器(将多个目标文件组合成单个ELF文件)的工具, 最后,在运行时,有动态链接器,它有效地将elf转换为bin,但纯粹在内存中,供CPU运行。

请注意,通常将整个过程称为"编译"(如GCC本身的名称),但在讨论具体细节时会导致混淆,就像在这种情况下一样,Ahmed正在澄清。 这是人类语言本身不精确的常见问题。

为避免混淆,GCC使用ELF格式输出目标代码(在内部使用汇编器),链接器只需获取其中几个(具有".o"扩展名), 并产生一个单一的组合结果,可能会将它们压缩(成"a.out")。但是所有这些文件,甚至".so"都是ELF。就像几个Word文档,每个以".chapter"结尾, 都被组合成最终的".book",所有这些文件都技术上使用相同的标准/格式,因此可以将".docx"作为扩展名。

"bin"有点像将书转换为".txt"文件,同时添加尽可能多的空格以等效于最终书籍的大小(打印在单个卷轴上),并有所有图片叠加的位置。


你拼错了“mammary”。 - greybeard
1
不会说“切割”,因为ELF比纯二进制文件要大得多。 - Angelo Dureghello

0
纯二进制是一系列可执行操作码的序列,从其0偏移处执行,但不仅限于此。
纯二进制通常用于微控制器、裸机编程和初始引导,在这种情况下,除了ROM加载器之外没有其他"加载器"。
纯二进制包括.bss和.data,从闪存存储器中读取的二进制文件(如微控制器)必须被重新定位到RAM中的某个位置。二进制文件可以包括一个向量表、用于"rel"或"rela"重定位的符号表(参见-fPIC)、GOT表等。所有这些附加数据都是从ELF->二进制转换中获得的,因为这些特定表的地址是从ELF中获取的。
二进制生成的另一个重要点是"链接脚本",它们生成节偏移量,并根据声明的节使用不同的绝对跳转地址。
最后注意:纯二进制代码(.text)通常也会被重新定位到RAM中,因为CPU内部的SRAM通常不太大。为此,这些重新定位表派上了用场。

-2
我只想在这里纠正一个观点。ELF文件是由链接器而非编译器产生的。
编译器的任务在将源代码文件编译成目标文件(*.o)后结束,链接器将所有.o文件链接在一起并生成ELF文件。

2
因为它既没有回答问题,也不一定正确,所以被踩了。广义地定义,编译包括链接。引用自ld文档通常编译程序的最后一步是运行ld。 - bzeaman
2
因为提供了错误的信息而被踩。.o也是ELF文件,类型为_relocatable_。还有另外两种类型:_executable_和_shared object_。所以,汇编器和链接器都会生成ELF文件。 - SenhorLucas
我还想补充一点,较新的GCC编译器也能够进行链接! - undefined

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