【问题标题】:What is in an executable besides the raw machine instructions?除了原始机器指令之外,可执行文件中还有什么?
【发布时间】:2020-12-19 11:22:25
【问题描述】:

我正在寻求了解低级计算。我注意到我编译的二进制文件比我认为的要大得多。所以我尝试在没有任何 stdlib 代码的情况下构建尽可能小的 c 程序,如下所示:

void _start()
{
    while(1) {};
}

gcc -nostdlib -o minimal minimal.c

当我解开二进制文件时,它向我展示了我所期望的,即三行汇编中的确切代码。

$ objdump -d minimal

minimal:     file format elf64-x86-64


Disassembly of section .text:

0000000000001000 <_start>:
    1000:   55                      push   %rbp
    1001:   48 89 e5                mov    %rsp,%rbp
    1004:   eb fe                   jmp    1004 <_start+0x4>

但我的实际可执行文件大小仍为 13856 字节。是什么,让这个这么大?那个文件里还有什么?操作系统是否需要超过这 6 个字节的机器码?

编辑#1: size 的输出为:

$ size -A minimal
minimal  :
section              size    addr
.interp                28     680
.note.gnu.build-id     36     708
.gnu.hash              28     744
.dynsym                24     776
.dynstr                 1     800
.text                   6    4096
.eh_frame_hdr          20    8192
.eh_frame              52    8216
.dynamic              208   16176
.comment               18       0
Total                 421

【问题讨论】:

  • 可执行文件包含的信息远不止代码本身。它可以在不同的操作系统和可执行文件类型之间变化。
  • 如果你把它作为一个精灵链接,那不是低级的。
  • 有一些关于可执行文件的元数据,由ELF 标准规定。编译器可能会添加其他元数据或部分,也可以尝试strip 文件。
  • 另见例如this tutorial 关于如何创建最小的可执行文件。
  • 如果你编译一个没有帧指针的 16 位 MSDOS .COM 程序,你最终只会得到代码。正如上面已经评论过的,对于大多数当前的操作系统,除了编译的代码之外,还有一些信息。

标签: c assembly gcc executable


【解决方案1】:

有许多不同的可执行文件格式。 .com、.exe、.elf、.coff、a.out 等。理想情况下,它们包含机器代码和其他部分(.text(代码)、.data、.bss、.rodata 和可能的其他部分,名称取决于工具链) 加上它们包含调试信息。请注意您的反汇编如何显示标签_start?这是一个字符串以及其他信息,以便能够将该字符串连接到地址以进行调试。 objdump 的输出也显示你使用的是一个elf文件,你可以很方便的查到文件格式,也可以随便写个程序来解析这个文件,或者试试用readelf等工具看看里面有什么(高级不是原始的)。

在通常(并非总是,但认为 pc)程序被加载到 ram 然后运行的操作系统上,因此您首先希望拥有操作系统支持的文件格式,有他们没有理由支持不止一个,但他们可能会。它取决于操作系统/系统设计,但操作系统可能被设计为不仅加载代码,还加载/初始化数据(.data、.bss)。启动时说一个 mcu,您需要将数据嵌入到二进制 blob 中,应用程序本身将数据从闪存复制到 ram,但在不一定需要的操作系统中,但为了做到这一点,您需要文件格式可以区分部分、目标位置和大小。这意味着文件中有额外的字节来定义这个和文件格式。

二进制文件包含引导代码,然后才能进入 C 生成的代码,具体取决于系统,取决于 C 库(计算机上可以使用多个/许多 C 库,并且引导通常特定于 C 库不是目标,也不是操作系统,不是编译器),所以文件的一部分是引导代码,当你的主程序非常小时,很多文件大小都是开销。

例如,您可以使用 strip 通过删除一些符号和其他非必要项目来使文件更小,例如文件大小应该变小,但 objdump 反汇编将没有标签,对于 x86 的情况,一个充其量难以反汇编的可变长度指令集变得更加困难,因此带或不带标签的输出可能无法反映实际指令,但没有标签,gnu反汇编程序不会在标签处自行重置并且可以输出更糟。

【讨论】:

  • 在此特定示例中,没有“引导代码”,因为正在使用-nostdlib。另一方面,这意味着 _start 不是使用标准 C 调用约定调用的,如果程序更复杂,这可能会导致麻烦。
【解决方案2】:

现代编译器和链接器并未真正针对在全平台上生成超小型代码进行优化。不是因为这项工作很困难,而是因为通常没有必要。编译器或链接器不一定会添加额外的代码(尽管可能),而是它不会努力将您的数据和代码打包到尽可能小的空间中。

在您的情况下,我注意到您正在使用动态链接,即使实际上没有任何链接。使用“-static”将减少大约 8kB。 "-s" (strip) 会去掉一点。

我不知道是否可以使用 gcc 来制作真正最小的 ELF 可执行文件。在你的情况下,这应该是大约 400 字节,几乎所有这些都是各种 ELF 标头、节表等。

我不知道我是否被允许链接我自己的网站(如果没有,我相信有人会纠正我),但我有一篇关于通过从头开始以二进制方式构建一个小型 ELF 可执行文件的文章:

http://kevinboone.me/elfdemo.html

【讨论】:

  • 哎呀——我忘了​​说:由 gcc/ld 编写的一些信息将有利于调试器等工具,实际上并不是正常执行所必需的。它仍然会增加文件的大小。
  • 对于 32 位 Windows 程序,Visual C / C++ 4.0 将生成比当前版本的 Visual Studio 更小的 .EXE 文件,但我没有调查原因。
  • @rcgldr EXE 文件的大小可以通过更改 DOS 标头轻松更改,因为该标头包含一个真实的 DOS 程序,如果在 DOS 中运行会显示一些消息
  • @phuclv - 我的最后一条评论是关于 Windows 32 位程序,而不是 DOS 程序。 Visual C / C++ 4.0 生成的 .EXE 文件比 Visual Studio 小。
  • @rcgldr 我说的是 32 位 exe 文件。它们总是包含一个 DOS 存根,以防止它们在 DOS 中执行,或者在某些情况下生成可以在任一环境中运行的“胖”二进制文件
【解决方案3】:

如果您使用 clang 10.0lld 10.0 并去掉不必要的部分,您可以获得 64 位静态链接可执行文件的大小到 800 字节以下。

$ cat minimal.c
void _start(void)
{
    int i = 0;

    while (i < 11) {
       i++;
    }

    asm( "int $0x80" :: "a"(1), "b"(i) );
}

$ clang -static -nostdlib -flto -fuse-ld=lld -o minimal minimal.c
$ ls -l minimal
-rwxrwxr-x 1 fpm fpm 1376 Sep  4 17:38 minimal

$ readelf --string-dump .comment minimal
String dump of section '.comment':
  [     0]  Linker: LLD 10.0.0
  [    13]  clang version 10.0.0 (Fedora 10.0.0-2.fc32)

$ readelf -W --section-headers minimal
There are 9 section headers, starting at offset 0x320:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .note.gnu.build-id NOTE            0000000000200190 000190 000018 00   A  0   0  4
  [ 2] .eh_frame_hdr     PROGBITS        00000000002001a8 0001a8 000014 00   A  0   0  4
  [ 3] .eh_frame         PROGBITS        00000000002001c0 0001c0 00003c 00   A  0   0  8
  [ 4] .text             PROGBITS        0000000000201200 000200 00002a 00  AX  0   0 16
  [ 5] .comment          PROGBITS        0000000000000000 00022a 000040 01  MS  0   0  1
  [ 6] .symtab           SYMTAB          0000000000000000 000270 000048 18      8   2  8
  [ 7] .shstrtab         STRTAB          0000000000000000 0002b8 000055 00      0   0  1
  [ 8] .strtab           STRTAB          0000000000000000 00030d 000012 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

$ strip -R .eh_frame_hdr -R .eh_frame minimal
$ strip -R .comment -R .note.gnu.build-id minimal
strip: minimal: warning: empty loadable segment detected at vaddr=0x200000, is this intentional?

$ readelf -W --section-headers minimal
There are 3 section headers, starting at offset 0x240:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        0000000000201200 000200 00002a 00  AX  0   0 16
  [ 2] .shstrtab         STRTAB          0000000000000000 00022a 000011 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

$ ll minimal
-rwxrwxr-x 1 fpm fpm 768 Sep  4 17:45 minimal

【讨论】:

    猜你喜欢
    • 2010-12-27
    • 2011-07-18
    • 1970-01-01
    • 1970-01-01
    • 2018-01-01
    • 1970-01-01
    • 2021-07-29
    • 1970-01-01
    相关资源
    最近更新 更多