编译符号导出

编译器符号导出规则

  • win (MSVC)默认所有的符号都是隐藏的。
  • android、iOS、MacOS(gcc/clang)默认所有的符号都是可见的。4.0以上版本的GCC可以使用 -fvisibility=hidden 设置默认符号隐藏,低版本GCC不支持符号隐藏):。

函数/类定义时导出

  • 通常可以在函数定义时,添加属性,显式导出单个函数。
  • 文件内函数使用static前缀,避免函数导出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//************************************************
// EXTERN C
//************************************************
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif

//************************************************
// EXPORT
//************************************************
#if defined _WIN32 || defined __CYGWIN__
#define FOX_HELPER_DLL_IMPORT __declspec(dllimport)
#define FOX_HELPER_DLL_EXPORT __declspec(dllexport)
#define FOX_HELPER_DLL_LOCAL
#else
#if __GNUC__ >= 4
#define FOX_HELPER_DLL_IMPORT __attribute__ ((visibility ("default")))
#define FOX_HELPER_DLL_EXPORT __attribute__ ((visibility ("default")))
#define FOX_HELPER_DLL_LOCAL __attribute__ ((visibility ("hidden")))
#else
#define FOX_HELPER_DLL_IMPORT
#define FOX_HELPER_DLL_EXPORT
#define FOX_HELPER_DLL_LOCAL
#endif
#endif


#if defined(_WIN32) || defined(_WIN64) || defined(__ORBIS__)
#ifdef SDK_BUILD
#define MY_API_C EXTERNC __declspec(dllexport)
#define MY_API __declspec(dllexport)
#define MY_LOCAL
#else
#define MY_API_C EXTERNC __declspec(dllimport)
#define MY_API __declspec(dllimport)
#define MY_LOCAL
#endif
#else
#if __GNUC__ >= 4
#define MY_API_C EXTERNC __attribute__ ((visibility ("default")))
#define MY_API __attribute__ ((visibility ("default")))
#define MY_LOCAL __attribute__ ((visibility ("hidden")))
#else
#define MY_API_C EXTERNC
#define MY_API
#define MY_LOCAL
#endif
#endif

//************************************************
// EXAMPLE
//************************************************
// export c api
MY_API_C void testA();

// export C++ api
MY_API void testB();

第三方静态库符号导出

当我们编译的动态库有依赖第三方的静态库,且需要导出其函数符号时,需要通过编译选项进行导出符号设置。

Window(dll)

可以使用 #pragma 预处理进行显式导出符号声明。最终在编译链接时,会通过编译选项 /I (Additional include directories) 导出符号。

1
2
3
4
5
6
7
8
// example: export c func abase_remoteconfig_GetBool
#if defined(_WIN64)
#pragma comment(linker, "/include:abase_remoteconfig_GetBool")

#elif defined(_WIN32)
#pragma comment(linker, "/include:_abase_remoteconfig_GetBool")

#endif

注意:C函数导出符号,win32 以下划线开头,win64 不带开头下划线。

macOS/iOS(dylib)

  • 使用编译选项 -exported_symbols_list 指定导出符号列表的文本文件。

  • 从链接到的静态库中排除符号,可以使用链接器选项–exclude-libs lib1,lib2,…或–exclude-libs ALL指定所有静态库。

1
2
# export.txt 中为导出符号列表
-exported_symbols_list export.txt

XCode 编译配置可以在直接指定 Exported Symbols File。

注意:设置 exported_symbols_list 后,非列表里的符号则都不会再导出。还有个 unexport 的设置,跟 export 不能同时用。

Andoird(so)

  • 暂未遇到使用场景,待补充。

符号查看

使用 nm 列出.o .a .so 中的符号信息,包括诸如符号的值,符号类型及符号名称等。所谓符号,通常指定义出的函数,全局变量等等。

1
nm [option(s)] [file(s)]

有用的 options:

  • -A 在每个符号信息的前面打印所在对象文件名称;
  • -C 输出demangle过了的符号名称(--demangle);
  • -D 打印动态符号;
  • -l 使用对象文件中的调试信息打印出所在源文件及行号;
  • -n 按照地址/符号值来排序;
  • -u 打印出那些未定义的符号;
  • -m 按照 darwin 格式输出(--format=darwin);

常见的 符号类型:

  • A 该符号的值是绝对的,在以后的链接过程中,不允许进行改变。这样的符号值,常常出现在中断向量表中,例如用符号来表示各个中断向量函数在中断向量表中的位置。
  • B 该符号放在非初始化数据段段(bss)中,通常是那些未初始化的全局变量;
  • D 该符号放在普通的数据段(data section)中,通常是那些已经初始化的全局变量;
  • T 该符号放在代码段(text section)中,通常是那些全局非静态函数;
  • U 该符号未定义过,需要自其他对象文件中链接进来;
  • S 符号位于非初始化数据区,用于small object。
  • W 未明确指定的弱链接符号;同链接的其他对象文件中有它的定义就用上,否则就用一个系统特别指定的默认值。
  • N 该符号是一个debugging符号。
  • ? 该符号类型没有定义

注意事项:

  • -C 总是适用于c++编译出来的对象文件。还记得c++中有重载么?为了区分重载函数,c++编译器会将函数返回值/参数等信息-附加到函数名称中去形成一个mangle过的符号,那用这个选项列出符号的时候,做一个逆操作,输出那些原始的、我们可理解的符号名称。
  • 使用 -l 时,必须保证你的对象文件中带有符号调式信息,这一般要求你在编译的时候指定一个 -g 选项,见 Linux:Gcc。
  • 使用nm前,最好先用Linux:File查看对象文件所属处理器架构,然后再用相应交叉版本的nm工具。

强弱符号

强符号、弱符号

对于 C/C++ 语言来说,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号(Clang 下无效,需要配合扩展显式指定符号的强弱类型)。

强符号:函数、已经初始化的全局变量、类外面定义的成员函数、模板函数的特化版本;
弱符号:未初始化的全局变量、类中定义的成员函数、模板函数、模板类中的成员函数。

使用规则:

  1. 不允许强符号被多次定义(即不同目标文件中不能有同名的强符号);
  2. 如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,则链接时链接器会选择使用强符号;
  3. 如果一个符号在所有目标文件中都是弱符号,则链接器通常会选择其中类型占用空间最大(Clang 下直接使用当前编译单元的符号值)的一个。

其他

  • -dead_strip项是删除多余的库符号
  • -all_load让链接器把所有找到的目标文件都加载到可执行文件中
  • -force_load所做的事情跟-all_load其实是一样的,但是-force_load需要指定要进行全部加载的库文件的路径

参考文档

  1. gcc官方说明:https://gcc.gnu.org/wiki/Visibility
  2. gcc官方说明:https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Function-Attributes.html
  3. http://blog.guorongfei.com/2018/04/11/symbol-visibility/