[英]Symbols from a static library are not exported while linking to a shared library
[英]How to prevent all symbols from static library to load and why other symbols from same .o file get exported to test while linking static library
假設有三個c文件,例如ac
包含函數xx()
, yy()
而bc
包含nn()
, mm()
和cc
包含qq()
, rr()
。
我用ao
, bo
和co
制作了一個靜態庫stat.a
如果我將stat.a
鏈接到調用xx()
的測試中,則符號yy()
也將導出: nm test
具有符號xx
和yy
。
qq
和rr
符號沒有導出? xx
以外的其他符號? 您必須將意圖告知鏈接器。 如何強制gcc鏈接未使用的靜態庫
gcc -L./ -o test test.c -Wl,-整體存檔狀態a -Wl,-無整體存檔
gcc -fact-sections -c ac
gcc -L./ -o test test.c -Wl,-gc-sections stat.a
這是您的方案的實現:
交流電
#include <stdio.h>
void xx(void)
{
puts(__func__);
}
void yy(void)
{
puts(__func__);
}
公元前
#include <stdio.h>
void nn(void)
{
puts(__func__);
}
void mm(void)
{
puts(__func__);
}
抄送
#include <stdio.h>
void qq(void)
{
puts(__func__);
}
void rr(void)
{
puts(__func__);
}
測試
extern void xx(void);
int main(void)
{
xx();
return 0;
}
將所有*.c
文件編譯為*.o
文件:
$ gcc -Wall -c a.c b.c c.c test.c
創建一個靜態庫stat.a
,其中包含ao
, bo
, co
:
$ ar rcs stat.a a.o b.o c.o
鏈接程序test
,輸入test.o
和stat.a
:
$ gcc -o test test.o stat.a
跑:
$ ./test
xx
讓我們看看stat.a
目標文件的符號表:
$ nm stat.a
a.o:
0000000000000000 r __func__.2250
0000000000000003 r __func__.2254
U _GLOBAL_OFFSET_TABLE_
U puts
0000000000000000 T xx
0000000000000013 T yy
b.o:
0000000000000000 r __func__.2250
0000000000000003 r __func__.2254
U _GLOBAL_OFFSET_TABLE_
0000000000000013 T mm
0000000000000000 T nn
U puts
c.o:
0000000000000000 r __func__.2250
0000000000000003 r __func__.2254
U _GLOBAL_OFFSET_TABLE_
U puts
0000000000000000 T qq
0000000000000013 T rr
xx
, yy
的定義( T
)在成員stat.a(ao)
。 nn
, mm
定義在stat.a(bo)
。 qq
, rr
定義在stat.a(co)
。
讓我們看看在程序test
的符號表中也定義了哪些符號:
$ nm test | egrep 'T (xx|yy|qq|rr|nn|mm)'
000000000000064a T xx
000000000000065d T yy
定義了在程序中調用的 xx
。 還定義了yy
( 未調用)。 都不存在nn
, mm
, qq
和rr
,都沒有被調用。
那就是你所觀察到的。
我想知道為什么符號
rr
無法導出?
什么是靜態庫 ,例如stat.a
,它在鏈接中的特殊作用是什么?
它是一個ar
檔案 ,按慣例-但不一定-除目標文件外不包含任何內容。 你可以提供這樣的存檔,以從中選擇需要的目標文件,如果有的話,進行聯動的連接。 鏈接器需要檔案庫中的那些目標文件,這些文件提供了已鏈接的輸入文件中已引用但尚未定義的符號的定義。 鏈接器從存檔中提取所需的目標文件,並將它們輸入到鏈接中,就像它們分別被命名為輸入文件一樣,並且根本沒有提及靜態庫。
因此,鏈接器對輸入靜態庫所做的操作與對輸入目標文件所做的操作不同。 任何輸入目標文件都無條件鏈接到輸出文件(無論是否需要)。
鑒於此,讓我們重做test
與某些診斷程序( -trace)
的鏈接,以顯示實際鏈接的文件:
$ gcc -o test test.o stat.a -Wl,--trace
/usr/bin/x86_64-linux-gnu-ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
test.o
(stat.a)a.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
除了gcc
默認添加的C程序鏈接的所有樣板文件之外,鏈接中我們唯一的文件是兩個目標文件:
test.o
(stat.a)a.o
鏈接:
$ gcc -o test test.o stat.a
與鏈接完全相同 :
$ gcc -o test test.o a.o
讓我們仔細考慮一下。
test.o
是第一個鏈接器輸入。 該目標文件無條件地鏈接到程序中。 test.o
包含對xx
的引用(特別是函數調用),但沒有函數xx
定義。 xx
的定義才能完成鏈接。 stat.a
stat.a
中搜索包含xx
定義的目標文件。 ao
。 它從存檔中提取ao
並將其鏈接到程序中。 stat.a(bo)
或stat(co)
找到其定義。 因此,這些目標文件都沒有被提取和鏈接。 通過提取一個鏈接(僅) stat.a(ao)
,鏈接器獲得了xx
的定義,該定義需要解析test.o
的函數調用。 但是ao
也包含yy
的定義。 因此,該定義也鏈接到程序中。 在程序中未定義nn
, mm
, qq
和rr
,因為在鏈接到程序中的目標文件中均未定義它們。
那就是你第一個問題的答案。 您的第二個是:
有什么方法可以防止加載除
xx
以外的其他符號?
至少有兩種方法。
一個簡單的方法是自己定義源文件中的xx
, yy
, nn
, mm
, qq
, rr
。 然后編譯目標文件xx.o
, yy.o
, nn.o
, mm.o
, qq.o
, rr.o
並將它們全部歸檔在stat.a
。 然后,如果鏈接器需要在stat.a
中找到定義xx
的目標文件,它將找到xx.o
,提取並鏈接它,並且僅將xx
的定義添加到鏈接中。
還有另一種方法,不需要您在每個源文件中僅編寫一個函數。 這種方式取決於以下事實:由編譯器生成的ELF目標文件由各個部分組成,而這些部分實際上是鏈接程序區分並合並到輸出文件中的單元。 默認情況下,每種符號都有一個標准的ELF部分。 編譯器將所有函數定義放在一個代碼段中,並將所有數據定義放在一個適當的數據段中。 您的程序test
鏈接同時包含xx
和yy
的定義的原因是,編譯器已將這兩個定義都放在ao
的單個代碼段中,因此鏈接程序可以將該代碼段合並到程序中,也可以不合並:它只能鏈接xx
和 yy
的定義,也不能兩者都鏈接,因此即使只需要xx
,也必須鏈接兩者。 讓我們看看ao
的代碼部分的反匯編。 默認情況下,代碼段稱為.text
:
$ objdump -d a.o
a.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <xx>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # b <xx+0xb>
b: e8 00 00 00 00 callq 10 <xx+0x10>
10: 90 nop
11: 5d pop %rbp
12: c3 retq
0000000000000013 <yy>:
13: 55 push %rbp
14: 48 89 e5 mov %rsp,%rbp
17: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 1e <yy+0xb>
1e: e8 00 00 00 00 callq 23 <yy+0x10>
23: 90 nop
24: 5d pop %rbp
25: c3 retq
在那里,您可以在.text
部分看到xx
和yy
的定義。
但是您可以要求編譯器將每個全局符號的定義放在目標文件的其自己的部分中。 然后,鏈接器可以將任何函數定義的代碼部分與其他任何部分分開,並且您可以要求鏈接器丟棄輸出文件中未使用的任何部分。 讓我們嘗試一下。
再次編譯所有源文件,這一次每個符號要求一個單獨的部分:
$ gcc -Wall -ffunction-sections -fdata-sections -c a.c b.c c.c test.c
現在再來看一下ao
的反匯編:
$ objdump -d a.o
a.o: file format elf64-x86-64
Disassembly of section .text.xx:
0000000000000000 <xx>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # b <xx+0xb>
b: e8 00 00 00 00 callq 10 <xx+0x10>
10: 90 nop
11: 5d pop %rbp
12: c3 retq
Disassembly of section .text.yy:
0000000000000000 <yy>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # b <yy+0xb>
b: e8 00 00 00 00 callq 10 <yy+0x10>
10: 90 nop
11: 5d pop %rbp
12: c3 retq
現在我們在ao
有兩個代碼段: .text.xx
,僅包含xx
的定義; .text.yy
,僅包含yy
的定義。 鏈接器可以將這些部分中的任何一個合並到一個程序中,而不能合並另一個。
重建stat.a
$ rm stat.a
$ ar rcs stat.a a.o b.o c.o
重新鏈接程序,這次要求鏈接器丟棄未使用的輸入節( -gc-sections
)。 我們還將要求它跟蹤加載的文件( -trace
)並為我們打印一個-Map=mapfile
文件( -Map=mapfile
):
$ gcc -o test test.o stat.a -Wl,-gc-sections,-trace,-Map=mapfile
/usr/bin/x86_64-linux-gnu-ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
test.o
(stat.a)a.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
-trace
輸出與以前完全相同。 但是,請再次檢查程序中定義了哪些符號:
$ nm test | egrep 'T (xx|yy|qq|rr|nn|mm)'
000000000000064a T xx
只有xx
,這是您想要的。
該程序的輸出與以前相同:
$ ./test
xx
最后看一下地圖文件。 在頂部附近,您會看到:
映射文件
...
Discarded input sections
...
...
.text.yy 0x0000000000000000 0x13 stat.a(a.o)
...
...
鏈接器能夠從輸入文件stat.a(ao)
丟棄冗余代碼段.text.yy
。 這就是程序中不再存在yy
的冗余定義的原因。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.