ELF 共享对象要求
在基于 ELF 的系统(如 Linux 和 Fuchsia)中,
共享对象(即 shared_library()
和 loadable_module()
中的目标
必须使用 -fPIC
编译器和链接器选项进行构建。
这与可执行代码不同,可执行代码使用 -fPIE
。这会生成
比 -fPIC
更小、更快的代码,但无法用于共享
对象的操作。
Fuchsia 版本不支持为 Linux 生成共享库, 但对于 Fuchsia,它会定义单独的工具链实例来编译 可执行文件和共享库
这种独立的工具链称为“shlib”工具链,甚至是
配套工具链(位于内部构建规则中),并始终由
将 -shared
后缀附加到基本工具链标签。例如
//build/toolchain/fuchsia:x64-shared
是基础的 shlib 工具链
//build/toolchain/fuchsia:x64
工具链。
ELF 共享库重定向
Fuchsia build 实现了一项功能,可确保 ELF shared_library()
和 loadbable_module()
目标始终在 shlib 工具链中构建,就像
定义。
Fuchsia 开发者无需知道这一点,
透明:照常写入 shared_library()
目标,而不必
完全不担心 ELF / 非 ELF。
本部分的其余内容介绍了实现方法。首先, 通过几个实际示例,了解为什么需要它:
请考虑可以链接的 C++ 静态库(例如 libutil
)的情况
可执行文件或共享库对象。由于这两种类型的二进制文件
需要使用不同的编译器标志来构建代码,其中一种方法是
是定义两个目标,如下所示:
# A variant of the library to be linked into executables.
static_library("libutil-static") {
sources = [ ... ]
...
}
# A variant of the library to be linked into shared libraries.
static_library("libutil-shared") {
sources = [ ... ]
...
if (is_fuchsia) {
cflags = [ "-fPIC" ] # Required for shared library code on Fuchsia.
}
}
executable('program') {
sources = [ ... ]
deps = [ ":libutil-static" ]
}
shared_library('foo') {
sources = [ ... ]
deps = [ ":libutil-shared" ]
}
这样做行得通,但也会带来一些不便:
该库需要两个目标定义,而不是一个; 需要保持同步即使未构建 Fuchsia 二进制文件也是如此。
显式
is_fuchsia
检查和编译器标记的额外需求 添加到每个共享变体定义中,这种定义非常细微, 并且使这些定义不太抽象。(其中一些可以通过 GN 配置进行简化,但仍然如此)。
任何使用库的目标都需要选择 两个变体。
为了让事情更贴近现实,我们来
libutil
库还依赖于另一个 liblog
静态库。
后者还需要同时提供静态变体和共享变体,
如:
static_library("liblog-static") {
sources = [ ... ]
...
}
static_library("liblog-shared") {
sources = [ ... ]
if (is_fuchsia) {
cflags = [ "-fPIC" ]
}
}
static_library("libutil-static") {
...
deps = [ ":liblog-static" ]
}
static_library("libutil-shared") {
...
if (is_fuchsia) {
cflags = [ "-fPIC" ] # Required for shared library code.
}
deps = [ ":liblog-shared" ]
}
... same as above
以下依赖关系图对此进行了说明:
program ->
libutil-static ->
liblog-static
foo ->
libutil-shared ->
liblog-shared
正确保留所有这些目标定义和依赖项 同步繁琐,大大降低构建规则的抽象性和实用性。
使用专用工具链构建 ELF 共享对象代码可以避免
libutil
及其依赖项的目标重复。例如:
# The following definition is global and should normally be put
# in the BUILDCONFIG.gn file
if (is_fuchsia) {
# Name of the toolchain used to build ELF shared library code.
# This toolchain adds the `-fPIC` option to all build commands
# by default so there is no need to add it in target definitions.
shlib_toolchain = "${current_toolchain}-shared"
} else {
# Shared library code can be built directly in the current toolchain
# on non-Fuchsia platforms.
shlib_toolchain = current_toolchain
}
# The following is part of a BUILD.gn file
static_library("liblog") {
...
}
static_library("libutil") {
...
deps = [ ":liblog" ]
}
executable('program') {
sources = [ ... ]
deps = [ ":libutil" ]
}
shared_library("foo") {
sources = [ ... ]
deps = [ ":libutils($shlib_toolchain)" ]
}
现在,它对应于依赖关系图:
program ->
libutil ->
liblog
foo ->
libutil($shlib_toolchain) ->
liblog($shlib_toolchain)
上述方案解决了大多数原始问题,原因如下:
显式
is_fuchsia
检查和-fPIC
编译器标记添加 已经从静态库 定义。libutil
目标无需担心哪个变体 选择其依赖项
另一方面,每个 shared_library()
实例仍然需要
仔细选择其静态库和源代码集依赖项
从 shlib_toolchain
链接到
代码。
Fuchsia 构建使用了最后一个技巧来解决最后一个问题: 在基本工具链中使用重定向组目标,以 在 shblib 工具链中引用实际共享库目标, 如:
# Set to true if the current toolchain's target platform is based
# on ELF and requires an shlib toolchain. This would normally be
# defined in BUILDCONFIG.gn for Fuchsia toolchains.
_requires_shlib_toolchain = ...
if (_requires_shlib_toolchain && current_toolchain != shlib_toolchain) {
# A simple group that depends on the same target built in
# the shblib_toolchain. Note that `public_deps` instead of `deps`
# is required when crossing toolchain boundaries for proper
# linking.
group("bar") {
public_deps = [ ":bar($shlib_toolchain)" ]
}
} else {
# The target that actually builds the shared library in
# the shlib_toolchain, or the base one for non-Fuchsia platforms.
# It will pick its dependencies in the same toolchain context.
shared_library("bar") {
...
deps = [ ":libutil" ]
}
}
executable("program2") {
deps = [ ":bar" ]
}
这最终会在 Fuchsia 上创建以下依赖关系图:
program2 -->
bar --> # redirection group
bar(shblib_toolchain) --> # real shared_library()
libutil(shlib_toolchain)
非 Fuchsia 基础工具链将获得:
program2 -->
bar --> # real shared_library()
libutil
为了进一步简化使用,Fuchsia build 重新定义了
shared_library()
模板的 BUILDCONFIG.gn
中,隐藏了以下内容
BUILD.gn
文件中的实现细节,可以用
最自然的方式,例如:
### This would appear in BUILDCONFIG.gn to ensure that `shared_library()`
### does ELF shared library redirection automatically when needed.
if (_requires_shlib_toolchain) {
template("shared_library") {
if (current_toolchain != shlib_toolchain) {
group(target_name) {
public_deps = [ ":${target_name}(${shlib_toolchain})" ]
}
} else {
# Ensure the built-in shared_library() function is called.
target("shared_library", target_name) {
forward_variables_from(invoker, "*")
}
}
}
}
### This would appear in regular BUILD.gn files
# Invokes the custom `shared_library()` template instead
# of the built-in one. On Fuchsia systems, this will create a
# redirection group that refers to a real shared_library()
# target in the shlib_toolchain. On a non-Fuchsia system, this
# simply defines a real shared_library() target.
shared_library("bar") {
...
deps = [ ":libutil" ]
}
executable("program2") {
...
deps = [ ":bar" ]
}
如需了解详情,请参阅 BUILDCONFIG.gn
中的 shared_library()
定义