编辑 ELF 可执行文件以减小其大小
Editing an ELF executable to reduce it's size
我正在尝试将 C 程序压缩到小于 1kb 的大小。我越来越接近了,但我一直在编辑我的 ELF 可执行文件。我的程序 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,我会看到
主要:文件格式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
中删除什么以减小其大小?我该怎么做?
简单的东西
您可以使用以下方法删除相当多的无用位:
-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
来自静态 libc。
我们可以使用自定义链接器脚本 (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
剩下的 "useless" 东西很少(例如 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
相关说明
但是这样做涉及使用(内联)汇编。
我正在尝试将 C 程序压缩到小于 1kb 的大小。我越来越接近了,但我一直在编辑我的 ELF 可执行文件。我的程序 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,我会看到
主要:文件格式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
中删除什么以减小其大小?我该怎么做?
简单的东西
您可以使用以下方法删除相当多的无用位:
-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
来自静态 libc。
我们可以使用自定义链接器脚本 (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
剩下的 "useless" 东西很少(例如 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
相关说明
但是这样做涉及使用(内联)汇编。