strip過的動態連結庫還能用嗎?
技術標籤:linux
在Linux中有一個命令我們平時肯定用過,它就是strip,
咳咳,跑題了,不是這個strip。
通過strip可以移除目標檔案的符號資訊,可以減少目標檔案的體積,這裡有幾個問題:
-
什麼是符號?
-
如何使用strip?
-
strip的作用是什麼?
-
動態連結庫如果被strip後還能被連結成功嗎?
-
靜態連結庫如果被strip後還能被連結成功嗎?
什麼是符號?
符號可以看作是連結中的粘合劑,整個連結過程需要基於符號才可以正確完成。連結過程的本質就是把多個不同的目標檔案相互粘到一起,像積木一樣各有凹凸部分,但還是可以拼接成一個整體,這個將多個目標檔案粘到一起的東西就是符號。可以將函式和變數統稱為符號,而函式名和變數名統稱為符號名。
在Linux中可以通過一些命令來檢視符號資訊:
nm命令:
$ nm test.o
U _GLOBAL_OFFSET_TABLE_
0000000000000000 T main
U puts
objdump命令:
$ objdump -t test.o 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使用方法,最主要的就是移除所有符號的-s引數,用於清除所有的符號資訊:
strip -s xxx
在使用strip之前先使用nm檢視下可執行程式的符號資訊:
~/test$ 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 [email protected]@GLIBCXX_3.4
U [email protected]@GLIBCXX_3.4
0000000000201020 B [email protected]@GLIBCXX_3.4
0000000000000934 r _ZStL19piecewise_construct
0000000000201131 b _ZStL8__ioinit
U [email protected]@GLIBCXX_3.4
0000000000000b24 r __FRAME_END__
0000000000000940 r __GNU_EH_FRAME_HDR
0000000000201010 D __TMC_END__
0000000000201010 B __bss_start
U [email protected]@GLIBC_2.2.5
w [email protected]@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 [email protected]@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
當前這個可執行程式的檔案大小是8840位元組:
-rwxrwxrwx 1 a a 8840 Nov 29 14:54 a.out
使用strip清除符號資訊:
~/test$ strip -s a.out
strip後再檢視可執行檔案的符號資訊:
~/test$ nm a.out
nm: a.out: no symbols
發現什麼符號都沒有了,但還是可以執行。
strip後的可執行程式檔案大小是6120位元組:
-rwxrwxrwx 1 a a 6120 Nov 29 14:54 a.out
由此可見通過strip我們可以減少程式的體積。
strip的作用是什麼?
前面已經大體介紹過,strip最大的作用就是可以減少程式的體積,一般公司對釋出的程式體積要求是極其嚴格的,strip命令是減少程式體積的一個很有效的方法。另一個作用就是提高了安全性,沒有了這些符號,別人分析沒有符號的程式會變得更加困難。
動態連結庫如果被strip後還能被連結成功嗎?
先說答案,可以。
先貼出兩段程式碼:
// shared.cc
#include <iostream>
void Print(int a) { std::cout << "Hello World " << a << std::endl; }
// main.cc
#include <iostream>
void Print(int a);
int main() {
Print(666);
return 0;
}
將shared.cc編成一個動態連結庫:
g++ shared.cc -o shared.so -shared -fPIC
使用readelf檢視連結庫的符號資訊:
~/test$ readelf -S shared.so
There are 28 section headers, starting at offset 0x1aa0:
Section Headers:
[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 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
[10] .plt PROGBITS 0000000000000780 00000780
0000000000000060 0000000000000010 AX 0 0 16
[11] .plt.got PROGBITS 00000000000007e0 000007e0
0000000000000008 0000000000000008 AX 0 0 8
[12] .text PROGBITS 00000000000007f0 000007f0
0000000000000181 0000000000000000 AX 0 0 16
[13] .fini PROGBITS 0000000000000974 00000974
0000000000000009 0000000000000000 AX 0 0 4
[14] .rodata PROGBITS 000000000000097d 0000097d
000000000000000e 0000000000000000 A 0 0 1
[15] .eh_frame_hdr PROGBITS 000000000000098c 0000098c
0000000000000034 0000000000000000 A 0 0 4
[16] .eh_frame PROGBITS 00000000000009c0 000009c0
00000000000000bc 0000000000000000 A 0 0 8
[17] .init_array INIT_ARRAY 0000000000200de0 00000de0
0000000000000010 0000000000000008 WA 0 0 8
[18] .fini_array FINI_ARRAY 0000000000200df0 00000df0
0000000000000008 0000000000000008 WA 0 0 8
[19] .dynamic DYNAMIC 0000000000200df8 00000df8
00000000000001d0 0000000000000010 WA 4 0 8
[20] .got PROGBITS 0000000000200fc8 00000fc8
0000000000000038 0000000000000008 WA 0 0 8
[21] .got.plt PROGBITS 0000000000201000 00001000
0000000000000040 0000000000000008 WA 0 0 8
[22] .data PROGBITS 0000000000201040 00001040
0000000000000008 0000000000000000 WA 0 0 8
[23] .bss NOBITS 0000000000201048 00001048
0000000000000008 0000000000000000 WA 0 0 1
[24] .comment PROGBITS 0000000000000000 00001048
0000000000000029 0000000000000001 MS 0 0 1
[25] .symtab SYMTAB 0000000000000000 00001078
0000000000000600 0000000000000018 26 46 8
[26] .strtab STRTAB 0000000000000000 00001678
0000000000000330 0000000000000000 0 0 1
[27] .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個符號段,主要有symtab、strtab、dynsym、dynstr段。
strip後再看下符號資訊:
~/test$ readelf -S shared.so
There are 26 section headers, starting at offset 0x1158:
Section Headers:
[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 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
[10] .plt PROGBITS 0000000000000780 00000780
0000000000000060 0000000000000010 AX 0 0 16
[11] .plt.got PROGBITS 00000000000007e0 000007e0
0000000000000008 0000000000000008 AX 0 0 8
[12] .text PROGBITS 00000000000007f0 000007f0
0000000000000181 0000000000000000 AX 0 0 16
[13] .fini PROGBITS 0000000000000974 00000974
0000000000000009 0000000000000000 AX 0 0 4
[14] .rodata PROGBITS 000000000000097d 0000097d
000000000000000e 0000000000000000 A 0 0 1
[15] .eh_frame_hdr PROGBITS 000000000000098c 0000098c
0000000000000034 0000000000000000 A 0 0 4
[16] .eh_frame PROGBITS 00000000000009c0 000009c0
00000000000000bc 0000000000000000 A 0 0 8
[17] .init_array INIT_ARRAY 0000000000200de0 00000de0
0000000000000010 0000000000000008 WA 0 0 8
[18] .fini_array FINI_ARRAY 0000000000200df0 00000df0
0000000000000008 0000000000000008 WA 0 0 8
[19] .dynamic DYNAMIC 0000000000200df8 00000df8
00000000000001d0 0000000000000010 WA 4 0 8
[20] .got PROGBITS 0000000000200fc8 00000fc8
0000000000000038 0000000000000008 WA 0 0 8
[21] .got.plt PROGBITS 0000000000201000 00001000
0000000000000040 0000000000000008 WA 0 0 8
[22] .data PROGBITS 0000000000201040 00001040
0000000000000008 0000000000000000 WA 0 0 8
[23] .bss NOBITS 0000000000201048 00001048
0000000000000008 0000000000000000 WA 0 0 1
[24] .comment PROGBITS 0000000000000000 00001048
0000000000000029 0000000000000001 MS 0 0 1
[25] .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個符號段,主要有dynsym、dynstr段,這兩個段symtab、strtab被清除掉。
而且依舊可以被連結成功並且成功執行程式:
~/test$ g++ main.cc -o main ./shared.so;./main
Hello World 666
為什麼動態連結庫被strip後還可以連結成功呢?因為strip只清除普通符號表,會保留動態符號表,即dynsym、dynstr段,而動態連結依靠的就是動態符號表。
靜態連結庫如果被strip後還能被連結成功嗎?
也是先說答案,合理strip後就可以。
先貼出兩段程式碼:
// static.cc
#include <iostream>
void Print(int a) { std::cout << "Hello World " << a << std::endl; }
#include <iostream>
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
檢視下靜態庫的符號:
~/test$ nm libsta.a
sta.o:
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庫strip後發現什麼符號都沒有,且連結會失敗:
~/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
那難道靜態連結庫就不能strip了嗎?不strip的檔案豈不是體積很大?
其實還是可以strip的,但需要合理的使用strip,這裡需要換一個strip的引數,就是--strip-unneeded,它確保strip掉的是沒有用的符號,保留用於連結的符號,儘管--strip-unneeded不如-s清除的徹底,但是保留了很多有用的資訊,確保該連結庫是可用的。
~/test$ strip --strip-unneeded libsta.a
~/test$ nm libsta.a
sta.o:
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
從上面可以看出:通過--strip-unneeded即清除了部分符號的資訊,還能保證庫可用,減少程式體積。
關於strip,今天先介紹到這裡,相信大家看完可以對strip理解的更深刻,並能更合理的使用strip。關於編譯和連結,大家可以後臺傳送關鍵字“程式連結”瞭解更多細節。
參考資料
https://zhuanlan.zhihu.com/p/72475595
https://xuanxuanblingbling.github.io/ctf/tools/2019/09/06/symbol/