【问题标题】:Editing an ELF executable to reduce it's size编辑 ELF 可执行文件以减小其大小
【发布时间】:2016-02-14 06:33:18
【问题描述】:

我正在尝试将 C 程序的大小降低到 main.c 看起来像:

#include<unistd.h>
#include<sys/syscall.h>

void _start() {
    const char msg [] = "Hello World!";
    syscall(SYS_write, 0, msg, sizeof(msg)-1);
    syscall(SYS_exit, 0);
}

我正在编译它

gcc -nostdlib -s -O3 -o main main.c /usr/lib/path/to/libc.a

然后我strip它。但是如果我在剥离它之前对其进行了 objdump,我会看到

main:文件格式elf64-x86-64

SYMBOL TABLE:
0000000000400158 l    d  .note.gnu.build-id     0000000000000000 .note.gnu.build-id
0000000000400180 l    d  .text  0000000000000000 .text
0000000000400214 l    d  .eh_frame_hdr  0000000000000000 .eh_frame_hdr
0000000000400238 l    d  .eh_frame      0000000000000000 .eh_frame
0000000000601000 l    d  .tbss  0000000000000000 .tbss
0000000000000000 l    d  .comment       0000000000000000 .comment
0000000000000000 l    df *ABS*  0000000000000000 main.c
0000000000000000 l    df *ABS*  0000000000000000
00000000004001d0 g     F .text  0000000000000026 syscall
0000000000000000 g       .tbss  0000000000000004 errno
0000000000400203 g       .text  0000000000000000 __syscall_error_1
0000000000400180 g     F .text  0000000000000048 _start
0000000000000000 g       .tbss  0000000000000004 __libc_errno
0000000000400200 g     F .text  0000000000000013 __syscall_error
0000000000601000 g       .eh_frame      0000000000000000 __bss_start
0000000000601000 g       .eh_frame      0000000000000000 _edata
0000000000000000         *UND*  0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000601000 g       .eh_frame      0000000000000000 _end

似乎我可以删除一些东西来手动减小可执行文件的大小? 注意:我知道这不是我实际上会做的事情,但我只是想删除任何现有的样板。

我会从可执行文件main 中删除什么来减小它的大小?我该怎么做?

旁注:我已经阅读了thisthis 的文章。无需链接它们。我故意选择留在C家

【问题讨论】:

  • 我不明白:你是想减小可执行代码的大小还是可执行文件的大小? $ ls -l 测试或 $ 大小测试;检查一个。
  • 我猜,你对-Os不满意……
  • 然后有编译成32位代码的选项。
  • @OldestSoftwareGuy 我正在尝试尽可能减小可执行文件的整体大小。我知道实际编辑和取出可执行文件的各个部分并不好,但我想知道无论我如何从命令行中做到这一点
  • 你可以通过-fno-asynchronous-unwind-tables -Qn获得几个字节

标签: linux size executable elf


【解决方案1】:

简单的东西

您可以使用以下方法删除很多无用的位:

  • -fno-asynchronous-unwind-tables -Qn;
  • 使用自定义链接器脚本-rlinker_script

我得到一个 992 字节的工作二进制文件(剥离后)。

链接器脚本

让我们看看部分(剥离之前):

[Nr] Name              Type             Address           Offset
     Size              EntSize          Flags  Link  Info  Align
[ 0]                   NULL             0000000000000000  00000000
     0000000000000000  0000000000000000           0     0     0
[ 1] .note.gnu.build-i NOTE             0000000000400120  00000120
     0000000000000024  0000000000000000   A       0     0     4
[ 2] .text             PROGBITS         0000000000400150  00000150
     0000000000000090  0000000000000000  AX       0     0     16
[ 3] .eh_frame         PROGBITS         00000000004001e0  000001e0
     0000000000000048  0000000000000000   A       0     0     8
[ 4] .tbss             NOBITS           0000000000601000  00000228
     0000000000000004  0000000000000000 WAT       0     0     4
[ 5] .shstrtab         STRTAB           0000000000000000  000003e7
     0000000000000044  0000000000000000           0     0     1
[ 6] .symtab           SYMTAB           0000000000000000  00000228
     0000000000000168  0000000000000018           7     6     8
[ 7] .strtab           STRTAB           0000000000000000  00000390
     0000000000000057  0000000000000000

从程序头 5 开始,所有内容都被剥离了,但我们给出了两个相对无用的部分,它们没有被剥离:.note.gnu.build-id.eh_frame.eh_frame 在编译器中被禁用,但一些 .eh_frame 来自静态库。

我们可以使用自定义链接描述文件 (gcc -T linker_script) 完全摆脱 .eh_frame.note.gnu.build-id 部分。

首先,我们得到默认的链接器脚本:

gcc test.c -Wl,--verbose

我们删除这些行:

.eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) }
.eh_frame       : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) }
.note.gnu.build-id : { *(.note.gnu.build-id) }

并修改这一行:

/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*)  *(.note.gnu.build-id) *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) *(.eh_frame) *(.eh_frame.*)  }

我使用这个得到 664 个字节。

其他选项

其他缩小尺寸的解决方案:

  • 优化大小 (-Os);

  • 32 位编译 (-m32)。

通过所有这些,我得到了一个没有自定义链接描述文件的 760 字节的二进制文件和修改后的链接描述文件的 488 个字节。

摆脱errno

剩下的“无用”东西很少(例如errno 处理和TLS)可以删除。

[Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
[ 0]                   NULL            00000000 000000 000000 00      0   0  0
[ 1] .text             PROGBITS        080480a0 0000a0 00008e 00  AX  0   0 16
[ 2] .tbss             NOBITS          08049130 000130 000004 00 WAT  0   0  4
[ 3] .shstrtab         STRTAB          00000000 000257 000027 00      0   0  1
[ 4] .symtab           SYMTAB          00000000 000130 0000d0 10      5   4  4
[ 5] .strtab           STRTAB          00000000 000200 000057 00      0   0  1

(从第 3 节开始的所有内容都被剥离。)

通过编写我们自己的系统调用代码,我们可以摆脱errno 处理。我们将删除:

  • 4 个字节的.symtab

  • errno相关说明。

但这样做涉及使用(内联)汇编。

【讨论】:

  • -rlinker_script 是链接器标志还是编译器标志?我已经尝试过-rbarebones.lds-Wl,-rbarebones.lds,但都解决了一个错误。我尝试了链接器标志-Wl,-Tbarebones.lds,它最终告诉我有_start的重新定义
  • 我知道您已经编辑掉了之前的代码,但是您知道我可以参考哪些参考资料来了解其中的内容吗?我想了解一下您编辑的内容
  • 我修复了损坏的链接描述文件。
  • 超级有用的答案!非常感谢,我的二进制文件现在非常小,而且我还知道如何查看链接器脚本并开始了解它们。
猜你喜欢
  • 2010-10-01
  • 2010-09-17
  • 2022-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多