ELF 共用物件需求
在 ELF 式系統 (例如 Linux 和 Fuchsia) 上,必須使用 -fPIC
編譯器和連接器選項建構共用物件 (即 GN 口說中的 shared_library()
和 loadable_module()
目標) 的機器程式碼。
這與使用 -fPIE
的可執行程式碼不同。這會產生比 -fPIC
更少且速度更快的程式碼,但無法用於共用物件。
Fuchsia 版本不支援產生 Linux 的共用程式庫,但對 Fuchsia 來說,它會定義獨立的工具鍊執行個體來編譯執行檔和共用程式庫。
在內部建構規則中,這個獨立工具鍊稱為「shlib」工具鍊或甚至是隨附工具鍊,而且一律會透過將 -shared
後置字串附加至基礎工具鍊標籤來命名。例如,//build/toolchain/fuchsia:x64-shared
是基礎 //build/toolchain/fuchsia:x64
工具鍊的 shlib 工具鍊。
ELF 共用程式庫重新導向
Fuchsia 建構會實作一項功能,確保 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 建構會在其 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" ]
}
詳情請參閱 BUILDCONFIG.gn
中的 shared_library()
定義