簡體   English   中英

如何防止靜態庫中的所有符號加載以及鏈接靜態庫時為什么導出來自同一.o文件的其他符號以進行測試

[英]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()

我用aoboco制作了一個靜態庫stat.a 如果我將stat.a鏈接到調用xx()的測試中,則符號yy()也將導出: nm test具有符號xxyy

  1. 我想知道為什么qqrr符號沒有導出?
  2. 有什么方法可以防止加載除xx以外的其他符號?
  1. 我想知道為什么qq和rr符號沒有導出?

您必須將意圖告知鏈接器。 如何強制gcc鏈接未使用的靜態庫

gcc -L./ -o test test.c -Wl,-整體存檔狀態a -Wl,-無整體存檔

  1. 有什么方法可以防止加載除xx以外的其他符號?

與gcc靜態鏈接時,我該如何僅包括使用的符號?

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 ,其中包含aoboco

$ ar rcs stat.a a.o b.o c.o

鏈接程序test ,輸入test.ostat.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

xxyy的定義( T )在成員stat.a(ao) nnmm定義在stat.a(bo) qqrr定義在stat.a(co)

讓我們看看在程序test的符號表中也定義了哪些符號:

$ nm test | egrep 'T (xx|yy|qq|rr|nn|mm)'
000000000000064a T xx
000000000000065d T yy

定義了在程序中調用的 xx 還定義了yy調用)。 都不存在nnmmqqrr ,都沒有被調用。

那就是你所觀察到的。

我想知道為什么符號qqrr無法導出?

什么是靜態庫 ,例如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的定義。 因此,該定義也鏈接到程序中。 在程序中未定義nnmmqqrr ,因為在鏈接到程序中的目標文件中均未定義它們。

那就是你第一個問題的答案。 您的第二個是:

有什么方法可以防止加載除xx以外的其他符號?

至少有兩種方法。

一個簡單的方法是自己定義源文件中xxyynnmmqqrr 然后編譯目標文件xx.oyy.onn.omm.oqq.orr.o並將它們全部歸檔在stat.a 然后,如果鏈接器需要在stat.a中找到定義xx的目標文件,它將找到xx.o ,提取並鏈接它,並且xx的定義添加到鏈接中。

還有另一種方法,不需要您在每個源文件中僅編寫一個函數。 這種方式取決於以下事實:由編譯器生成的ELF目標文件由各個部分組成,而這些部分實際上是鏈接程序區分並合並到輸出文件中的單元。 默認情況下,每種符號都有一個標准的ELF部分。 編譯器將所有函數定義放在一個代碼段中,並將所有數據定義放在一個適當的數據段中。 您的程序test鏈接同時包含xxyy的定義的原因是,編譯器已將這兩個定義都放在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部分看到xxyy的定義。

但是您可以要求編譯器將每個全局符號的定義放在目標文件的其自己的部分中。 然后,鏈接器可以將任何函數定義的代碼部分與其他任何部分分開,並且您可以要求鏈接器丟棄輸出文件中未使用的任何部分。 讓我們嘗試一下。

再次編譯所有源文件,這一次每個符號要求一個單獨的部分:

$ 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM