转自:https://blog.csdn.net/whb_fei/article/details/76974543

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/whb_fei/article/details/76974543

GCC制作动态链接库时默认会将所有的函数及变量都导出到符号表,这里的函数及变量指的是没有使用static修饰的,使用static修饰的函数及变量不会导出。正常情况下所有符号均导出是不会有问题的,但是有时会有问题,在下边的例子中会说明。为了避免这种情况,就需要定制符号表,即仅仅将需要提供给其他模块使用的接口或变量导出到符号表,本文就是介绍制作这样的动态库的方法。

一 问题示例

介绍制作方法前先举例说明其必要性,有两个动态库libhello.so及libhello1.so,对应的源文件是hello.c及hello1.c,其源码如下:

/*
*hello.c源码
*/ int hello()
{
return 1;
}
/*
*hello1.c源码
*/ int hello()
{
return 10;
}
/*
*hello.h源码
*/ int hello();

将hello.c和hello1.c分别编译成动态库:

gcc -shared -o libhello.so hello.c
gcc -shared -o libhello1.so hello1.c

测试文件源码:

#include <stdio.h>
#include "hello.h" int main()
{
printf(">>>%d\n", hello()); return 0;
}

现在编译测试文件,并且同时连接libhello.so和libhello1.so两个文件:

gcc main.c -L. -lhello -lhello1
运行结果:
>>>1

结果如下图所示:

由上图ldd查看结果可知,实际上只连接了libhello.so库,现在交换编译测试文件时连接库的顺序并运行:

gcc main.c -L. –lhello1 -lhello
运行结果:
>>>10

结果如下图所示:

由上图ldd查看结果可知,实际上只连接了libhello1.so库。
由此可知有相同函数时,调用先找到的库中的函数实体。假设项目中有两个模块以库的方式提供分别为A、B库,而两个库中均有函数D并且函数格式一样但功能不同,其中A库中的D是模块内部使用的,但是没有用static修饰,这样两个库的符号表中均有D函数符号,上层模块调用了D函数接口,编译时同时连接了A、B库,则实际调用的D函数就有编译链接时A在前还是B在前,如果B在前则不会有问题,但是如果A在前就会调用A中的D函数,功能就会出错。
这种问题遇到了就很不好查找与排除,因为并非代码问题,修改代码解决不了问题,所以才有了本文描述的制作自己需要的库,该库仅仅暴露提供给外部模块使用的接口符号表。

二 定制方法

2.1 前期准备

前期准备只是熟悉查看库的符号表方法及相关介绍。查看符号表用的命令是readelf,选项是-s,如readelf –s libhello.so,查看的结果如图所示:

(注:图比较长,并未截完整,剩下的都是.symtab的内容)
从上图可知,符号表有两部分:.dynsyn和.symtab,这里只做简单介绍,具体信息可自行查找。
.symtab和.dynsym两个不同的symbol table,.dynsym用来保存与动态链接相关的导入导出符号,而 .symtab 则保存所有符号,包括 .dynsym 中的符号,.dynsym是.symtab的一个子集。
ELF文件包含一些sections(如code和data)是在运行时需要的, 这些sections被称为allocable;而其他一些sections仅仅是linker、debugger等工具需要,在运行时并不需要,这些sections被称为non-allocable。当linker构建ELF文件时,它把allocable的数据放到一个地方,将non-allocable的数据放到其他地方,当OS加载ELF文件时,仅仅allocable的数据被映射到内存,non-allocable的数据仍静静地呆在文件里不被处理。strip就是用来移除某些non-allocable sections的,移除后不影响使用。

2.2 制作动态库

本文制作的动态库的目的是控制暴露在符号表中的符号,使用的是gcc的visibility属性,网上介绍还有其他方法,本文不做介绍。
制作动态库的源码hello.c如下:

/*
*hello.c源码
*/
int call_count = 0; int hello_init()
{
call_count = 0; return 0;
} int hello_call_count_add()
{
return ++call_count;
} int hello_handle()
{
return hello_call_count_add();
} void hello_exit()
{
call_count = 0;
}
/*
*hello.h源码
*/
#ifndef __HELLO_H
#define __HELLO_H int hello_init(); int hello_handle(); void hello_exit(); #endif

由hello.h头文件可知hello_init()、hello_handle()和hello_exit()是对外接口,而hello_call_count_add()不是。先用常规方法制作libhello.so,查看符号表,此处仅查看.dynsyn部分,.symtab部分用strip命令处理掉。

gcc -shared -o libhello.so hello.c
strip libhello.so
readelf -s libhello.so

查看结果:

由图可知,不仅所有的函数在符号表中,未用static修饰的变量call_count也在符号表中,这没有达到目的,需要将hello_call_count_add()函数和变量call_count去掉。
要达到目的,需要用gcc的visibility属性,将需要导出的符号用__attribute__ ((visibility (“default”)))修饰,并且gcc编译时需要加入-fvisibility=hidden选项。
使用visibility属性后的源码:

#define DLL_PUBLIC __attribute__ ((visibility ("default")))

int call_count = 0;

DLL_PUBLIC int hello_init()
{
call_count = 0; return 0;
} int hello_call_count_add()
{
return ++call_count;
} DLL_PUBLIC int hello_handle()
{
return hello_call_count_add();
} DLL_PUBLIC void hello_exit()
{
call_count = 0;
}

编译并用strip命令处理掉.symtab部分:

gcc -shared -fvisibility=hidden -o libhello.so hello.c
strip libhello.so
readelf -s libhello.so

结果如下:

从结果可知达到了目的,经验证制作的库可以正常使用。

2.3 整理

gcc在链接时设置-fvisibility=hidden,则不加 visibility声明的都默认为hidden,即都是隐藏的, gcc默认设置-fvisibility=default,即全部可见。

三 总结

本文只介绍了linux下gcc的编译选项,交叉编译时选项参考GNU文档How to use the new C++ visibility support部分,网址:http://gcc.gnu.org/wiki/Visibility

---------------------

本文来自 我是菜鸟_我在学 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/whb_fei/article/details/76974543?utm_source=copy

GCC制作动态库导出符号表【转】的更多相关文章

  1. C++ 动态库导出函数名“乱码”及解决

    C++ 动态库导出函数名“乱码”及解决 刚接触C++,在尝试从 dll 中导出函数时,发现导出的函数名都“乱码”了. 导出过程如下: 新建一个Win32项目: 新建的解决方案里有几个导出的示例: // ...

  2. 【图文】[新手]C++ 动态库导出函数名“乱码”及解决

    刚接触C++,在尝试从 dll 中导出函数时,发现导出的函数名都"乱码"了. 导出过程如下: 新建一个Win32项目: 新建的解决方案里有几个导出的示例: // 下列 ifdef ...

  3. linux中制作动态库

    制作一个动态库我们可以使用gcc工具来制作一个动态库示例:自己制作一个动态库,库函数的功能是传递一个字符串并输出.第一步:需要准备3个文件:hello.h.hello.c.test.c.其中hello ...

  4. C/C++编译和链接过程详解 (重定向表,导出符号表,未解决符号表)

    详解link  有 些人写C/C++(以下假定为C++)程序,对unresolved external link或者duplicated external simbol的错误信息不知所措(因为这样的错 ...

  5. (转载) C/C++编译和链接过程详解 (重定向表,导出符号表,未解决符号表)

    转载http://blog.csdn.net/neo_ustc/article/details/9024839 有 些人写C/C++(以下假定为C++)程序,对unresolved external ...

  6. 如何使用GCC生成动态库和静态库

    根据链接时期的不同,库又有静态库和动态库之分.静态库是在链接阶段被链接的,所以生成的可执行文件就不受库的影响,即使库被删除,程序依然可以成功运行.而动态库是在程序执行的时候被链接的.程序执行完,库仍需 ...

  7. gcc 编译动态库和静态库

    Linux C 编程入门之一:gcc 编译动态库和静态库 cheungmine 2012 参考: C程序编译过程浅析 http://blog.csdn.net/koudaidai/article/de ...

  8. 【C++】如何使用GCC生成动态库和静态库

    一.静态库和动态库的定义及区别 程序编译的四个过程: 1.预处理  展开头文件/宏替换/去掉注释/条件编译(.i后缀) 2.编译    检查语法,生成汇编(.s后缀) 3.汇编    汇编代码转换成机 ...

  9. Linux gcc链接动态库出错:LIBRARY_PATH和LD_LIBRARY_PATH的区别

    昨天在自己的CentOs7.1上写makefile的时候,发现在一个C程序在编译并链接一个已生成好的lib动态库的时候出错.链接命令大概是这样的: [root@typecodes tcpmsg]# g ...

随机推荐

  1. 前端学习 -- Html&Css -- 背景

    background 在一个声明中设置所有的背景属性. background-attachment 设置背景图像是否固定或者随着页面的其余部分滚动. background-color 设置元素的背景颜 ...

  2. c基础:函数参数是 struct(结构),传的是引用,还是值?

    比如函数形式:void func(struct a data1, struct b data2); 答案: 只要不是指针或者数组都是传值,其实指针也是传递的地址值. 追问但是如果这个结构体里面有数组这 ...

  3. 跟我一起使用electron搭建一个文件浏览器应用吧(四)

    在软件的世界里面,创建一个新项目很容易,但是坚持将他们开发完成并发布却并非易事.分发软件就是一个分水岭, 分水岭的一边是那些完成的被全世界用户在用的软件,而另外一边则是启动了无数项目却没有一个完成的. ...

  4. chage命令

    chage命令是用来修改帐号和密码的有效期限. 语法 chage [选项] 用户名 选项 -m:密码可更改的最小天数.为零时代表任何时候都可以更改密码. -M:密码保持有效的最大天数. -w:用户密码 ...

  5. nginx设置反向代理后端jenklins,页面上的js css文件无法加载

    转载 2017年06月14日 22:36:59 8485 问题现象: nginx配置反向代理后,网页可以正常访问,但是页面上的js css文件无法加载,页面样式乱了. (1)nginx配置如下: (2 ...

  6. pyqt4

    PyQt4 工具包简介1.1 关于本指南 这是一个入门级的 PyQt 指南.其目的在于引导读者快速上手 PyQt4 工具包.该指南在 Linux 环境下创建并通过测试. 关于 PyQt PyQt 是用 ...

  7. Linux shell 自启动脚本写法

    直接上脚本内容 #!/bin/bash #chkconfig: 2345 80 90 #description:sniffer #第一行,告诉系统使用的shell,所以的shell脚本都是这样. #第 ...

  8. CentOS下的yum upgrade和yum update区别

    说明:生产环境对软件版本和内核版本要求非常精确,别没事有事随便的进行yum update操作!! ! yum update:升级所有包同时也升级软件和系统内核 yum upgrade:只升级所有包,不 ...

  9. 函数和常用模块【day05】:不同目录间进行模块调用(八)

    本节内容 1.背景 2.函数功能解释 3.绝对路径和相对路径 4.不同目录间进行模块调用 一.背景 之前写了软件开发目录规范这篇博客,相信很多人都已经知道,我们在写程序时需要遵循一定的规范,不然,就算 ...

  10. Python复习笔记(二)变量进阶

    02. 可变和不可变类型 不可变类型,内存中的数据不允许被修改: 数字类型 int , bool , float , complex , long(2.x) 字符串 str 元组 tuple 可变类型 ...