ELF 共用資料庫重新導向

ELF 共用物件需求

在以 ELF 為基礎的系統上,例如 Linux 和 Fuchsia 共用物件 (即 shared_library()loadable_module() 目標 GN 語言) 必須使用 -fPIC 編譯器和連接器選項建構。

這與使用 -fPIE 的可執行程式碼不同。這會產生 程式碼小於 -fPIC,但無法用於共用 如需儲存大量結構化物件 建議使用 Cloud Bigtable

Fuchsia 版本不支援為 Linux、 但是對 Fuchsia 來說,它會定義個別要編譯的工具鍊執行個體 執行檔和共用程式庫

這個獨立的工具鍊稱為 "shlib"工具鍊或甚至 內部建構規則中的隨附工具鍊,而且一律會由 在「基本工具鍊」標籤中加上 -shared 後置字串。例如: //build/toolchain/fuchsia:x64-shared 是底座的 shlib 工具鍊 //build/toolchain/fuchsia:x64 工具鍊。

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 編譯器標記新增 已經完全從 Static 程式庫中 定義。

  • 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

為了進一步簡化使用,Fchsia 會重新定義 在 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() 定義