【问题标题】:Why is my hello world binary mostly zeroes?为什么我的 hello world 二进制文件大多为零?
【发布时间】:2021-06-26 03:49:36
【问题描述】:

我已经编译了

#include <stdio.h>

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

在 Mac 上,大小为 48k。但是,当我使用xxd 查看二进制文件时,大部分看起来是这样的:

...
0000b990: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000b9a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000b9b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
...

为什么会这样?

otool 告诉我:

 otool -L hello
hello:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1292.0.0)

太好了,它再次动态链接 libSystem,它在 printf 所在的位置。

那为什么都是零呢?

【问题讨论】:

标签: c macos assembly executable mach-o


【解决方案1】:

因为对齐。

XNU 强制将映射部分二进制文件的每个段与平台的页面大小对齐。在 x86_64 上是 0x1000 字节,在 arm64 上是 0x4000 字节(即使硬件支持 0x1000)。如果某些段的数据必须对齐到某个偏移量,那么文件中必须有 something 来填补两者之间的空白——通常是零。

现在,如果你的二进制文件是 48KB,那么它的段可能看起来像这样:

LC 00: LC_SEGMENT_64  Mem: 0x000000000-0x100000000  File: Not Mapped    ---/--- __PAGEZERO
LC 01: LC_SEGMENT_64  Mem: 0x100000000-0x100004000  File: 0x0-0x4000    r-x/r-x __TEXT
LC 02: LC_SEGMENT_64  Mem: 0x100004000-0x100008000  File: 0x4000-0x8000 rw-/rw- __DATA_CONST
LC 03: LC_SEGMENT_64  Mem: 0x100008000-0x10000c000  File: 0x8000-0xc000 rw-/rw- __DATA
LC 04: LC_SEGMENT_64  Mem: 0x10000c000-0x100010000  File: 0xc000-0xc110 r--/r-- __LINKEDIT

对于 0x4000 的对齐方式,这已经是最小布局了。但是,如果您使用的是 Intel,则可以通过将 -Wl,-segalign,0x1000 传递给编译器来强制链接器使用 0x1000。这应该会产生一个只有大约 12KB 的二进制文件:

LC 00: LC_SEGMENT_64  Mem: 0x000000000-0x100000000  File: Not Mapped    ---/--- __PAGEZERO
LC 01: LC_SEGMENT_64  Mem: 0x100000000-0x100001000  File: 0x0-0x1000    r-x/r-x __TEXT
LC 02: LC_SEGMENT_64  Mem: 0x100001000-0x100002000  File: 0x1000-0x2000 rw-/rw- __DATA_CONST
LC 03: LC_SEGMENT_64  Mem: 0x100002000-0x100003000  File: 0x2000-0x3000 rw-/rw- __DATA
LC 04: LC_SEGMENT_64  Mem: 0x100003000-0x100004000  File: 0x3000-0x3110 r--/r-- __LINKEDIT

如果你想进一步优化你的二进制文件,你需要去掉段。通过导入和链接,您唯一可以真正摆脱的是__DATA_CONST,您可以通过使用-mmacosx-version-min=10.14 定位macOS Mojave(或更早版本)来做到这一点。这将使您只剩下 8KB 多一点:

LC 00: LC_SEGMENT_64  Mem: 0x000000000-0x100000000  File: Not Mapped    ---/--- __PAGEZERO
LC 01: LC_SEGMENT_64  Mem: 0x100000000-0x100001000  File: 0x0-0x1000    r-x/r-x __TEXT
LC 02: LC_SEGMENT_64  Mem: 0x100001000-0x100002000  File: 0x1000-0x2000 rw-/rw- __DATA
LC 03: LC_SEGMENT_64  Mem: 0x100002000-0x100003000  File: 0x2000-0x20f0 r--/r-- __LINKEDIT

如果您正在争取尽可能小的可执行文件,您可以进一步放弃 __DATA 甚至可能是 __LINKEDIT,但您必须大幅更改您的代码以仅发出原始系统调用,而不使用动态链接器等.

对于任何实际应用程序,我还要说这些零实际上并不重要。给定四个映射段,它们使用的空间永远不会超过 48KB。二进制越大,零组成的百分比越小。

至于分发,答案很明显:xz
用它压缩上述二进制文件:

  • 48KB 二进制文件为 776 字节。
  • 12KB 二进制文件为 736 字节。
  • 8KB 二进制文件为 684 字节。

【讨论】:

  • 旁注:zstd 在这方面的表现应该和xz 一样好,并且压缩/解压缩速度更快(对于大尺寸来说更快;IDK 关于启动开销)。例如,Arch GNU / Linux 已将其二进制包切换到 zstd 而不是 xz。
  • 您还可以在其虚拟内存中设置物理零大小(在可执行文件中)__LINKEDITbind_at_load(没有dylb_stub_binder)。不过,这不是链接器会发出的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-07
相关资源
最近更新 更多