接上一篇
隐藏部分符号使之不可被外部引用
创建文件 c.c
,c.h
#include <stdio.h>
void testC();
#include "c.h"
void testInnerC(){
printf("hello Inner C\n");
}
void testC(){
printf("hello C\n");
}
执行编译
$ gcc -shared -fPIC -I. c.c -o libc.so
$ objdump -Tt libc.so |grep test
00000000000006b3 g F .text 0000000000000013 testC
00000000000006a0 g F .text 0000000000000013 testInnerC
00000000000006b3 g DF .text 0000000000000013 Base testC
00000000000006a0 g DF .text 0000000000000013 Base testInnerC
我们需要的是把testInnerC符号隐藏掉,需要怎么做呢?
如果可以的话,你可以想想。如果你已经知道了,那么就直接跳过吧。
废话不多说,直接给答案。
在c.c的文件中 ,增加关键字 __attribute__((visibility("default")))
,就像这样
//c.c
__attribute__((visibility("default"))) void testC(){
printf("hello C\n");
}
然后编译参数也要变化
$ gcc -shared -fPIC -I. c.c -o libc.so -fvisibility=hidden
$ objdump -Tt libc.so |grep test
0000000000000670 l F .text 0000000000000013 testInnerC
0000000000000683 g F .text 0000000000000013 testC
0000000000000683 g DF .text 0000000000000013 Base testC
也许你会说:这不是还看得见符号吗?
上面的步骤,我们已经隐藏了定义,那我们再加去掉符号
$ strip libc.so
$ objdump -Tt libc.so |grep test
0000000000000683 g DF .text 0000000000000013 Base testC
可以看到符号和需要隐藏的定义都不见了,这个时候我们就可以给到别人使用了
我们可以连接直接我们连接过的库了。
我们再 main.c
里面增加 testC
的调用
$ gcc -c -I. a.c -o a.o
$ ar -cr liba.a a.o
$ gcc -shared -o libb.so -I. b.c
$ gcc -shared -o libc.so -I. c.c -fvisibility=hidden
$ gcc -I. -o main.exe main.c -L. -Wl,-Bstatic -la -Wl,-Bdynamic -lb -lc
/usr/bin/ld: /tmp/ccXEuLPw.o: undefined reference to symbol 'puts@@GLIBC_2.2.5'
//lib/x86_64-linux-gnu/libc.so.6: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status
这里却出现了异常经过分析是由于和系统的 libc.so
重名了,我们换个名字就可以 了
$ mv libc.so libmtc.so
$ gcc -I. -o main.exe main.c -L. -Wl,-Bstatic -la -Wl,-Bdynamic -lb -lmtc
$ ./main.exe
hello main
hello A
hello B
hello C
现在让我们调用 libmtc.so
里面的 testInnerC
需要修改下 c.h
#include <stdio.h>
void testC();
void testInnerC();
执行编译
$ gcc -I. -o main.exe main.c -L. -Wl,-Bstatic -la -Wl,-Bdynamic -lb -lmtc
/tmp/ccnG2ec3.o:在函数‘main’中:
main.c:(.text+0x3f):对‘testInnerC’未定义的引用
collect2: error: ld returned 1 exit status
在这里,你有发现为什么这里会有问题吗?
如果在这里让你想起了你之前项目遇到过的未定义的引用,那这就是我的荣幸。如果没有,那我们一起收获。
我们知道在 libmtc.so
里面是没有这个定义的,怎么知道,上面说到使用 objdump
来进行查看
$ objdump -Tt libmtc.so |grep test
0000000000000670 l F .text 0000000000000013 testInnerC
0000000000000683 g F .text 0000000000000013 testC
0000000000000683 g DF .text 0000000000000013 Base testC
如果你要调用则需要重新编译 libmtc.so
这个库,不加-fvisibility=hidden
即可通过编译
也许你会说,“这是因为你之前自己把它隐藏了的”,
那我们在写一个例子。
需求:如何在编译期就发现符号未定义
我们先来个正常的发现不了未定义的。
创建 d.c
,d.h
#include <stdio.h>
void testD();
void testUD();
#include "d.h"
void testD(){
printf("hello D\n");
}
在增加 e.c
,e.h
#include "d.h"
#include "e.h"
void testE(){
testD();
printf("hello E\n");
}
#include <stdio.h>
void testE();
修改main.c
#include "a.h"
#include "b.h"
#include "c.h"
#include "e.h"
int main(int argc,char* argv[])
{
printf("hello main\n");
testA();
testB();
testC();
//testInnerC();
testE();
return 0;
}
开始编译出 libd.so
和libe.so
$ gcc -shared -fPIC d.c -I. -o libd.so
$ gcc -shared -fPIC e.c -I. -o libe.so
$ gcc -I. -o main.exe main.c -L. -Wl,-Bstatic -la -Wl,-Bdynamic -lb -lmtc -ld -le
$ ./main.exe
hello main
hello A
hello B
hello C
hello D
hello E
到这里,都是正常的输出
现在让我们修改一下文件
#include "d.h"
void testD(){
printf("hello D\n");
testUD(); //发现这里做了修改,调用了一个没有定义的方法
}
开始编译,现打出静态库 libd.a
再将静态库达成动态库,再使用动态库libe.so
进行链接
$ gcc -c d.c -I. -o d.o
$ ar -cr libd.a d.o
$ gcc -shared -fPIC e.c -I. -L. -Wl,--whole-archive -Wl,-Bstatic -ld -Wl,-Bdynamic -Wl,-no-whole-archive -o libe.so
$ gcc -I. -o main.exe main.c -L. -Wl,-Bstatic -la -le -Wl,-Bdynamic -lb -lmtc -le
./libe.so:对‘testUD’未定义的引用
collect2: error: ld returned 1 exit status
此时可以发现出现‘testUD’未定义的引用
的问题
也许你跟着上面的步骤,知道是我有个方法未定义,并且是在libd.a 里面就有问题了。
但是,如果只是给你一个库,不管静态库动态库,你只有再链接成可执行文件的时候才出现问题,那会导致你不知道从哪里下手。在大项目中更是如此,有可能会耗费你一天的精力,去发现哪里出现了问题。
那么怎么发现哪里有问题呢?需要在链接的时候增加一个参数 z,defs
$ gcc -shared -fPIC e.c -I. -L. -Wl,--whole-archive -Wl,-Bstatic -ld -Wl,-Bdynamic -Wl,-no-whole-archive -o libe.so -Wl,-z,defs
./libd.a(d.o):在函数‘testD’中:
d.c:(.text+0x16):对‘testUD’未定义的引用
collect2: error: ld returned 1 exit status
这样你就可以清晰的看出那个库里面的,哪个方法是有问题的。
这里建议所有的c/c++ 程序员都加上这个参数 -z,defs
在链接的时候,可以减少很多问题的!!!
需求:如何将一个静态库,打包成为一个动态库
还是使用之前的liba.a
现在我只有这个库,没有这个库的源码,现在想要吧liba.a 变成liba.so 怎么做呢?
我们知道链接的时候,优先链接动态库,如果动态库找不到就会去链接静态库
$ gcc -shared -L. -ld -o libtd.so
$ objdump -Tt libtd.so |grep test
# 会发现没有输出
那么怎么做呢?
如果你想自己想一下的话,先不要看我的实现方案,或者你可以想想有没有其他的实现方案。
$ gcc -c -o d.o d.c
$ ar -cr libd.a d.o
$ gcc -shared -L. -Wl,--whole-archive -ld -Wl,--no-whole-archive -o libtd.so
$ objdump -Tt libtd.so |grep test
#还是没有输出,
如果你有输出,那么恭喜你,你成功了,为什么我会失败?因为我的环境存在了同名的静态库和动态库
$ ls
a.c a.o b.h c.h d.h e.c liba.a libd.a libmtc.so main.c
a.h b.c c.c d.c d.o e.h libb.so libd.so libtd.so
#我们需要强制指定使用静态库,或者删除动态库
$ gcc -shared -L. -Wl,--whole-archive -Wl,-Bstatic -ld -Wl,-Bdynamic -Wl,--no-whole-archive -o libtd.so
$ nm -D libtd.so |grep test
0000000000000670 T testD
小结
几个重要的链接,编译参数
-Wl,-z,defs
-Wl,--whole-archive
和 -Wl,--no-whole-archive
-Wl,-Bstatic
和 -Wl,-Bdynamic