ELF 共用資料庫重新導向

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() 定義