ELF 共享库重定向

ELF 共享对象要求

在基于 ELF 的系统(如 Linux 和 Fuchsia)中, 共享对象(即 shared_library()loadable_module() 中的目标 必须使用 -fPIC 编译器和链接器选项进行构建。

这与可执行代码不同,可执行代码使用 -fPIE。这会生成 比 -fPIC 更小、更快的代码,但无法用于共享 对象的操作。

Fuchsia 版本不支持为 Linux 生成共享库, 但对于 Fuchsia,它会定义单独的工具链实例来编译 可执行文件和共享库

这种独立的工具链称为“shlib”工具链,甚至是 配套工具链(位于内部构建规则中),并始终由 将 -shared 后缀附加到基本工具链标签。例如 //build/toolchain/fuchsia:x64-shared 是基础的 shlib 工具链 //build/toolchain/fuchsia:x64 工具链。

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

正确保留所有这些目标定义和依赖项 同步繁琐,大大降低构建规则的抽象性和实用性。

使用专用工具链构建 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 build 重新定义了 shared_library() 模板的 BUILDCONFIG.gn 中,隐藏了以下内容 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() 定义