ELF 共享对象要求
在基于 ELF 的系统(如 Linux 和 Fuchsia)上,进入共享对象(即 GN 中的 shared_library() 和
loadable_module() 目标)的机器代码必须使用 -fPIC 编译器和链接器选项进行构建。
这与使用 -fPIE 的可执行代码不同。与 -fPIC 相比,这会生成更小、更快的代码,但不能用于共享对象。
Fuchsia build 不支持为 Linux 生成共享库,但对于 Fuchsia,它定义了单独的工具链实例来编译可执行文件和共享库。
此单独的工具链在内部 build 规则中称为 "shlib" 工具链,甚至称为
配套工具链,并且始终通过将 -shared 后缀附加到 基本工具链 标签来命名。例如,
//build/toolchain/fuchsia:x64-shared 是基本
//build/toolchain/fuchsia:x64 工具链的 shlib 工具链。
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
保持所有这些目标定义和依赖项正确同步非常繁琐,并且会使 build 规则不太抽象和实用。
使用专用工具链构建 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 build 使用最后一个技巧来解决最后一个问题:在基本工具链中使用重定向组目标,以引用 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 在其 BUILDCONFIG.gn 中重新定义了 shared_library() 模板,从而向
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" ]
}
如需了解详情,请参阅 shared_library() 中的 BUILDCONFIG.gn