SuperMan-Lfj / blog

Apache License 2.0
1 stars 0 forks source link

动态库符号隐藏 #57

Open SuperMan-Lfj opened 1 year ago

SuperMan-Lfj commented 1 year ago

GCC4之后支持使用-fvisibility=hidden编译选项,将库的所有符号默认设置为对外不可见。这样编译出的二进制就不会导出可供外部链接的符号。然后再结合GCC的__attribute__ ((visibility ("default")))属性,在代码中明确指定可以暴露给外部的API,于是我们就可以显示的控制库的对外API的可见性。

//cmake 设置方式
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE }" -fvisibility=hidden -fvisibility-inlines-hidden")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE }" -fvisibility=hidden -fvisibility-inlines-hidden")

如下代码示例:

// entry.h

void function1();
__attribute__ ((visibility ("default"))) void entry_point();
// entry.cpp

#include "entry.h"

void function1() {
    // ...
}

void entry_point() {
    function1();
}

当我们采用-fvisibility=hidden将entry.cpp编译成静态库或者动态库后,无论用户是静态链接还是使用dlopen动态库的方式,都只能访问到void entry_point()函数,而不能访问到void funcion1()

通过该方法,我们不仅能显示控制库的导出API,还可以帮助编译器和链接器优化出更好的二进制,并且缩短动态库的加载时间。

Windows下也有类似的机制__declspec(dllexport),它和gcc下的attribute ((visibility ("default")))作用类似。稍微不同的是Windows下还存在__declspec(dllimport)用于API的使用方显示导入外部API,以便编译器对代码进行优化,但gcc下没有对应的扩展。

动态库可以和静态库进行链接,以获取自己需要的符号。但是有些时候我们只想要和静态库进行链接,却不想在动态库中将静态库中的符号间接暴露出去。这时可以采用-fvisibility=hidden选项重新编译该静态库。但遗憾的是我们不总是能够控制第三方静态库的编译过程,这时可以借助链接器提供的显示指定符号表的方法。该方法需要按照链接器的规范写一个导出符号表,在链接期通过参数传递给链接器,这样就可以精细的控制动态库需要暴露的符号了。该方法并不常用,因此我们不多做介绍,具体用法可以参考https://www.gnu.org/software/gnulib/manual/html_node/LD-Version-Scripts.html

//cmake 使用方式
set_property(TARGET xxx APPEND_STRING PROPERTY LINK_FLAGS " -Wl,--gc-sections -Wl,--version-script, export符号文件路径")

export符号文件内容举例(jni的):

{
    global:
            JNI_OnLoad;
            JNI_OnUnload;
    local:
            *;
};

文章参考:C/C++符号隐藏与依赖管理:库的符号隐藏