控制符号可见性
实验环境
系统: 16.04.1-Ubuntu
编译器:gnu 5.4.0
参考
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. 水平有限,此处只关注函数的符号可见性
举例
// 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()