工作中遇到的编译情况【下】

接上一篇

隐藏部分符号使之不可被外部引用

创建文件 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.solibe.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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 动态链接,在可执行文件装载时或运行时,由操作系统的装载程序加载库。大多数操作系统将解析外部引用(比如库)作为加载过...
    小5筒阅读 5,566评论 0 3
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,883评论 0 27
  • 背景 目前后台服务器都是由bin+so的方式构成,ServerFrame(bin)提供网络通信,内存管理,配置管理...
    laughxing阅读 2,634评论 1 4
  • 转自http://blog.csdn.net/navyhu/article/details/47023317理解链...
    扎Zn了老Fe阅读 1,480评论 0 0
  • 1. 介绍 使用GNU的工具我们如何在Linux下创建自己的程序函数库?一个“程序函数库”简单的说就是一个文件包含...
    逍遥_9353阅读 1,587评论 0 2