专业的编程技术博客社区

网站首页 > 博客文章 正文

[Linux C/C++]如何对ELF目标文件(Object File)进行瘦身(strip)?

baijin 2024-10-20 04:09:24 博客文章 9 ℃ 0 评论

在项目的开发过程中,为了方便调试,我们一般要在项目的编译时通过添加-g/-ggdb等选项来添加调试信息,从而方便开发过程中的调试。而在程序发布的时候,我们需要删除其中的符号信息。

我们在文章<<[Linux C/C++]如何正确分离可执行程序及其调试符号?>>已经介绍了为什么要删除可执行程序中符号信息,这里不再介绍。

那么,我们应该如何删除ELF目标文件中的符号信息呢?

下面我们以一个简单的例子来说明。

如下的工程中有4个源文件组成,其中main.c为main函数所在为源文件。

[root:~/work/v1/gdb/no-symbols/test1]# tree

.

├── main.c

├── mod1.c

├── mod2.c

├── mod3.c


0 directories, 4 files

[root:~/work/v1/gdb/no-symbols/test1]#

1) 源码

//file name: main.c
#include <stdio.h>

int mod1_func();
int main()
{
printf("in %s...\n", __func__);
mod1_func();

return 0;
}
//file name: mod1.c
#include <stdio.h>

int mod2_func();
int mod1_func()
{
printf("enter %s...\n", __func__);
mod2_func();

return 0;
}
//file name: mod2.c
#include <stdio.h>

int mod3_func();
int mod2_func()
{
printf("enter %s...\n", __func__);
mod3_func();

return 0;
}
//file name: mod3.c
#include <unistd.h>
#include <stdio.h>

int mod3_func()
{
unsigned counter = 0;

printf("enter %s...\n", __func__);
while(1) {
printf("counter:%d\n", counter++);
sleep(5);
}

return 0;
}

2) 编译

[root:~/work/v1/gdb/no-symbols/test1]# gcc -g -o prog main.c mod1.c mod2.c mod3.c

[root:~/work/v1/gdb/no-symbols/test1]#

3) 检查生成的可执行文件 - prog

[root:~/work/v1/gdb/no-symbols/test1]# ls
main.c mod1.c mod2.c mod3.c prog
[root:~/work/v1/gdb/no-symbols/test1]#
[root:~/work/v1/gdb/no-symbols/test1]# file prog
prog: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0c686fa8f234e7fe6e7d8323dce7a067fd8187fe, for GNU/Linux 3.2.0, with debug_info, not stripped
[root:~/work/v1/gdb/no-symbols/test1]#

由于我们在编译的时候使用了-g选项,所以通过file命令查看prog时,可以看到其中的” with debug_info”。

删除prog中的符号信息

一般来说,我们都是通过strip或objcopy命令来删除可执行程序中的符号信息的。

对于strip命令来说,我们可以通过man strip来查看strip命令的帮助信息,其中常用的删除符号信息的命令选项包括:

? strip -s 或 strip --strip-all

不带任何选项的strip相当于strip -s,即strip --strip-all:

man strip

……

-s

--strip-all

Remove all symbols.

该选项用于删除二进制目标文件中的符号表(.symtab)和调试节信息。--strip-all选项可以极大地减小文件大小,但也会使目标文件丧失调试的能力。

对于可重定位目标文件或共享目标文件(静态链接库和动态链接库),最好不要使用strip --strip-all进行瘦身,而应该考虑使用--strip-debug或--strip-unneeded 2个strip选项进行瘦身。这是因为在可重定位目标文件或共享目标文件可能会参与链接过程,需要使用符号表(.symtab)中的信息来解决符号引用的问题,如果删除了它们的符号表,就会造成链接失败。而对于可执行文件,符号表被strip后并不会影响可执行文件的执行,因为对于静态链接的符号,可执行文件是完全链接的,这些符号已经完成了重定位。

  • strip -g或strip --strip-debug

-g

-S

-d

--strip-debug

Remove debugging symbols only.

该选项用于删除二进制目标文件中的调试信息。在编译程序时,编译器通常会将一些调试信息嵌入到可执行文件中,以便在调试时使用。这些调试信息包括源代码行号、函数名、变量名等。--strip-debug选项可以移除这些调试信息,从而减小文件大小。需要注意的是,移除调试信息会导致文件变得难以调试,因此只有在不需要调试时才建议使用--strip-debug选项。

  • strip --strip--unneeded

--strip-unneeded

Remove all symbols that are not needed for relocation processing.

该选项用于删除二进制目标文件中未使用的符号,而保留其他可能被使用的符号。当编译程序时,编译器会生成一些不被实际使用的符号。--strip-unneeded选项可以移除这些未被使用的符号,从而减小文件大小。

我们可以通过strip --strip-all命令来删除prog文件中符号表和调试信息。在strip prog之前我们先备份prog为prog.debug。

[root:~/work/v1/gdb/no-symbols/test1]# cp prog prog.debug

[root:~/work/v1/gdb/no-symbols/test1]#

删除prog中的符号表及所有和调试相关的节:

[root:~/work/v1/gdb/no-symbols/test1]# strip --strip-all prog

[root:~/work/v1/gdb/no-symbols/test1]# file prog

prog: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0c686fa8f234e7fe6e7d8323dce7a067fd8187fe, for GNU/Linux 3.2.0, stripped

[root:~/work/v1/gdb/no-symbols/test1]#

1) 检查strip后的prog文件

[root:~/work/v1/gdb/no-symbols/test1]# strip --strip-all prog

[root:~/work/v1/gdb/no-symbols/test1]# readelf -WS prog

There are 29 section headers, starting at offset 0x3148:


Section Headers:

[Nr] Name Type Address Off Size ES Flg Lk Inf Al

[ 0] NULL 0000000000000000 000000 000000 00 0 0 0

[ 1] .interp PROGBITS 0000000000000318 000318 00001c 00 A 0 0 1

[ 2] .note.gnu.property NOTE 0000000000000338 000338 000020 00 A 0 0 8

[ 3] .note.gnu.build-id NOTE 0000000000000358 000358 000024 00 A 0 0 4

[ 4] .note.ABI-tag NOTE 000000000000037c 00037c 000020 00 A 0 0 4

[ 5] .gnu.hash GNU_HASH 00000000000003a0 0003a0 000024 00 A 6 0 8

[ 6] .dynsym DYNSYM 00000000000003c8 0003c8 0000c0 18 A 7 1 8

[ 7] .dynstr STRTAB 0000000000000488 000488 00008a 00 A 0 0 1

[ 8] .gnu.version VERSYM 0000000000000512 000512 000010 02 A 6 0 2

[ 9] .gnu.version_r VERNEED 0000000000000528 000528 000020 00 A 7 1 8

[10] .rela.dyn RELA 0000000000000548 000548 0000c0 18 A 6 0 8

[11] .rela.plt RELA 0000000000000608 000608 000030 18 AI 6 24 8

[12] .init PROGBITS 0000000000001000 001000 00001b 00 AX 0 0 4

[13] .plt PROGBITS 0000000000001020 001020 000030 10 AX 0 0 16

[14] .plt.got PROGBITS 0000000000001050 001050 000010 10 AX 0 0 16

[15] .plt.sec PROGBITS 0000000000001060 001060 000020 10 AX 0 0 16

[16] .text PROGBITS 0000000000001080 001080 000245 00 AX 0 0 16

[17] .fini PROGBITS 00000000000012c8 0012c8 00000d 00 AX 0 0 4

[18] .rodata PROGBITS 0000000000002000 002000 000082 00 A 0 0 8

[19] .eh_frame_hdr PROGBITS 0000000000002084 002084 00005c 00 A 0 0 4

[20] .eh_frame PROGBITS 00000000000020e0 0020e0 000168 00 A 0 0 8

[21] .init_array INIT_ARRAY 0000000000003db0 002db0 000008 08 WA 0 0 8

[22] .fini_array FINI_ARRAY 0000000000003db8 002db8 000008 08 WA 0 0 8

[23] .dynamic DYNAMIC 0000000000003dc0 002dc0 0001f0 10 WA 7 0 8

[24] .got PROGBITS 0000000000003fb0 002fb0 000050 08 WA 0 0 8

[25] .data PROGBITS 0000000000004000 003000 000010 00 WA 0 0 8

[26] .bss NOBITS 0000000000004010 003010 000008 00 WA 0 0 1

[27] .comment PROGBITS 0000000000000000 003010 00002b 01 MS 0 0 1

[28] .shstrtab STRTAB 0000000000000000 00303b 00010a 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)

[root:~/work/v1/gdb/no-symbols/test1]#

可以看到被strip后的prog文件中的符号表(.symtab)节及所有的调试节已经被删除掉了。

prog.debug是带有调试信息的可执行文件,而prog是移除调试信息的可执行文件。这2个文件都能正常运行,但prog比prog.debug小了很多,而且prog中完全没有了符号信息。

2) 运行prog和prog.debug

[root:~/work/v1/gdb/no-symbols/test1]# ./prog

in main...

enter mod1_func...

enter mod2_func...

enter mod3_func...

counter:0

^C

[root:~/work/v1/gdb/no-symbols/test1]# ./prog.debug

in main...

enter mod1_func...

enter mod2_func...

enter mod3_func...

counter:0

^C

[root:~/work/v1/gdb/no-symbols/test1]#

可以发现prog和prog.debug都运行正常。

3) 对比prog和prog.debug的反汇编

由于反汇编的输出较多,这里我们只对比prog和prog.debug的.text节中的部分反汇编进行示意。

? 查看prog的反汇编:

[root:~/work/v1/gdb/no-symbols/test1]# objdump -d -j .text prog


prog: file format elf64-x86-64


Disassembly of section .text:


0000000000001080 <.text>:

1080: f3 0f 1e fa endbr64

1084: 31 ed xor %ebp,%ebp

1086: 49 89 d1 mov %rdx,%r9

1089: 5e pop %rsi

108a: 48 89 e2 mov %rsp,%rdx

108d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp

1091: 50 push %rax

1092: 54 push %rsp

1093: 4c 8d 05 26 02 00 00 lea 0x226(%rip),%r8 # 12c0 <sleep@plt+0x250>

109a: 48 8d 0d af 01 00 00 lea 0x1af(%rip),%rcx # 1250 <sleep@plt+0x1e0>

10a1: 48 8d 3d 45 01 00 00 lea 0x145(%rip),%rdi # 11ed <sleep@plt+0x17d>

10a8: ff 15 32 2f 00 00 callq *0x2f32(%rip) # 3fe0 <sleep@plt+0x2f70>

10ae: f4 hlt

10af: 90 nop

10b0: 48 8d 3d 59 2f 00 00 lea 0x2f59(%rip),%rdi # 4010 <sleep@plt+0x2fa0>

10b7: 48 8d 05 52 2f 00 00 lea 0x2f52(%rip),%rax # 4010 <sleep@plt+0x2fa0>

10be: 48 39 f8 cmp %rdi,%rax

10c1: 74 15 je 10d8 <sleep@plt+0x68>

10c3: 48 8b 05 0e 2f 00 00 mov 0x2f0e(%rip),%rax # 3fd8 <sleep@plt+0x2f68>

10ca: 48 85 c0 test %rax,%rax

10cd: 74 09 je 10d8 <sleep@plt+0x68>

10cf: ff e0 jmpq *%rax

10d1: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)

10d8: c3 retq

10d9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)

10e0: 48 8d 3d 29 2f 00 00 lea 0x2f29(%rip),%rdi # 4010 <sleep@plt+0x2fa0>

10e7: 48 8d 35 22 2f 00 00 lea 0x2f22(%rip),%rsi # 4010 <sleep@plt+0x2fa0>

10ee: 48 29 fe sub %rdi,%rsi

......

12b0: 41 5e pop %r14

12b2: 41 5f pop %r15

12b4: c3 retq

12b5: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)

12bc: 00 00 00 00

12c0: f3 0f 1e fa endbr64

12c4: c3 retq

[root:~/work/v1/gdb/no-symbols/test1]#

? 查看prog.debug的反汇编:

[root:~/work/v1/gdb/no-symbols/test1]# objdump -d -j .text prog.debug


prog.debug: file format elf64-x86-64


Disassembly of section .text:


0000000000001080 <_start>:

1080: f3 0f 1e fa endbr64

1084: 31 ed xor %ebp,%ebp

1086: 49 89 d1 mov %rdx,%r9

1089: 5e pop %rsi

108a: 48 89 e2 mov %rsp,%rdx

108d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp

1091: 50 push %rax

1092: 54 push %rsp

1093: 4c 8d 05 26 02 00 00 lea 0x226(%rip),%r8 # 12c0 <__libc_csu_fini>

109a: 48 8d 0d af 01 00 00 lea 0x1af(%rip),%rcx # 1250 <__libc_csu_init>

10a1: 48 8d 3d 45 01 00 00 lea 0x145(%rip),%rdi # 11ed <main>

10a8: ff 15 32 2f 00 00 callq *0x2f32(%rip) # 3fe0 <__libc_start_main@GLIBC_2.2.5>

10ae: f4 hlt

10af: 90 nop


......


0000000000001169 <mod3_func>:

1169: f3 0f 1e fa endbr64

116d: 55 push %rbp

116e: 48 89 e5 mov %rsp,%rbp

1171: 48 83 ec 10 sub $0x10,%rsp

1175: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)

117c: 48 8d 35 a5 0e 00 00 lea 0xea5(%rip),%rsi # 2028 <__func__.2925>

1183: 48 8d 3d 7e 0e 00 00 lea 0xe7e(%rip),%rdi # 2008 <_IO_stdin_used+0x8>

118a: b8 00 00 00 00 mov $0x0,%eax

118f: e8 cc fe ff ff callq 1060 <printf@plt>

1194: 8b 45 fc mov -0x4(%rbp),%eax

1197: 8d 50 01 lea 0x1(%rax),%edx

119a: 89 55 fc mov %edx,-0x4(%rbp)

119d: 89 c6 mov %eax,%esi

119f: 48 8d 3d 6f 0e 00 00 lea 0xe6f(%rip),%rdi # 2015 <_IO_stdin_used+0x15>

11a6: b8 00 00 00 00 mov $0x0,%eax

11ab: e8 b0 fe ff ff callq 1060 <printf@plt>

11b0: bf 05 00 00 00 mov $0x5,%edi

11b5: e8 b6 fe ff ff callq 1070 <sleep@plt>

11ba: eb d8 jmp 1194 <mod3_func+0x2b>


00000000000011bc <mod2_func>:

11bc: f3 0f 1e fa endbr64

11c0: 55 push %rbp

11c1: 48 89 e5 mov %rsp,%rbp

11c4: 48 8d 35 7d 0e 00 00 lea 0xe7d(%rip),%rsi # 2048 <__func__.2316>

11cb: 48 8d 3d 66 0e 00 00 lea 0xe66(%rip),%rdi # 2038 <__func__.2925+0x10>

11d2: b8 00 00 00 00 mov $0x0,%eax

11d7: e8 84 fe ff ff callq 1060 <printf@plt>

11dc: b8 00 00 00 00 mov $0x0,%eax

11e1: e8 83 ff ff ff callq 1169 <mod3_func>

11e6: b8 00 00 00 00 mov $0x0,%eax

11eb: 5d pop %rbp

11ec: c3 retq


00000000000011ed <main>:

11ed: f3 0f 1e fa endbr64

11f1: 55 push %rbp

11f2: 48 89 e5 mov %rsp,%rbp

11f5: 48 8d 35 60 0e 00 00 lea 0xe60(%rip),%rsi # 205c <__func__.2316>

11fc: 48 8d 3d 4f 0e 00 00 lea 0xe4f(%rip),%rdi # 2052 <__func__.2316+0xa>

1203: b8 00 00 00 00 mov $0x0,%eax

1208: e8 53 fe ff ff callq 1060 <printf@plt>

120d: b8 00 00 00 00 mov $0x0,%eax

1212: e8 07 00 00 00 callq 121e <mod1_func>

1217: b8 00 00 00 00 mov $0x0,%eax

121c: 5d pop %rbp

121d: c3 retq


000000000000121e <mod1_func>:

121e: f3 0f 1e fa endbr64

1222: 55 push %rbp

1223: 48 89 e5 mov %rsp,%rbp

1226: 48 8d 35 4b 0e 00 00 lea 0xe4b(%rip),%rsi # 2078 <__func__.2316>

122d: 48 8d 3d 34 0e 00 00 lea 0xe34(%rip),%rdi # 2068 <__func__.2316+0xc>

1234: b8 00 00 00 00 mov $0x0,%eax

1239: e8 22 fe ff ff callq 1060 <printf@plt>

123e: b8 00 00 00 00 mov $0x0,%eax

1243: e8 74 ff ff ff callq 11bc <mod2_func>

1248: b8 00 00 00 00 mov $0x0,%eax

124d: 5d pop %rbp

124e: c3 retq

124f: 90 nop


......


00000000000012c0 <__libc_csu_fini>:

12c0: f3 0f 1e fa endbr64

12c4: c3 retq

[root:~/work/v1/gdb/no-symbols/test1]#

我们从prog.debug的反汇编中可以看到很多函数的名字,如main、mod1_func、mod2_func和mod等_func,但在prog的反汇编中是完全没有的,这是因为我们把prog文件中的符号表(.symtab)删除了,所以无法看到诸如函数名字的信息了

可想而知,由于没有了象main、mod1_func、mod2_func等这些符号信息,调试的时候会非常困难。

【注意】

? 程序中的很多符号信息主要是给程序员看的,计算机看到的主要是字节和地址,未必需要这些符号。

总结:

  1. 对于可执行目标文件,可以使用strip --strip-all来进行瘦身,这样能极大的减小可执行文件的大小,但由于删除了符号表和调试相关的信息,这会使调试困难。
  2. 对于可重定位目标文件和共享目标文件(静态链接库和动态链接库),尽量不要使用strip --strip-all进行瘦身,这是因为可重定位目标文件或共享目标文件可能会参与链接过程,需要使用符号表中的信息来解决符号引用的问题,如果删除了它们的符号表,就会造成链接失败。可以考虑使用strip --strip-unneeded对可重定位目标文件和共享目标文件瘦身。
  3. 对于可重定位目标文件,其实它们一般不参与发布或调试过程,所以它们一般不需要保留,也就谈不上还需要瘦身了。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表