
#干了這碗雞湯
生活總是這樣,不能叫人處處都滿(mǎn)意。但我們還要熱情地活下去。人活一生,值得愛(ài)的東西很多,不要因?yàn)橐粋€(gè)不滿(mǎn)意,就灰心。
—— 路遙《平凡的世界》
在Linux中有一個(gè)命令我們平時(shí)肯定用過(guò),它就是strip,
咳咳,跑題了,不是這個(gè)strip。
通過(guò)strip可以移除目標(biāo)文件的符號(hào)信息,可以減少目標(biāo)文件的體積,這里有幾個(gè)問(wèn)題:
什么是符號(hào)?
如何使用strip?
strip的作用是什么?
動(dòng)態(tài)鏈接庫(kù)如果被strip后還能被鏈接成功嗎?
靜態(tài)鏈接庫(kù)如果被strip后還能被鏈接成功嗎?
什么是符號(hào)?
符號(hào)可以看作是鏈接中的粘合劑,整個(gè)鏈接過(guò)程需要基于符號(hào)才可以正確完成。鏈接過(guò)程的本質(zhì)就是把多個(gè)不同的目標(biāo)文件相互粘到一起,像積木一樣各有凹凸部分,但還是可以拼接成一個(gè)整體,這個(gè)將多個(gè)目標(biāo)文件粘到一起的東西就是符號(hào)。可以將函數(shù)和變量統(tǒng)稱(chēng)為符號(hào),而函數(shù)名和變量名統(tǒng)稱(chēng)為符號(hào)名。
在Linux中可以通過(guò)一些命令來(lái)查看符號(hào)信息:
nm命令:
nm test.o
U _GLOBAL_OFFSET_TABLE_
0000000000000000 T main
U puts
objdump命令:
objdump -t test.o
file format elf64-x86-64 :
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 test_c.cc
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g F .text 0000000000000017 main
0000000000000000 *UND* 0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000000000 *UND* 0000000000000000 puts
readelf命令:
readelf -s test.o
Symbol table '.symtab' contains 12 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS test_c.cc
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 6
9: 0000000000000000 23 FUNC GLOBAL DEFAULT 1 main
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
如何使用strip?
在Linux中可以使用man strip查看strip使用方法,最主要的就是移除所有符號(hào)的-s參數(shù),用于清除所有的符號(hào)信息:
strip -s xxx
在使用strip之前先使用nm查看下可執(zhí)行程序的符號(hào)信息:
nm a.out
0000000000200da0 d _DYNAMIC
0000000000200fa0 d _GLOBAL_OFFSET_TABLE_
000000000000089b t _GLOBAL__sub_I__Z4funcPc
0000000000000930 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000000852 t _Z41__static_initialization_and_destruction_0ii
00000000000007fa T _Z4funcPc
000000000000081c T _Z4funci
U _ZNSt8ios_base4InitC1Ev@@GLIBCXX_3.4
U _ZNSt8ios_base4InitD1Ev@@GLIBCXX_3.4
0000000000201020 B _ZSt4cout@@GLIBCXX_3.4
0000000000000934 r _ZStL19piecewise_construct
0000000000201131 b _ZStL8__ioinit
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@@GLIBCXX_3.4
0000000000000b24 r __FRAME_END__
0000000000000940 r __GNU_EH_FRAME_HDR
0000000000201010 D __TMC_END__
0000000000201010 B __bss_start
U __cxa_atexit@@GLIBC_2.2.5
w __cxa_finalize@@GLIBC_2.2.5
0000000000201000 D __data_start
00000000000007b0 t __do_global_dtors_aux
0000000000200d98 t __do_global_dtors_aux_fini_array_entry
0000000000201008 D __dso_handle
0000000000200d88 t __frame_dummy_init_array_entry
w __gmon_start__
0000000000200d98 t __init_array_end
0000000000200d88 t __init_array_start
0000000000000920 T __libc_csu_fini
00000000000008b0 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
0000000000201010 D _edata
0000000000201138 B _end
0000000000000924 T _fini
0000000000000688 T _init
00000000000006f0 T _start
0000000000201130 b completed.7698
0000000000201000 W data_start
0000000000000720 t deregister_tm_clones
00000000000007f0 t frame_dummy
000000000000083d T main
0000000000000760 t register_tm_clones
當(dāng)前這個(gè)可執(zhí)行程序的文件大小是8840字節(jié):
-rwxrwxrwx 1 a a 8840 Nov 29 14:54 a.out
使用strip清除符號(hào)信息:
~/test$ strip -s a.out
strip后再查看可執(zhí)行文件的符號(hào)信息:
~/test$ nm a.out
nm: a.out: no symbols
發(fā)現(xiàn)什么符號(hào)都沒(méi)有了,但還是可以執(zhí)行。
strip后的可執(zhí)行程序文件大小是6120字節(jié):
-rwxrwxrwx 1 a a 6120 Nov 29 14:54 a.out
由此可見(jiàn)通過(guò)strip我們可以減少程序的體積。
strip的作用是什么?
前面已經(jīng)大體介紹過(guò),strip最大的作用就是可以減少程序的體積,一般公司對(duì)發(fā)布的程序體積要求是極其嚴(yán)格的,strip命令是減少程序體積的一個(gè)很有效的方法。另一個(gè)作用就是提高了安全性,沒(méi)有了這些符號(hào),別人分析沒(méi)有符號(hào)的程序會(huì)變得更加困難。
動(dòng)態(tài)鏈接庫(kù)如果被strip后還能被鏈接成功嗎?
先說(shuō)答案,可以。
先貼出兩段代碼:
// shared.cc
void Print(int a) { std::cout << "Hello World " << a << std::endl; }
// main.cc
void Print(int a);
int main() {
Print(666);
return 0;
}
將shared.cc編成一個(gè)動(dòng)態(tài)鏈接庫(kù):
g++ shared.cc -o shared.so -shared -fPIC
使用readelf查看鏈接庫(kù)的符號(hào)信息:
readelf -S shared.so
There are 28 section headers, starting at offset 0x1aa0:
Section Headers:
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 00000000000001c8 000001c8
0000000000000024 0000000000000000 A 0 0 4
2] .gnu.hash GNU_HASH 00000000000001f0 000001f0
000000000000003c 0000000000000000 A 3 0 8
3] .dynsym DYNSYM 0000000000000230 00000230
00000000000001c8 0000000000000018 A 4 1 8
4] .dynstr STRTAB 00000000000003f8 000003f8
0000000000000189 0000000000000000 A 0 0 1
5] .gnu.version VERSYM 0000000000000582 00000582
0000000000000026 0000000000000002 A 3 0 2
6] .gnu.version_r VERNEED 00000000000005a8 000005a8
0000000000000040 0000000000000000 A 4 2 8
7] .rela.dyn RELA 00000000000005e8 000005e8
0000000000000108 0000000000000018 A 3 0 8
8] .rela.plt RELA 00000000000006f0 000006f0
0000000000000078 0000000000000018 AI 3 21 8
9] .init PROGBITS 0000000000000768 00000768
0000000000000017 0000000000000000 AX 0 0 4
.plt PROGBITS 0000000000000780 00000780
0000000000000060 0000000000000010 AX 0 0 16
.plt.got PROGBITS 00000000000007e0 000007e0
0000000000000008 0000000000000008 AX 0 0 8
.text PROGBITS 00000000000007f0 000007f0
0000000000000181 0000000000000000 AX 0 0 16
.fini PROGBITS 0000000000000974 00000974
0000000000000009 0000000000000000 AX 0 0 4
.rodata PROGBITS 000000000000097d 0000097d
000000000000000e 0000000000000000 A 0 0 1
.eh_frame_hdr PROGBITS 000000000000098c 0000098c
0000000000000034 0000000000000000 A 0 0 4
.eh_frame PROGBITS 00000000000009c0 000009c0
00000000000000bc 0000000000000000 A 0 0 8
.init_array INIT_ARRAY 0000000000200de0 00000de0
0000000000000010 0000000000000008 WA 0 0 8
.fini_array FINI_ARRAY 0000000000200df0 00000df0
0000000000000008 0000000000000008 WA 0 0 8
.dynamic DYNAMIC 0000000000200df8 00000df8
00000000000001d0 0000000000000010 WA 4 0 8
.got PROGBITS 0000000000200fc8 00000fc8
0000000000000038 0000000000000008 WA 0 0 8
.got.plt PROGBITS 0000000000201000 00001000
0000000000000040 0000000000000008 WA 0 0 8
.data PROGBITS 0000000000201040 00001040
0000000000000008 0000000000000000 WA 0 0 8
.bss NOBITS 0000000000201048 00001048
0000000000000008 0000000000000000 WA 0 0 1
.comment PROGBITS 0000000000000000 00001048
0000000000000029 0000000000000001 MS 0 0 1
.symtab SYMTAB 0000000000000000 00001078
0000000000000600 0000000000000018 26 46 8
.strtab STRTAB 0000000000000000 00001678
0000000000000330 0000000000000000 0 0 1
.shstrtab STRTAB 0000000000000000 000019a8
00000000000000f1 0000000000000000 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)
注意這里有28個(gè)符號(hào)段,主要有symtab、strtab、dynsym、dynstr段。
strip后再看下符號(hào)信息:
readelf -S shared.so
There are 26 section headers, starting at offset 0x1158:
Section Headers:
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 00000000000001c8 000001c8
0000000000000024 0000000000000000 A 0 0 4
2] .gnu.hash GNU_HASH 00000000000001f0 000001f0
000000000000003c 0000000000000000 A 3 0 8
3] .dynsym DYNSYM 0000000000000230 00000230
00000000000001c8 0000000000000018 A 4 1 8
4] .dynstr STRTAB 00000000000003f8 000003f8
0000000000000189 0000000000000000 A 0 0 1
5] .gnu.version VERSYM 0000000000000582 00000582
0000000000000026 0000000000000002 A 3 0 2
6] .gnu.version_r VERNEED 00000000000005a8 000005a8
0000000000000040 0000000000000000 A 4 2 8
7] .rela.dyn RELA 00000000000005e8 000005e8
0000000000000108 0000000000000018 A 3 0 8
8] .rela.plt RELA 00000000000006f0 000006f0
0000000000000078 0000000000000018 AI 3 21 8
9] .init PROGBITS 0000000000000768 00000768
0000000000000017 0000000000000000 AX 0 0 4
.plt PROGBITS 0000000000000780 00000780
0000000000000060 0000000000000010 AX 0 0 16
.plt.got PROGBITS 00000000000007e0 000007e0
0000000000000008 0000000000000008 AX 0 0 8
.text PROGBITS 00000000000007f0 000007f0
0000000000000181 0000000000000000 AX 0 0 16
.fini PROGBITS 0000000000000974 00000974
0000000000000009 0000000000000000 AX 0 0 4
.rodata PROGBITS 000000000000097d 0000097d
000000000000000e 0000000000000000 A 0 0 1
.eh_frame_hdr PROGBITS 000000000000098c 0000098c
0000000000000034 0000000000000000 A 0 0 4
.eh_frame PROGBITS 00000000000009c0 000009c0
00000000000000bc 0000000000000000 A 0 0 8
.init_array INIT_ARRAY 0000000000200de0 00000de0
0000000000000010 0000000000000008 WA 0 0 8
.fini_array FINI_ARRAY 0000000000200df0 00000df0
0000000000000008 0000000000000008 WA 0 0 8
.dynamic DYNAMIC 0000000000200df8 00000df8
00000000000001d0 0000000000000010 WA 4 0 8
.got PROGBITS 0000000000200fc8 00000fc8
0000000000000038 0000000000000008 WA 0 0 8
.got.plt PROGBITS 0000000000201000 00001000
0000000000000040 0000000000000008 WA 0 0 8
.data PROGBITS 0000000000201040 00001040
0000000000000008 0000000000000000 WA 0 0 8
.bss NOBITS 0000000000201048 00001048
0000000000000008 0000000000000000 WA 0 0 1
.comment PROGBITS 0000000000000000 00001048
0000000000000029 0000000000000001 MS 0 0 1
.shstrtab STRTAB 0000000000000000 00001071
00000000000000e1 0000000000000000 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)
注意這里有26個(gè)符號(hào)段,主要有dynsym、dynstr段,這兩個(gè)段symtab、strtab被清除掉。
而且依舊可以被鏈接成功并且成功執(zhí)行程序:
~/test$ g++ main.cc -o main ./shared.so;./main
Hello World 666
為什么動(dòng)態(tài)鏈接庫(kù)被strip后還可以鏈接成功呢?因?yàn)閟trip只清除普通符號(hào)表,會(huì)保留動(dòng)態(tài)符號(hào)表,即dynsym、dynstr段,而動(dòng)態(tài)鏈接依靠的就是動(dòng)態(tài)符號(hào)表。
靜態(tài)鏈接庫(kù)如果被strip后還能被鏈接成功嗎?
也是先說(shuō)答案,合理strip后就可以。
先貼出兩段代碼:
// static.cc
void Print(int a) { std::cout << "Hello World " << a << std::endl; }
void Print(int a);
int main() {
Print(666);
return 0;
}
先將static.cc打包成libsta.a:
gcc -c staticd.cc -o sta.o
ar -r libsta.a sta.o
查看下靜態(tài)庫(kù)的符號(hào):
nm libsta.a
:
U _GLOBAL_OFFSET_TABLE_
000000000000008f t _GLOBAL__sub_I__Z5Printi
0000000000000046 t _Z41__static_initialization_and_destruction_0ii
0000000000000000 T _Z5Printi
U _ZNSolsEPFRSoS_E
U _ZNSolsEi
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
U _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
0000000000000000 r _ZStL19piecewise_construct
0000000000000000 b _ZStL8__ioinit
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
U __cxa_atexit
U __dso_handle
將libsta.a庫(kù)strip后發(fā)現(xiàn)什么符號(hào)都沒(méi)有,且鏈接會(huì)失敗:
~/test$ strip -s libsta.a
~/test$ nm libsta.a
sta.o:
nm: sta.o: no symbols
~/test$ g++ main.cc -o main -L. -lsta; ./main
./libsta.a: error adding symbols: Archive has no index; run ranlib to add one
collect2: error: ld returned 1 exit status
-bash: ./main: No such file or directory
那難道靜態(tài)鏈接庫(kù)就不能strip了嗎?不strip的文件豈不是體積很大?
其實(shí)還是可以strip的,但需要合理的使用strip,這里需要換一個(gè)strip的參數(shù),就是--strip-unneeded,它確保strip掉的是沒(méi)有用的符號(hào),保留用于鏈接的符號(hào),盡管--strip-unneeded不如-s清除的徹底,但是保留了很多有用的信息,確保該鏈接庫(kù)是可用的。
strip --strip-unneeded libsta.a
nm libsta.a
:
0000000000000000 T _Z5Printi
U _ZNSolsEPFRSoS_E
U _ZNSolsEi
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
U _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
U __cxa_atexit
U __dso_handle
從上面可以看出:通過(guò)--strip-unneeded即清除了部分符號(hào)的信息,還能保證庫(kù)可用,減少程序體積。
關(guān)于strip,今天先介紹到這里,相信大家看完可以對(duì)strip理解的更深刻,并能更合理的使用strip。關(guān)于編譯和鏈接,大家可以后臺(tái)發(fā)送關(guān)鍵字“程序鏈接”了解更多細(xì)節(jié)。
參考資料
https://zhuanlan.zhihu.com/p/72475595
https://xuanxuanblingbling.github.io/ctf/tools/2019/09/06/symbol/
往期推薦



