为什么 STATIC lib 中未使用的对象在 SHARED lib 引用它们时包含在最终二进制文件中?
Why unused objects in STATIC lib included in final binary when SHARED lib reference them?
总结:
STATIC 和 SHARED lib 之间的交叉使用函数导致 STATIC lib 的所有对象(甚至未使用!)都包含在最终二进制文件中!
我想你不明白我的意思吧? :-p
坐下来阅读下面的完整故事!
名称已更改以保护无辜者。示例的 tar 简单且可重现。
预告片:有 SSCCE 可用! (简短、自包含、正确(可编译),示例:http://www.sscce.org/)
一开始,我有:
二进制文件 (main
) 调用存储在静态库 (libsub.a
) 中的函数 (fun1a()
)。 main
也有内部函数 (mainsub()
).
包含 SEVERAL 个对象的静态库 (libsub.a
),每个对象都有其他来源使用的多个函数。
编译 main
生成的二进制文件 只有 包含引用函数的对象(静态库)的副本。
在下面的示例中,main
将仅包含对象 shared1.o
的函数(因为 main 正在调用 func1a()
)和 NOT [=41= 的函数](因为没有引用)。
好的!
main.c libsub.a
+-------------+ +------------+
| main | | shared1.o |
| func1a() | <----> | func1a() |
| mainsub() | | func1b() |
+-------------+ | ---- |
| shared2.o |
| func2a() |
| func2b() |
+------------+
作为一项改进,我想让'external'人们能够用他们自己的代码覆盖在main
中调用的函数,而不必重新编译 MY 二进制文件。
无论如何我都没有提供源代码,也没有提供我的静态库。
为此,我打算提供一个 "ready to fill" 函数框架源。 (这就是所谓的用户退出?!)
使用 SHARED / DYNAMIC lib 可以做到这一点恕我直言。
可以被覆盖的函数,要么是主函数内部的(mainsub()
),要么是共享函数(func1a()
...),并且将存储在共享库(.so)中成为 added/referenced在 link.
期间
创建了新源,前缀为 'c',它将包含 'Client' 版本的 'standard' 函数。
使用(或不)覆盖函数的开关超出范围。照原样,如果UE
为真,则覆盖。
cmain.c
是一个包含 Client_mainsub()
的新来源,可以称为 mainsub()
的 'in replacement'
cshared1.c
是一个包含 Client_func1a()
的新来源,可以称为 func1a()
的 'in replacement'。 实际上 shared1.c
中的所有函数都可以在 cshared1.c
中替换
cshared2.c
是一个包含 Client_func2a()
的新来源,可以称为 func2a()
的 'in replacement'
概览变为:
main.c libsub.a clibsub.so
+-----------------------+ +------------------------+ +--------------------+
| main | | shared1.o | | cshared1.o |
| func1a() {} | | func1a() | | Client_func1a() |
| mainsub() | <-> | { if UE | <-> | {do ur stuff } |
| { if UE | | Client_func1a() | | |
| Client_mainsub() | | return } | | cshared2.o |
| return }| | func1b() | | Client_func2a() |
+-----------------------+ | ------- | >| {do ur stuff } |
^ | shared2.o | / +--------------------+
cmain.c v | func2a() | /
+--------------------+ | { if UE | /
| cmain | | Client_func2a() |<
| Client_mainsub() | | return } |
| {do ur stuff } | | func2b() |
+--------------------+ +------------------------+
再次强调,由于 main
不调用 func2a()
也不调用 func2b()
,(静态)对象 shared2.o 不包含在二进制文件中,也没有引用(共享)Client_func2a()
也存在。
好的!
最后,简单地覆盖函数是不够的(或者太多了!)。
我希望外部人员能够调用我的函数(或不能)...但是 ALSO 允许他们做一些正确的事情 BEFORE and/or 对 AFTER 我的功能。
因此,与其将 func2a()
愚蠢地替换为 Client_func2a()
,不如使用伪代码:
shared2.c | cshared2.c
(assume UE=true)
|
func2a() { |Client_func2a() {
if UE {} |
Client_func2a() ==> do (or not) some stuf PRE call
|
| if (DOIT) { // activate or not standard call
| UE=false
| func2a() // do standard stuff
| UE=true
| } else
| { do ur bespoke stuff }
|
| do (or not) some stuf POST call
| }
<==
} else
{ do standard stuff }
}
请记住,cshared2.c
提供给可以(或不能)在提供的框架上做自己的事情的其他人。
(注意:在 Client_func2a()
中将 UE
设置为 false 并返回 true 避免了 func2a()
调用中的无限循环!;-))
我的问题来了。
在那种情况下,结果二进制文件现在包含 shared2.o
对象,尽管 NO 在 main 中对 shared2.c
或 [=54 的任何函数进行了调用=] !!!!!
搜索后这看起来是因为交叉 calls/reference :
shared2.o contains func2a() that may call Client_func2a()
cshared2.o contains Client_func2a() that may call func2a()
那么为什么 main
二进制文件包含 shared2.o ?
>dump -Tv main
main:
***Loader Section***
***Loader Symbol Table Information***
[Index] Value Scn IMEX Sclass Type IMPid Name
[0] 0x00000000 undef IMP RW EXTref libc.a(shr_64.o) errno
[1] 0x00000000 undef IMP DS EXTref libc.a(shr_64.o) __mod_init
[2] 0x00000000 undef IMP DS EXTref libc.a(shr_64.o) exit
[3] 0x00000000 undef IMP DS EXTref libc.a(shr_64.o) printf
[4] 0x00000000 undef IMP RW EXTref libc.a(shr_64.o) __n_pthreads
[5] 0x00000000 undef IMP RW EXTref libc.a(shr_64.o) __crt0v
[6] 0x00000000 undef IMP RW EXTref libc.a(shr_64.o) __malloc_user_defined_name
[7] 0x00000000 undef IMP DS EXTref libcmain.so Client_mainsub1
[8] 0x00000000 undef IMP DS EXTref libcshared.so Client_func1b
[9] 0x00000000 undef IMP DS EXTref libcshared.so Client_func1a
[10] 0x00000000 undef IMP DS EXTref libcshared.so Client_func2b <<< but why ??? ok bcoz func2b() is referenced ...
[11] 0x00000000 undef IMP DS EXTref libcshared.so Client_func2a <<< but why ??? ok bcoz func2a() is referenced ...
[12] 0x110000b50 .data ENTpt DS SECdef [noIMid] __start
[13] 0x110000b78 .data EXP DS SECdef [noIMid] func1a
[14] 0x110000b90 .data EXP DS SECdef [noIMid] func1b
[15] 0x110000ba8 .data EXP DS SECdef [noIMid] func2b <<< but why this ? Not a single call is made in main ???
[16] 0x110000bc0 .data EXP DS SECdef [noIMid] func2a <<< but why this ? Not a single call is made in main ???
请注意,只需添加评论 func2a()
(和 func2b()
)即可解决 link 问题(打叉)...但这是不可能的,因为我想保留共享库 !?
在 AIX 7.1 和 IBM XL C/C++ 12.1 上发生了这种行为,但在 Linux 上看起来是一样的(Red Hat 5 + GCC 5.4 在编译中有一些小的变化参数)
IBM XL C/C++ for AIX, V12.1 (5765-J02, 5725-C72)
Version: 12.01.0000.0000
Driver Version: 12.01(C/C++) Level: 120315
C Front End Version: 12.01(C/C++) Level: 120322
High-Level Optimizer Version: 12.01(C/C++) and 14.01(Fortran) Level: 120315
Low-Level Optimizer Version: 12.01(C/C++) and 14.01(Fortran) Level: 120321
所以我发现这肯定是个误会。谁能解释一下?
这里承诺的是SSCCE。
您可以通过 recreating/downloading 以下小文件和 运行 go.sh 重播我的问题(请参阅脚本内的注释)
Edit1:将代码添加到问题中,而不是按照建议在外部站点上添加代码
main.c
#include <stdio.h>
#include "inc.h"
extern void func1a (), func1b ();
int UEXIT(char* file, char* func)
{
printf(" UEXIT file=<%s> func=<%s>\n",file,func);
return 1; /* always true for testing */
}
main (){
printf(">>> main\n");
func1a ();
mainsub ();
printf("<<< main\n");
}
mainsub () {
printf(">>> mainsub\n");
if(UEXIT("main","mainsub")) {
Client_mainsub1();
return;
}
printf("<<< mainsub\n");
}
cmain.c
#include <stdio.h>
#include "inc.h"
void Client_mainsub1 () {
printf(">>>>>> Client_mainsub1\n");
printf("<<<<<< Client_mainsub1\n");
return;
}
inc.h
extern int UEXIT(char * fileName, char * functionName);
shared1.c
#include <stdio.h>
#include "inc.h"
void func1a (){
printf(">>>>> func1a\n");
if(UEXIT("main","func1a")) {
Client_func1a();
return;
}
printf("<<<<< func1a\n");
}
void func1b (){
printf(">>>>> func1b\n");
if(UEXIT("main","func1b")){
Client_func1b();
return;
}
printf("<<<<< func1b\n");
}
shared2.c
#include <stdio.h>
#include "inc.h"
void func2a (){
printf(">>>>> func2a\n");
if(UEXIT("main","func2a")) {
Client_func2a();
return;
}
printf("<<<<< func2a\n");
}
void func2b (){
printf(">>>>> func2b\n");
if(UEXIT("main","func2b")){
Client_func2b();
return;
}
printf("<<<<< func2b\n");
}
cshared1.c
#include <stdio.h>
#include "inc.h"
void Client_func1a () {
int standardFunctionCall = 0;
printf("\t>>>> Client_func1a\n");
if (standardFunctionCall) {
func1a();
}
printf("\t<<< Client_func1a\n");
return;
}
void Client_func1b () {
int standardFunctionCall = 0;
printf("\t>>>> Client_func1b\n");
if (standardFunctionCall) {
func1b();
}
printf("\t<<< Client_func1b\n");
return;
}
cshared2.c
#include <stdio.h>
#include "inc.h"
void Client_func2a () {
int standardFunctionCall = 0;
printf("\t>>>> Client_func2a\n");
if (standardFunctionCall) {
func2a(); /* !!!!!! comment this to avoid crossed link with shared2.c !!!!! */
}
printf("\t<<< Client_func2a\n");
return;
}
void Client_func2b () {
int standardFunctionCall = 0;
printf("\t>>>> Client_func2b\n");
if (standardFunctionCall) {
func2b(); /* !!!!!! ALSO comment this to avoid crossed link with shared2.c !!!!! */
}
printf("\t<<< Client_func2b\n");
return;
}
go.sh
#!/bin/bash
## usage :
## . ./go.sh
## so that the redefinition of LIBPATH is propagated to calling ENV ...
## otherwise : "Dependent module libcshared.so could not be loaded."
# default OBJECT_MODE to 64 bit (avoid explicitely setting -X64 options...)
export OBJECT_MODE=64
export LIBPATH=.:$LIBPATH
# Compile client functions for target binary
cc -q64 -c -o cmain.o cmain.c
# (1) Shared lib for internal function
cc -G -q64 -o libcmain.so cmain.o
# Compile common functions
cc -c shared2.c shared1.c
# Compile client common functions overwrite
cc -c cshared2.c cshared1.c
# (2) Built libsub.a for common functions (STATIC)
ar -rv libsub.a shared1.o shared2.o
# (3) Built libcshared.so for client common functions overwrite (SHARED)
cc -G -q64 -o libcshared.so cshared1.o cshared2.o
# Finally built binary using above (1) (2) (3)
# main only call func1a() , so should only include objects shared1
# But pragmatically shared2 is also included if cshared2 reference a possible call to func2() in shared2 !!!!????
# Check this with "nm main |grep shared2" or "nm main |grep func2" or "dump -Tv main |grep func2"
cc -q64 -o main main.c -bstatic libsub.a -bshared libcmain.so libcshared.so
# result is the same without specifying -bstatic or -bshared
#cc -q64 -o main2 main.c libsub.a libcmain.so libcshared.so
#If I split libcshared.so into libcshared1.so and libcshared2.so it is also the same :
#cc -G -q64 -o libcshared1.so cshared1.o
#cc -G -q64 -o libcshared2.so cshared2.o
#cc -q64 -o main4 main.c -bstatic libsub.a -bshared libcmain.so libcshared1.so libcshared2.so
#If I do not inlcude libcshared2.so, binary is of course well working, without reference to cshared2 nor shared2 .
# So why linker chooses to add STATIC shared2.o only if libcshared2.so is listed ?
# Is there a way to avoid this add of unused code ?
#cc -q64 -o main4 main.c -bstatic libsub.a -bshared libcmain.so libcshared1.so
Edit2 : 根据要求添加了 RedHat 版本的 go.sh 脚本
gored.sh
## usage :
## . ./gored.sh
## so that the redefinition of LD_LIBRARY_PATH is propagated to calling ENV ...
## otherwise : "Dependent module libcshared.so could not be loaded."
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
# Compile client functions for target binary
gcc -fPIC -c cmain.c
# (1) Shared lib for internal function
gcc -shared -o libcmain.so cmain.o
# Compile common functions
gcc -c shared2.c shared1.c
# Compile client common functions overwrite
gcc -fPIC -c cshared2.c cshared1.c
# (2) Built libsub.a for common functions (STATIC)
ar -rv libsub.a shared1.o shared2.o
# (3) Built libcshared.so for client common functions overwrite (SHARED)
gcc -shared -o libcshared.so cshared1.o cshared2.o
# Finally built binary using above (1) (2) (3)
# main only call func1a() , so should only include objects shared1
# But pragmatically shared2 is also included if cshared2 reference a possible call to func2() in shared2 !!!!????
# Check this with "nm main |grep shared2" or "nm main |grep func2" or "dump -Tv main |grep func2"
gcc -o main main.c libcmain.so libcshared.so libsub.a
#If I split libcshared.so into libcshared1.so and libcshared2.so it is also the same :
gcc -shared -o libcshared1.so cshared1.o
gcc -shared -o libcshared2.so cshared2.o
cc -o main2 main.c libcmain.so libcshared1.so libcshared2.so libsub.a
#If I do not inlcude libcshared2.so, binary is of course well working, without reference to cshared2 nor shared2 .
# So why linker chooses to add STATIC shared2.o only if libcshared2.so is listed ?
# Is there a way to avoid this add of unused code ?
cc -o main3 main.c libcmain.so libcshared1.so libsub.a
或此处将上述完整文件(不含 gored.sh)放在一个 .tar.bz2 中。 (6KB).
只是 copy/paste 在一个新文件中(例如 poc.uue
)。然后输入
uudecode poc.uue
你应该得到 poc.tar.bz2
解压缩,untar进入poc文件夹然后运行
. ./go.sh
然后
dump -Tv main
或者如果在 RedHat 下
nm main
gored.sh
之后的结果示例:
poc>nm main |grep func2
* U Client_func2a
U Client_func2b
0000000000400924 T func2a
000000000040095d T func2b
poc>nm main2 |grep func2
U Client_func2a
U Client_func2b
0000000000400934 T func2a
000000000040096d T func2b
poc>nm main3 |grep func2
poc>
Edit3:ASCII 艺术! :-)
这是未使用 objects/references 的 'visual' 最终状态 我认为包含 linker 是错误的。或者至少不够聪明,无法检测为未使用。
也许这是正常的,或者有一个选项可以避免在最终二进制文件中包含未使用的静态代码。这看起来并不复杂,因为被包围的标记 'UNUSED !?' 代码 link 没有任何内容?不是吗?
main.c libsub.a clibsub.so
+-----------------------+ +-------------------------+ +-----------------------------+
| main | | +---------------------+ | | +-------------------------+ |
| func1a(); <-------------\ | |shared1.o | | | | cshared1.o | |
| mainsub() | \------>func1a() { <-------------+ /-----> Client_func1a() { | |
| { if UE { | | | if UE { | | | / | | PRE-stuff | |
| Client_mainsub() | | | Client_func1a() <-----C---/ | | if (DOIT) { | |
| return ^ | | | return | | | | | UE=false | |
| } | | | | } else { | | +----------------> func1a() | |
| } | | | | do std stuff | | | | UE=true | |
+-------------|---------+ | | } | | | | } else { | |
| | | | | | | do bespoke stuff | |
| | | func1b() { | | | | } | |
| | | same as above | | | | POST-stuff | |
| | | } | | | | } | |
| | +---------------------+ | | | Client_func1b() {} | |
| | | | +-------------------------+ |
| ***|*******U*N*U*S*E*D**?!***|*****U*N*U*S*E*D**?!*******U*N*U*S*E*D**?!****
| * | +---------------------+ | | +-------------------------+ | *
| U | |shared2.o | | | | cshared2.o | | U
| * | | func2a() { <-------------+ /-----> Client_func2a() { | | *
| N | | if UE { | | | / | | PRE-stuff | | N
cmain.so | * | | Client_func2a())<-----C---/ | | if (DOIT) { | | *
+-------------|------+ U | | return | | | | | UE=false | | U
| cmain.o v | * | | } else { | | +----------------> func2a() | | *
| Client_mainsub() | S | | do std stuff | | | | UE=true | | S
| {do ur stuff } | * | | } | | | | } else { | | *
+--------------------+ E | | | | | | do bespoke stuff | | E
* | | func2b() { | | | | } | | *
D | | same as above | | | | POST-stuff | | D
* | | } | | | | Client_func2b() {} | | *
* | +---------------------+ | | +-------------------------+ | *
? +-------------------------+ +---------------------------+ | ?
! !
*********U*N*U*S*E*D**?!*************U*N*U*S*E*D**?!******U*N*U*S*E*D**?!***
欢迎任何让我走上正确道路的建设性答案。
谢谢。
这里是 linker 令人费解的行为的简化说明
你:
main.c
extern void foo(void);
int main(void)
{
foo();
return 0;
}
foo.c
#include <stdio.h>
void foo(void)
{
puts(__func__);
}
bar.c
#include <stdio.h>
extern void do_bar(void);
void bar(void)
{
do_bar();
}
do_bar.c
#include <stdio.h>
void do_bar(void)
{
puts(__func__);
}
让我们将所有这些源文件编译成 object 个文件:
$ gcc -Wall -c main.c foo.c bar.c do_bar.c
现在我们将尝试 link 一个程序,如下所示:
$ gcc -o prog main.o foo.o bar.o
bar.o: In function `bar':
bar.c:(.text+0x5): undefined reference to `do_bar'
未定义函数do_bar
仅在定义中被引用
bar
,并且 bar
未被引用
该程序在所有。为什么 linkage 失败?
很简单,这个 linkage 失败了,因为 我们告诉 linker link bar.o
进入程序;确实如此; bar.o
包含 bar
的定义,
其中引用了 do_bar
,它在 linkage 中没有定义。 bar
不是
被引用,但是 do_bar
是 - 由 bar
,它在程序中 linked。
默认情况下,linker 要求 linkage 中引用的任何符号
程序的定义是在 linkage 中定义的。如果我们强制它到 link 的定义
bar
,那么它将需要 do_bar
的定义,因为没有
do_bar
的定义实际上 没有 bar
的定义。如果 links
bar
的一个定义,它不质疑我们是否需要到link它,
如果答案为否,则允许对 do_bar
的未定义引用
link年龄故障当然可以通过以下方法修复:
$ gcc -o prog main.o foo.o bar.o do_bar.o
$ ./prog
foo
现在在这个例子中,linking bar.o
在程序中是完全没有必要的。我们
也可以 link 成功 而不是 告诉 linker link bar.o
.
gcc -o prog main.o foo.o
$ ./prog
foo
bar.o
和 do_bar.o
都是多余的
正在执行 main
,但程序只能 link 两者都使用,或者两者都不使用
但是假设 foo
和 bar
定义在同一个文件中?
它们可能在同一个 object 文件中定义,foobar.o
:
ld -r -o foobar.o foo.o bar.o
然后:
$ gcc -o prog main.o foobar.o
foobar.o: In function `bar':
(.text+0x18): undefined reference to `do_bar'
collect2: error: ld returned 1 exit status
现在,linker 不能 link foo
的定义而不 link
bar
的定义。所以再一次,我们必须link定义do_bar
:
$ gcc -o prog main.o foobar.o do_bar.o
$ ./prog
foo
这样链接,prog
包含 foo
、bar
和 do_bar
的定义:
$ nm prog | grep -e foo -e bar
000000000000065d T bar
0000000000000669 T do_bar
000000000000064a T foo
(T
= 定义函数符号).
同样,foo
和 bar
可能定义在同一个共享库中:
$ gcc -Wall -fPIC -c foo.c bar.c
$ gcc -shared -o libfoobar.so foo.o bar.o
然后这个link年龄:
$ gcc -o prog main.o -L. -lfoobar -Wl,-rpath=$(pwd)
./libfoobar.so: undefined reference to `do_bar'
collect2: error: ld returned 1 exit status
和以前一样失败,可以用同样的方法修复:
$ gcc -o prog main.o do_bar.o -L. -lfoobar -Wl,-rpath=$(pwd)
$ ./prog
foo
当我们link共享库libfoobar.so
而不是object
文件 foobar.o
,我们的 prog
有一个不同的符号 table:
$ nm prog | grep -e foo -e bar
00000000000007aa T do_bar
U foo
这一次,prog
不包含 foo
或 bar
的定义。它
包含对 foo
的未定义引用 (U
),因为它调用 foo
,并且
当然,该引用现在将在运行时通过 libfoobar.so
中的定义得到满足。
甚至没有对 bar
的未定义引用,也不应该有,因为程序
从不调用 bar
.
但是,prog
仍然包含 do_bar
的定义 ,现在未被引用
来自符号 table.
中的所有函数
这与您自己的 SSCCE 相呼应,但方式不那么复杂。你的情况:
object 文件 libsub.a(shared2.o)
是
link 进入程序以提供 func2a
和 func2b
的定义。
必须找到这些定义并 linked 因为它们分别在 Client_func2a
的定义中被引用
和 Client_func2b
,它们在 libcshared.so
.
中定义
libcshared.so
必须 linked 以提供 Client_func1a
.
的定义
必须找到 Client_func1a
的定义并 linked 因为它是
引用自 func1a
.
的定义
而func1a
被main
调用。
这就是我们看到的原因:
$ nm main | grep func2
U Client_func2a
U Client_func2b
00000000004009f7 T func2a
0000000000400a30 T func2b
在您程序的符号 table 中。
将定义 link 编辑到程序中并不罕见
它不调用的函数。它通常以我们所见的方式发生:link年龄,
递归解析以 main
开头的符号引用,发现它需要一个定义
f
,它只能通过 linking 一些 object 文件 file.o
和 file.o
获得
它还 link 是函数 g
的定义,它永远不会被调用。
相当奇怪的是最终得到一个像你的 main
和我上一个版本的 prog
一样的程序,
其中包含未调用函数的定义(例如 do_bar
),该函数被 linked 解析
从程序中定义的另一个未调用函数(例如 bar
)的定义中引用 而不是 。
即使有多余的函数定义,通常我们也可以将它们链接回一个或多个
object link 时代的文件,其中 第一个 冗余定义与
一些必要的定义。
这种奇怪的情况是在这样的情况下造成的:
gcc -o prog main.o do_bar.o -L. -lfoobar -Wl,-rpath=$(pwd)
因为必须 linked (bar
) 的第一个冗余函数定义是
由 link 提供 共享库 、libfoobar.so
,而 do_bar
的定义
bar
所要求的在该共享库或任何其他共享库中是 而不是 ,
但是在 object 文件中 .
libfoobar.so
提供的 bar
的定义将在
程序是 link 使用该共享库编辑的。它不会被物理 link 编辑到
程序。这就是动态 link 年龄的本质。但是任何 object 文件需要
link年龄 - 无论是 free-standing object 文件,如 do_bar.o
还是一个
linker 从像 libsub.a(shared2.o)
这样的档案中提取 - 只能是
link身体力行进入程序。所以多余的 do_bar
出现在
prog
的符号 table。但是多余的 bar
,这解释了为什么 do_bar
在那里,
不在。它在libfoobar.so
的符号table中。
当您发现程序中的死代码时,您可能希望 link 工程师变得更聪明。
通常,它 可以 更聪明,但要付出一些额外的努力。你需要问garbage-collect sections,
在此之前,您需要让编译器通过生成 data-sections 和
function-sections 在 object 文件中。参见 How to remove unused C/C++ symbols with GCC and ld?,以及
the answer
但是这种修剪死代码的方法在异常情况下不起作用
在程序中 link 编辑死代码以满足来自 共享库 的冗余引用
link年龄要求。 linker 只能递归地 garbage-collect 来自
它输出到程序中的那些,它只输出输入的部分
来自 object 文件,而不是来自要动态 linked.
的共享库
避免你的 main
和我的 prog
中的死代码的正确方法是 不要做那种奇怪的 linkage 其中
共享库将包含程序不调用但必须调用的未定义引用
通过 linking dead object 代码到你的 program.
解决
相反,当您构建共享库时,要么不要在其中留下任何未定义的引用,要么
或者只留下未定义的引用,这些引用将由其自身的动态依赖项满足。
因此,构建我的 libfoobar.so
的正确方法是:
$ gcc -shared -o libfoobar.so foo.o bar.o do_bar.o
这为我提供了一个 API 的共享库:
void foo(void);
void bar(void);
对于想要其中一个或两个的人,并且没有未定义的引用。然后
我构建的程序是 foo
:
的客户端
$ gcc -o prog main.o -L. -lfoobar -Wl,-rpath=$(pwd)
$ ./prog
foo
并且它不包含死代码:
$ nm prog | grep -e foo -e bar
U foo
同样,如果您构建 libshared.so
时没有未定义的引用,例如:
$ gcc -c -fPIC shared2.c shared1.c
$ ar -crs libsub.a shared1.o shared2.o
$ gcc -shared -o libcshared.so cshared1.o cshared2.o -L. -lsub
然后 link 你的程序:
$ gcc -o main main.c libcmain.so libcshared.so
它也没有死代码:
$ nm main | grep func
U func1a
如果您不喜欢 libsub.a(shared1.o)
和 libsub.a(shared2.o)
通过这个解决方案在身体上 link 进入 libcshared.so
,然后采取
other linking 共享库的正统方法:在 libcshared.so
中保留所有 func*
函数未定义: make libsub
还有
一个共享库,它是 libcshared.so
的动态依赖。
如果您只是想摆脱未使用的功能,则可能不需要使用共享库。对于 GCC,请尝试 this. For XL, replace -fdata-sections -ffunction-sections
with -qfuncsect
. An important related topic is the use of export/import lists and visibility options. These control whether extra symbols linked into your library are exported outside your library or not. See here 了解更多信息。
总结:
STATIC 和 SHARED lib 之间的交叉使用函数导致 STATIC lib 的所有对象(甚至未使用!)都包含在最终二进制文件中!
我想你不明白我的意思吧? :-p
坐下来阅读下面的完整故事! 名称已更改以保护无辜者。示例的 tar 简单且可重现。
预告片:有 SSCCE 可用! (简短、自包含、正确(可编译),示例:http://www.sscce.org/)
一开始,我有:
二进制文件 (
main
) 调用存储在静态库 (libsub.a
) 中的函数 (fun1a()
)。main
也有内部函数 (mainsub()
).包含 SEVERAL 个对象的静态库 (
libsub.a
),每个对象都有其他来源使用的多个函数。
编译 main
生成的二进制文件 只有 包含引用函数的对象(静态库)的副本。
在下面的示例中,main
将仅包含对象 shared1.o
的函数(因为 main 正在调用 func1a()
)和 NOT [=41= 的函数](因为没有引用)。
好的!
main.c libsub.a
+-------------+ +------------+
| main | | shared1.o |
| func1a() | <----> | func1a() |
| mainsub() | | func1b() |
+-------------+ | ---- |
| shared2.o |
| func2a() |
| func2b() |
+------------+
作为一项改进,我想让'external'人们能够用他们自己的代码覆盖在main
中调用的函数,而不必重新编译 MY 二进制文件。
无论如何我都没有提供源代码,也没有提供我的静态库。
为此,我打算提供一个 "ready to fill" 函数框架源。 (这就是所谓的用户退出?!)
使用 SHARED / DYNAMIC lib 可以做到这一点恕我直言。
可以被覆盖的函数,要么是主函数内部的(mainsub()
),要么是共享函数(func1a()
...),并且将存储在共享库(.so)中成为 added/referenced在 link.
创建了新源,前缀为 'c',它将包含 'Client' 版本的 'standard' 函数。
使用(或不)覆盖函数的开关超出范围。照原样,如果UE
为真,则覆盖。
cmain.c
是一个包含 Client_mainsub()
的新来源,可以称为 mainsub()
cshared1.c
是一个包含 Client_func1a()
的新来源,可以称为 func1a()
的 'in replacement'。 实际上 shared1.c
中的所有函数都可以在 cshared1.c
cshared2.c
是一个包含 Client_func2a()
的新来源,可以称为 func2a()
概览变为:
main.c libsub.a clibsub.so
+-----------------------+ +------------------------+ +--------------------+
| main | | shared1.o | | cshared1.o |
| func1a() {} | | func1a() | | Client_func1a() |
| mainsub() | <-> | { if UE | <-> | {do ur stuff } |
| { if UE | | Client_func1a() | | |
| Client_mainsub() | | return } | | cshared2.o |
| return }| | func1b() | | Client_func2a() |
+-----------------------+ | ------- | >| {do ur stuff } |
^ | shared2.o | / +--------------------+
cmain.c v | func2a() | /
+--------------------+ | { if UE | /
| cmain | | Client_func2a() |<
| Client_mainsub() | | return } |
| {do ur stuff } | | func2b() |
+--------------------+ +------------------------+
再次强调,由于 main
不调用 func2a()
也不调用 func2b()
,(静态)对象 shared2.o 不包含在二进制文件中,也没有引用(共享)Client_func2a()
也存在。
好的!
最后,简单地覆盖函数是不够的(或者太多了!)。 我希望外部人员能够调用我的函数(或不能)...但是 ALSO 允许他们做一些正确的事情 BEFORE and/or 对 AFTER 我的功能。
因此,与其将 func2a()
愚蠢地替换为 Client_func2a()
,不如使用伪代码:
shared2.c | cshared2.c
(assume UE=true)
|
func2a() { |Client_func2a() {
if UE {} |
Client_func2a() ==> do (or not) some stuf PRE call
|
| if (DOIT) { // activate or not standard call
| UE=false
| func2a() // do standard stuff
| UE=true
| } else
| { do ur bespoke stuff }
|
| do (or not) some stuf POST call
| }
<==
} else
{ do standard stuff }
}
请记住,cshared2.c
提供给可以(或不能)在提供的框架上做自己的事情的其他人。
(注意:在 Client_func2a()
中将 UE
设置为 false 并返回 true 避免了 func2a()
调用中的无限循环!;-))
我的问题来了。
在那种情况下,结果二进制文件现在包含 shared2.o
对象,尽管 NO 在 main 中对 shared2.c
或 [=54 的任何函数进行了调用=] !!!!!
搜索后这看起来是因为交叉 calls/reference :
shared2.o contains func2a() that may call Client_func2a()
cshared2.o contains Client_func2a() that may call func2a()
那么为什么 main
二进制文件包含 shared2.o ?
>dump -Tv main
main:
***Loader Section***
***Loader Symbol Table Information***
[Index] Value Scn IMEX Sclass Type IMPid Name
[0] 0x00000000 undef IMP RW EXTref libc.a(shr_64.o) errno
[1] 0x00000000 undef IMP DS EXTref libc.a(shr_64.o) __mod_init
[2] 0x00000000 undef IMP DS EXTref libc.a(shr_64.o) exit
[3] 0x00000000 undef IMP DS EXTref libc.a(shr_64.o) printf
[4] 0x00000000 undef IMP RW EXTref libc.a(shr_64.o) __n_pthreads
[5] 0x00000000 undef IMP RW EXTref libc.a(shr_64.o) __crt0v
[6] 0x00000000 undef IMP RW EXTref libc.a(shr_64.o) __malloc_user_defined_name
[7] 0x00000000 undef IMP DS EXTref libcmain.so Client_mainsub1
[8] 0x00000000 undef IMP DS EXTref libcshared.so Client_func1b
[9] 0x00000000 undef IMP DS EXTref libcshared.so Client_func1a
[10] 0x00000000 undef IMP DS EXTref libcshared.so Client_func2b <<< but why ??? ok bcoz func2b() is referenced ...
[11] 0x00000000 undef IMP DS EXTref libcshared.so Client_func2a <<< but why ??? ok bcoz func2a() is referenced ...
[12] 0x110000b50 .data ENTpt DS SECdef [noIMid] __start
[13] 0x110000b78 .data EXP DS SECdef [noIMid] func1a
[14] 0x110000b90 .data EXP DS SECdef [noIMid] func1b
[15] 0x110000ba8 .data EXP DS SECdef [noIMid] func2b <<< but why this ? Not a single call is made in main ???
[16] 0x110000bc0 .data EXP DS SECdef [noIMid] func2a <<< but why this ? Not a single call is made in main ???
请注意,只需添加评论 func2a()
(和 func2b()
)即可解决 link 问题(打叉)...但这是不可能的,因为我想保留共享库 !?
在 AIX 7.1 和 IBM XL C/C++ 12.1 上发生了这种行为,但在 Linux 上看起来是一样的(Red Hat 5 + GCC 5.4 在编译中有一些小的变化参数)
IBM XL C/C++ for AIX, V12.1 (5765-J02, 5725-C72)
Version: 12.01.0000.0000
Driver Version: 12.01(C/C++) Level: 120315
C Front End Version: 12.01(C/C++) Level: 120322
High-Level Optimizer Version: 12.01(C/C++) and 14.01(Fortran) Level: 120315
Low-Level Optimizer Version: 12.01(C/C++) and 14.01(Fortran) Level: 120321
所以我发现这肯定是个误会。谁能解释一下?
这里承诺的是SSCCE。 您可以通过 recreating/downloading 以下小文件和 运行 go.sh 重播我的问题(请参阅脚本内的注释)
Edit1:将代码添加到问题中,而不是按照建议在外部站点上添加代码
main.c
#include <stdio.h>
#include "inc.h"
extern void func1a (), func1b ();
int UEXIT(char* file, char* func)
{
printf(" UEXIT file=<%s> func=<%s>\n",file,func);
return 1; /* always true for testing */
}
main (){
printf(">>> main\n");
func1a ();
mainsub ();
printf("<<< main\n");
}
mainsub () {
printf(">>> mainsub\n");
if(UEXIT("main","mainsub")) {
Client_mainsub1();
return;
}
printf("<<< mainsub\n");
}
cmain.c
#include <stdio.h>
#include "inc.h"
void Client_mainsub1 () {
printf(">>>>>> Client_mainsub1\n");
printf("<<<<<< Client_mainsub1\n");
return;
}
inc.h
extern int UEXIT(char * fileName, char * functionName);
shared1.c
#include <stdio.h>
#include "inc.h"
void func1a (){
printf(">>>>> func1a\n");
if(UEXIT("main","func1a")) {
Client_func1a();
return;
}
printf("<<<<< func1a\n");
}
void func1b (){
printf(">>>>> func1b\n");
if(UEXIT("main","func1b")){
Client_func1b();
return;
}
printf("<<<<< func1b\n");
}
shared2.c
#include <stdio.h>
#include "inc.h"
void func2a (){
printf(">>>>> func2a\n");
if(UEXIT("main","func2a")) {
Client_func2a();
return;
}
printf("<<<<< func2a\n");
}
void func2b (){
printf(">>>>> func2b\n");
if(UEXIT("main","func2b")){
Client_func2b();
return;
}
printf("<<<<< func2b\n");
}
cshared1.c
#include <stdio.h>
#include "inc.h"
void Client_func1a () {
int standardFunctionCall = 0;
printf("\t>>>> Client_func1a\n");
if (standardFunctionCall) {
func1a();
}
printf("\t<<< Client_func1a\n");
return;
}
void Client_func1b () {
int standardFunctionCall = 0;
printf("\t>>>> Client_func1b\n");
if (standardFunctionCall) {
func1b();
}
printf("\t<<< Client_func1b\n");
return;
}
cshared2.c
#include <stdio.h>
#include "inc.h"
void Client_func2a () {
int standardFunctionCall = 0;
printf("\t>>>> Client_func2a\n");
if (standardFunctionCall) {
func2a(); /* !!!!!! comment this to avoid crossed link with shared2.c !!!!! */
}
printf("\t<<< Client_func2a\n");
return;
}
void Client_func2b () {
int standardFunctionCall = 0;
printf("\t>>>> Client_func2b\n");
if (standardFunctionCall) {
func2b(); /* !!!!!! ALSO comment this to avoid crossed link with shared2.c !!!!! */
}
printf("\t<<< Client_func2b\n");
return;
}
go.sh
#!/bin/bash
## usage :
## . ./go.sh
## so that the redefinition of LIBPATH is propagated to calling ENV ...
## otherwise : "Dependent module libcshared.so could not be loaded."
# default OBJECT_MODE to 64 bit (avoid explicitely setting -X64 options...)
export OBJECT_MODE=64
export LIBPATH=.:$LIBPATH
# Compile client functions for target binary
cc -q64 -c -o cmain.o cmain.c
# (1) Shared lib for internal function
cc -G -q64 -o libcmain.so cmain.o
# Compile common functions
cc -c shared2.c shared1.c
# Compile client common functions overwrite
cc -c cshared2.c cshared1.c
# (2) Built libsub.a for common functions (STATIC)
ar -rv libsub.a shared1.o shared2.o
# (3) Built libcshared.so for client common functions overwrite (SHARED)
cc -G -q64 -o libcshared.so cshared1.o cshared2.o
# Finally built binary using above (1) (2) (3)
# main only call func1a() , so should only include objects shared1
# But pragmatically shared2 is also included if cshared2 reference a possible call to func2() in shared2 !!!!????
# Check this with "nm main |grep shared2" or "nm main |grep func2" or "dump -Tv main |grep func2"
cc -q64 -o main main.c -bstatic libsub.a -bshared libcmain.so libcshared.so
# result is the same without specifying -bstatic or -bshared
#cc -q64 -o main2 main.c libsub.a libcmain.so libcshared.so
#If I split libcshared.so into libcshared1.so and libcshared2.so it is also the same :
#cc -G -q64 -o libcshared1.so cshared1.o
#cc -G -q64 -o libcshared2.so cshared2.o
#cc -q64 -o main4 main.c -bstatic libsub.a -bshared libcmain.so libcshared1.so libcshared2.so
#If I do not inlcude libcshared2.so, binary is of course well working, without reference to cshared2 nor shared2 .
# So why linker chooses to add STATIC shared2.o only if libcshared2.so is listed ?
# Is there a way to avoid this add of unused code ?
#cc -q64 -o main4 main.c -bstatic libsub.a -bshared libcmain.so libcshared1.so
Edit2 : 根据要求添加了 RedHat 版本的 go.sh 脚本
gored.sh
## usage :
## . ./gored.sh
## so that the redefinition of LD_LIBRARY_PATH is propagated to calling ENV ...
## otherwise : "Dependent module libcshared.so could not be loaded."
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
# Compile client functions for target binary
gcc -fPIC -c cmain.c
# (1) Shared lib for internal function
gcc -shared -o libcmain.so cmain.o
# Compile common functions
gcc -c shared2.c shared1.c
# Compile client common functions overwrite
gcc -fPIC -c cshared2.c cshared1.c
# (2) Built libsub.a for common functions (STATIC)
ar -rv libsub.a shared1.o shared2.o
# (3) Built libcshared.so for client common functions overwrite (SHARED)
gcc -shared -o libcshared.so cshared1.o cshared2.o
# Finally built binary using above (1) (2) (3)
# main only call func1a() , so should only include objects shared1
# But pragmatically shared2 is also included if cshared2 reference a possible call to func2() in shared2 !!!!????
# Check this with "nm main |grep shared2" or "nm main |grep func2" or "dump -Tv main |grep func2"
gcc -o main main.c libcmain.so libcshared.so libsub.a
#If I split libcshared.so into libcshared1.so and libcshared2.so it is also the same :
gcc -shared -o libcshared1.so cshared1.o
gcc -shared -o libcshared2.so cshared2.o
cc -o main2 main.c libcmain.so libcshared1.so libcshared2.so libsub.a
#If I do not inlcude libcshared2.so, binary is of course well working, without reference to cshared2 nor shared2 .
# So why linker chooses to add STATIC shared2.o only if libcshared2.so is listed ?
# Is there a way to avoid this add of unused code ?
cc -o main3 main.c libcmain.so libcshared1.so libsub.a
或此处将上述完整文件(不含 gored.sh)放在一个 .tar.bz2 中。 (6KB).
只是 copy/paste 在一个新文件中(例如 poc.uue
)。然后输入
uudecode poc.uue
你应该得到 poc.tar.bz2
解压缩,untar进入poc文件夹然后运行
. ./go.sh
然后
dump -Tv main
或者如果在 RedHat 下
nm main
gored.sh
之后的结果示例:
poc>nm main |grep func2
* U Client_func2a
U Client_func2b
0000000000400924 T func2a
000000000040095d T func2b
poc>nm main2 |grep func2
U Client_func2a
U Client_func2b
0000000000400934 T func2a
000000000040096d T func2b
poc>nm main3 |grep func2
poc>
Edit3:ASCII 艺术! :-)
这是未使用 objects/references 的 'visual' 最终状态 我认为包含 linker 是错误的。或者至少不够聪明,无法检测为未使用。
也许这是正常的,或者有一个选项可以避免在最终二进制文件中包含未使用的静态代码。这看起来并不复杂,因为被包围的标记 'UNUSED !?' 代码 link 没有任何内容?不是吗?
main.c libsub.a clibsub.so
+-----------------------+ +-------------------------+ +-----------------------------+
| main | | +---------------------+ | | +-------------------------+ |
| func1a(); <-------------\ | |shared1.o | | | | cshared1.o | |
| mainsub() | \------>func1a() { <-------------+ /-----> Client_func1a() { | |
| { if UE { | | | if UE { | | | / | | PRE-stuff | |
| Client_mainsub() | | | Client_func1a() <-----C---/ | | if (DOIT) { | |
| return ^ | | | return | | | | | UE=false | |
| } | | | | } else { | | +----------------> func1a() | |
| } | | | | do std stuff | | | | UE=true | |
+-------------|---------+ | | } | | | | } else { | |
| | | | | | | do bespoke stuff | |
| | | func1b() { | | | | } | |
| | | same as above | | | | POST-stuff | |
| | | } | | | | } | |
| | +---------------------+ | | | Client_func1b() {} | |
| | | | +-------------------------+ |
| ***|*******U*N*U*S*E*D**?!***|*****U*N*U*S*E*D**?!*******U*N*U*S*E*D**?!****
| * | +---------------------+ | | +-------------------------+ | *
| U | |shared2.o | | | | cshared2.o | | U
| * | | func2a() { <-------------+ /-----> Client_func2a() { | | *
| N | | if UE { | | | / | | PRE-stuff | | N
cmain.so | * | | Client_func2a())<-----C---/ | | if (DOIT) { | | *
+-------------|------+ U | | return | | | | | UE=false | | U
| cmain.o v | * | | } else { | | +----------------> func2a() | | *
| Client_mainsub() | S | | do std stuff | | | | UE=true | | S
| {do ur stuff } | * | | } | | | | } else { | | *
+--------------------+ E | | | | | | do bespoke stuff | | E
* | | func2b() { | | | | } | | *
D | | same as above | | | | POST-stuff | | D
* | | } | | | | Client_func2b() {} | | *
* | +---------------------+ | | +-------------------------+ | *
? +-------------------------+ +---------------------------+ | ?
! !
*********U*N*U*S*E*D**?!*************U*N*U*S*E*D**?!******U*N*U*S*E*D**?!***
欢迎任何让我走上正确道路的建设性答案。
谢谢。
这里是 linker 令人费解的行为的简化说明 你:
main.c
extern void foo(void);
int main(void)
{
foo();
return 0;
}
foo.c
#include <stdio.h>
void foo(void)
{
puts(__func__);
}
bar.c
#include <stdio.h>
extern void do_bar(void);
void bar(void)
{
do_bar();
}
do_bar.c
#include <stdio.h>
void do_bar(void)
{
puts(__func__);
}
让我们将所有这些源文件编译成 object 个文件:
$ gcc -Wall -c main.c foo.c bar.c do_bar.c
现在我们将尝试 link 一个程序,如下所示:
$ gcc -o prog main.o foo.o bar.o
bar.o: In function `bar':
bar.c:(.text+0x5): undefined reference to `do_bar'
未定义函数do_bar
仅在定义中被引用
bar
,并且 bar
未被引用
该程序在所有。为什么 linkage 失败?
很简单,这个 linkage 失败了,因为 我们告诉 linker link bar.o
进入程序;确实如此; bar.o
包含 bar
的定义,
其中引用了 do_bar
,它在 linkage 中没有定义。 bar
不是
被引用,但是 do_bar
是 - 由 bar
,它在程序中 linked。
默认情况下,linker 要求 linkage 中引用的任何符号
程序的定义是在 linkage 中定义的。如果我们强制它到 link 的定义
bar
,那么它将需要 do_bar
的定义,因为没有
do_bar
的定义实际上 没有 bar
的定义。如果 links
bar
的一个定义,它不质疑我们是否需要到link它,
如果答案为否,则允许对 do_bar
的未定义引用
link年龄故障当然可以通过以下方法修复:
$ gcc -o prog main.o foo.o bar.o do_bar.o
$ ./prog
foo
现在在这个例子中,linking bar.o
在程序中是完全没有必要的。我们
也可以 link 成功 而不是 告诉 linker link bar.o
.
gcc -o prog main.o foo.o
$ ./prog
foo
bar.o
和 do_bar.o
都是多余的
正在执行 main
,但程序只能 link 两者都使用,或者两者都不使用
但是假设 foo
和 bar
定义在同一个文件中?
它们可能在同一个 object 文件中定义,foobar.o
:
ld -r -o foobar.o foo.o bar.o
然后:
$ gcc -o prog main.o foobar.o
foobar.o: In function `bar':
(.text+0x18): undefined reference to `do_bar'
collect2: error: ld returned 1 exit status
现在,linker 不能 link foo
的定义而不 link
bar
的定义。所以再一次,我们必须link定义do_bar
:
$ gcc -o prog main.o foobar.o do_bar.o
$ ./prog
foo
这样链接,prog
包含 foo
、bar
和 do_bar
的定义:
$ nm prog | grep -e foo -e bar
000000000000065d T bar
0000000000000669 T do_bar
000000000000064a T foo
(T
= 定义函数符号).
同样,foo
和 bar
可能定义在同一个共享库中:
$ gcc -Wall -fPIC -c foo.c bar.c
$ gcc -shared -o libfoobar.so foo.o bar.o
然后这个link年龄:
$ gcc -o prog main.o -L. -lfoobar -Wl,-rpath=$(pwd)
./libfoobar.so: undefined reference to `do_bar'
collect2: error: ld returned 1 exit status
和以前一样失败,可以用同样的方法修复:
$ gcc -o prog main.o do_bar.o -L. -lfoobar -Wl,-rpath=$(pwd)
$ ./prog
foo
当我们link共享库libfoobar.so
而不是object
文件 foobar.o
,我们的 prog
有一个不同的符号 table:
$ nm prog | grep -e foo -e bar
00000000000007aa T do_bar
U foo
这一次,prog
不包含 foo
或 bar
的定义。它
包含对 foo
的未定义引用 (U
),因为它调用 foo
,并且
当然,该引用现在将在运行时通过 libfoobar.so
中的定义得到满足。
甚至没有对 bar
的未定义引用,也不应该有,因为程序
从不调用 bar
.
但是,prog
仍然包含 do_bar
的定义 ,现在未被引用
来自符号 table.
这与您自己的 SSCCE 相呼应,但方式不那么复杂。你的情况:
object 文件
libsub.a(shared2.o)
是 link 进入程序以提供func2a
和func2b
的定义。必须找到这些定义并 linked 因为它们分别在
Client_func2a
的定义中被引用 和Client_func2b
,它们在libcshared.so
. 中定义
libcshared.so
必须 linked 以提供Client_func1a
. 的定义
必须找到
Client_func1a
的定义并 linked 因为它是 引用自func1a
. 的定义
而
func1a
被main
调用。
这就是我们看到的原因:
$ nm main | grep func2
U Client_func2a
U Client_func2b
00000000004009f7 T func2a
0000000000400a30 T func2b
在您程序的符号 table 中。
将定义 link 编辑到程序中并不罕见
它不调用的函数。它通常以我们所见的方式发生:link年龄,
递归解析以 main
开头的符号引用,发现它需要一个定义
f
,它只能通过 linking 一些 object 文件 file.o
和 file.o
获得
它还 link 是函数 g
的定义,它永远不会被调用。
相当奇怪的是最终得到一个像你的 main
和我上一个版本的 prog
一样的程序,
其中包含未调用函数的定义(例如 do_bar
),该函数被 linked 解析
从程序中定义的另一个未调用函数(例如 bar
)的定义中引用 而不是 。
即使有多余的函数定义,通常我们也可以将它们链接回一个或多个
object link 时代的文件,其中 第一个 冗余定义与
一些必要的定义。
这种奇怪的情况是在这样的情况下造成的:
gcc -o prog main.o do_bar.o -L. -lfoobar -Wl,-rpath=$(pwd)
因为必须 linked (bar
) 的第一个冗余函数定义是
由 link 提供 共享库 、libfoobar.so
,而 do_bar
的定义
bar
所要求的在该共享库或任何其他共享库中是 而不是 ,
但是在 object 文件中 .
libfoobar.so
提供的 bar
的定义将在
程序是 link 使用该共享库编辑的。它不会被物理 link 编辑到
程序。这就是动态 link 年龄的本质。但是任何 object 文件需要
link年龄 - 无论是 free-standing object 文件,如 do_bar.o
还是一个
linker 从像 libsub.a(shared2.o)
这样的档案中提取 - 只能是
link身体力行进入程序。所以多余的 do_bar
出现在
prog
的符号 table。但是多余的 bar
,这解释了为什么 do_bar
在那里,
不在。它在libfoobar.so
的符号table中。
当您发现程序中的死代码时,您可能希望 link 工程师变得更聪明。 通常,它 可以 更聪明,但要付出一些额外的努力。你需要问garbage-collect sections, 在此之前,您需要让编译器通过生成 data-sections 和 function-sections 在 object 文件中。参见 How to remove unused C/C++ symbols with GCC and ld?,以及 the answer
但是这种修剪死代码的方法在异常情况下不起作用 在程序中 link 编辑死代码以满足来自 共享库 的冗余引用 link年龄要求。 linker 只能递归地 garbage-collect 来自 它输出到程序中的那些,它只输出输入的部分 来自 object 文件,而不是来自要动态 linked.
的共享库避免你的 main
和我的 prog
中的死代码的正确方法是 不要做那种奇怪的 linkage 其中
共享库将包含程序不调用但必须调用的未定义引用
通过 linking dead object 代码到你的 program.
相反,当您构建共享库时,要么不要在其中留下任何未定义的引用,要么 或者只留下未定义的引用,这些引用将由其自身的动态依赖项满足。
因此,构建我的 libfoobar.so
的正确方法是:
$ gcc -shared -o libfoobar.so foo.o bar.o do_bar.o
这为我提供了一个 API 的共享库:
void foo(void);
void bar(void);
对于想要其中一个或两个的人,并且没有未定义的引用。然后
我构建的程序是 foo
:
$ gcc -o prog main.o -L. -lfoobar -Wl,-rpath=$(pwd)
$ ./prog
foo
并且它不包含死代码:
$ nm prog | grep -e foo -e bar
U foo
同样,如果您构建 libshared.so
时没有未定义的引用,例如:
$ gcc -c -fPIC shared2.c shared1.c
$ ar -crs libsub.a shared1.o shared2.o
$ gcc -shared -o libcshared.so cshared1.o cshared2.o -L. -lsub
然后 link 你的程序:
$ gcc -o main main.c libcmain.so libcshared.so
它也没有死代码:
$ nm main | grep func
U func1a
如果您不喜欢 libsub.a(shared1.o)
和 libsub.a(shared2.o)
通过这个解决方案在身体上 link 进入 libcshared.so
,然后采取
other linking 共享库的正统方法:在 libcshared.so
中保留所有 func*
函数未定义: make libsub
还有
一个共享库,它是 libcshared.so
的动态依赖。
如果您只是想摆脱未使用的功能,则可能不需要使用共享库。对于 GCC,请尝试 this. For XL, replace -fdata-sections -ffunction-sections
with -qfuncsect
. An important related topic is the use of export/import lists and visibility options. These control whether extra symbols linked into your library are exported outside your library or not. See here 了解更多信息。