ELF 共用物件規定
在 Linux 和 Fuchsia 等以 ELF 為基礎的系統上,進入共用物件的機器碼 (即 GN 語言中的 shared_library() 和 loadable_module() 目標) 必須使用 -fPIC 編譯器和連結器選項建構。
這與可執行程式碼不同,後者會改用 -fPIE。這會產生比 -fPIC 更小且更快的程式碼,但無法用於共用物件。
Fuchsia 建構作業不支援為 Linux 產生共用程式庫,但 Fuchsia 會定義個別的工具鍊執行個體,用於編譯可執行檔和共用程式庫。
這個獨立的工具鍊在內部建構規則中稱為「shlib」工具鍊,甚至是「隨附工具鍊」,一律會透過在基本工具鍊標籤後方附加 -shared 後置字串來命名。舉例來說,//build/toolchain/fuchsia:x64-shared 是基本 //build/toolchain/fuchsia:x64 工具鍊的 shlib 工具鍊。
ELF 共用程式庫重新導向
Fuchsia 建構作業會實作一項功能,確保 ELF shared_library() 和 loadbable_module() 目標一律會以共用程式庫工具鍊建構,如上一節所述。
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() 定義