控制目标文件符号可见性

控制符号可见性

实验环境

系统: 16.04.1-Ubuntu
编译器:gnu 5.4.0

参考

第 1 部分 - 符号可见性简介

nm工具使用

使用NM查看目标文件的符号列表

// file : symtest.hpp
class SymTest
{
    SymTest();
    SymTest(int x);
    ~SymTest();
    void foo();
};

// file : symtest.cc
#include "symtest.hpp"
SymTest::SymTest()       {}
SymTest::SymTest( int x) {}
SymTest::~SymTest()      {}
void SymTest::foo()      {}

> g++ -g -shared -o libsymtest.so symtest.cc // 编译动态库
> g++ -g -c symtest.cc -o libsymtest.o; ar rvs libsymtest.a libsymtest.o // 编译静态库
  • so 编译时,加上 -g 编译信息
  • -g 仅显示外部符号 -C 显示用户级名字
$ nm -g libsymtest.a 

libsymtest.o:
0000000000000026 T _ZN7SymTest3fooEv
000000000000000c T _ZN7SymTestC1Ei
0000000000000000 T _ZN7SymTestC1Ev
000000000000000c T _ZN7SymTestC2Ei
0000000000000000 T _ZN7SymTestC2Ev
000000000000001a T _ZN7SymTestD1Ev
000000000000001a T _ZN7SymTestD2Ev
$ nm -C libsymtest.a 

libsymtest.o:
0000000000000026 T SymTest::foo()
000000000000000c T SymTest::SymTest(int)
0000000000000000 T SymTest::SymTest()
000000000000000c T SymTest::SymTest(int)
0000000000000000 T SymTest::SymTest()
000000000000001a T SymTest::~SymTest()
000000000000001a T SymTest::~SymTest()

ps. 构造函数和系够函数会出现两次,见使用NM查看目标文件的符号列表

  • -A 符号前显示二进制文件名称
$ nm -C -A libsymtest.a

libsymtest.a:libsymtest.o:0000000000000026 T SymTest::foo()
libsymtest.a:libsymtest.o:000000000000000c T SymTest::SymTest(int)
libsymtest.a:libsymtest.o:0000000000000000 T SymTest::SymTest()
libsymtest.a:libsymtest.o:000000000000000c T SymTest::SymTest(int)
libsymtest.a:libsymtest.o:0000000000000000 T SymTest::SymTest()
libsymtest.a:libsymtest.o:000000000000001a T SymTest::~SymTest()
libsymtest.a:libsymtest.o:000000000000001a T SymTest::~SymTest()
  • -l 显示行号,需要配合 -g 调试选项使用
$ nm -C -A -l libsymtest.a
 
libsymtest.a:libsymtest.o:0000000000000026 T SymTest::foo()
libsymtest.a:libsymtest.o:000000000000000c T SymTest::SymTest(int)  /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/symtest.cc:4
libsymtest.a:libsymtest.o:0000000000000000 T SymTest::SymTest() /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/symtest.cc:3
libsymtest.a:libsymtest.o:000000000000000c T SymTest::SymTest(int)
libsymtest.a:libsymtest.o:0000000000000000 T SymTest::SymTest()
libsymtest.a:libsymtest.o:000000000000001a T SymTest::~SymTest()    /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/symtest.cc:5
libsymtest.a:libsymtest.o:000000000000001a T SymTest::~SymTest()

符号类型

符号类型一列有两个字母时,小写字母代表局部符号,大写则为全局/外部符号。

‘A’ - 符号的值为连接期间不能改变
‘B b’ - BSS 段, 存放未初始化的数据
‘C’ - comm symbols ?
'D d' - 初始化的数据段
‘G g’ - 存放小对象的初始化数据段,某些目标文件格式支持小对象的快速访问,比如全局的 int 类型可以存放在此处,而大型全局数组不能存放在这里
'i' - 看不懂 ?
"N" - 调试相关的符号
"p" - 该符号在堆栈展开部分
"R r" - 常量,只读数据段
"S s" - 存放小对象的未初始化数据段
"T t" - text段,代码段
"U" - 未定义符号
"u" - 独占的全局符号,GNU对ELF标准的拓展,链接过程需要保证该符号是独占的
"V v" - 看不懂?
"W w" - 看不懂?
"-" - a.out 文件中的调试信息相关
"?" - 看不懂?

符号及符号可见性是什么?

符号概念与对象文件(.o)、链接等概念相关,对于 C/C++ 语言,用户定义的变量、函数名称、及命名空间、类/结构/名称等,都会在对象文件中生成符号,这些符号对于链接器(linker)确定不同模块(对象文件、动态共享库、可执行文件)是否会共享相同的数据或代码很有用。 ps. 水平有限,此处只关注函数的符号可见性

举例

nm命令中符号类型详解

// file : demo.cc
// description : demo symbol of elements in C

int a1;
int a2 = 1;
const int a3 = 1;
static int sa = 1;
static int funA() {return 1;}
int funB() {return 2;}


$ g++ -g -c demo.cc 
$ nm -C -A -l demo.o 
demo.o:0000000000000000 B global BBS段-未初始化变量   a1   /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:1
demo.o:0000000000000000 D global 数据段-初始化变量    a2    /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:5
demo.o:000000000000000b T global 代码段             funB() /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:6
demo.o:0000000000000000 r local  常量               a3    /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:5
demo.o:0000000000000004 d local  数据段-初始化变量    sa    /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:5
demo.o:0000000000000000 t local  代码段             funA() /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:5


> readelf -s demo.o
Symbol table '.symtab' contains 20 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     6: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 _ZL2a3
     7: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    2 _ZL2sa
     8: 0000000000000000    11 FUNC    LOCAL  DEFAULT    1 _ZL4funAv
    17: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 a1
    18: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    2 a2
    19: 000000000000000b    11 FUNC    GLOBAL DEFAULT    1 _Z4funBv

为什么控制符号可见性?

编译器默认导出所有的符号,存在很高的风险,链接时可能会导致符号冲突,只要有人链接一个跟您的库具有相同符号名称的库,当进行链接器解析时,该库就可能会意外地覆盖您自己的符号。

导出所有的符号,会增加动态库的加载和链接时间。

举例

> g++ -c demo.cc                          // 生成目标文件 demo.o
> cp demo.cc demo1.cc                     // 此时 demo1.cc 同样具有符号 a、sa、funA 和 funB
> g++ -c demo1.cc                         // 生成目标文件 demo1.o
> g++ -shared -o demo.so demo.o demo1.o   // 链接生成动态库 demo.so, 产生符号冲突
demo1.o:(.data+0x0): `a'被多次定义
demo.o:(.data+0x0):第一次在此定义
demo1.o:在函数‘funB()’中:
demo1.cc:(.text+0xb): `funB()'被多次定义
demo.o:demo.cc:(.text+0xb):第一次在此定义
collect2: error: ld returned 1 exit status

题外话 - 头文件中放置函数的定义引起符号冲突

// file: demo1.hpp
#ifndef __demo1_hpp_
#define __demo1_hpp_
int test1() { return 1; }
#endif//#ifndef __demo1_hpp_

// file: demo2.cc
#include "demo1.hpp"
int test2()
{ return test1(); }

// file: demo3.cc
#include "demo1.hpp"
int test3()
{ return test1(); }

> g++ -fPIC -shared -o demo.so demo2.cc demo3.cc
/tmp/cchKMCYz.o:在函数‘test1()’中:
demo3.cc:(.text+0x0): `test1()'被多次定义
/tmp/ccLp8ynr.o:demo2.cc:(.text+0x0):第一次在此定义
collect2: error: ld returned 1 exit status

// 1, demo1.hpp 中的 `#ifndef ... #define ...#endif` 不能阻止此类符号冲突
// 2, 将 demo1.hpp 中的函数 test1 改为 inline  可以修复该编译错误
// 3, 也可以将 demo1.hpp 中的函数 test1 改为 static [inline], 会生成两个版本的 test1  

控制符号可见性的方式

1, static 关键字

如上所示 demo.cc 中, static 改变可见性

2, (仅针对gnu)visibility属性

// file : demo.cc
// description : demo visibility change the visiable of symbol

#if defined(__GNUC__) && ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3))
#define NP_VISIBILITY_DEFAULT __attribute__((visibility("default")))
#else
#define NP_VISIBILITY_DEFAULT
#endif

#define NP_EXPORT(__type) NP_VISIBILITY_DEFAULT __type

int a1; 
int a2 = 1;
const int a3 = 1;
int sa = 1;
int funA() {return 1;} 
NP_EXPORT(int) funB() {return 2;} 

$ g++ -g -shared -o libdemo.so -fvisibility=hidden demo.cc 
$ nm -C -a -l libdemo.so 

000000000020102c b a1   /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:9
0000000000201020 d a2   /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:10
0000000000201024 d sa   /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:12
0000000000000600 t funA()   /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:13
000000000000060b T funB()   /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:14
0000000000000624 r a3

3, 使用导出列表

// file : demo.cc
// description : demo version-script change the visiable of symbol

int a = 1;
int sa = 1;
int funA() {return 1;}
int funB() {return 2;}

/*
// file : exportmap
// description : define which is global or local
{
global:
a;         // var a is global, D
_Z4funAv;  // funA is global, T
local: *;  // default is local
};
*/

> g++ -shared -o demo.so  demo.cc -fPIC -Wl,--version-script=exportmap 
> nm demo.so
0000000000201020 D a
0000000000201024 d sa
0000000000000570 T _Z4funAv
000000000000057b t _Z4funBv

cpp 文件

// file : symtest.hpp
#if defined(__GNUC__) && ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3))
#define NP_EXPORT __attribute__((visibility("default")))
#else
#define NP_EXPORT
#endif

class SymTest
{
    SymTest() ;
    NP_EXPORT SymTest(int x); 
    ~SymTest();
    void foo();
};

// file : symtest.cc
#include "symtest.hpp"
SymTest::SymTest()       {}
SymTest::SymTest( int x) {}
SymTest::~SymTest()      {}
void SymTest::foo()      {}

$ g++ -g -fvisibility=hidden -shared -o libsymtest.so symtest.cc
$ nm -C -A -l libsymtest.so 

libsymtest.so:0000000000000656 t SymTest::foo() /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/symtest.cc:6
libsymtest.so:000000000000063c T SymTest::SymTest(int)  /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/symtest.cc:4
libsymtest.so:0000000000000630 t SymTest::SymTest() /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/symtest.cc:3
libsymtest.so:000000000000063c T SymTest::SymTest(int)
libsymtest.so:0000000000000630 t SymTest::SymTest()
libsymtest.so:000000000000064a t SymTest::~SymTest()    /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/symtest.cc:5
libsymtest.so:000000000000064a t SymTest::~SymTest()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容