#symbol #llvm #object #mach-o #elf

bartleby

基于 LLVM 的符号重命名工具包。

2 个版本

0.2.3 2023 年 8 月 4 日
0.2.2 2023 年 8 月 4 日

#674开发工具

Apache-2.0

1.5MB
7K SLoC

D 4K SLoC C++ 1.5K SLoC // 0.2% comments C 659 SLoC // 0.1% comments Bazel 395 SLoC // 0.0% comments Rust 129 SLoC // 0.1% comments TypeScript 22 SLoC

包含 (静态库,1MB) build/lib/libBartleby.a,(Mach-o 可执行文件,285KB) ArchiveWriter.cpp.o,(Mach-o 可执行文件,245KB) Bartleby.cpp.o,(Mach-o 可执行文件,69KB) Bartleby-c.cpp.o,(静态库,110KB) build/lib/libBartleby-c.a,(Mach-o 可执行文件,54KB) Error.cpp.o 以及其他 5 个文件。

Bartleby: 一个符号重命名工具。

此仓库包含巴特比的源代码,这是一个库和工具,可以在多个目标文件和静态库之间重命名符号。

为什么?

如果您开发一个库,您的最终产品将包含

  • 对提供给用户使用的符号的定义(“导出”函数等),
  • 对您认为是库实现细节且希望隐藏给用户的符号的定义(“私有”函数),
  • 对您在依赖项中使用的符号的引用(如果您静态链接,则包括其定义)。

Bartleby 帮助您发布只暴露您希望暴露的符号的静态库,而不会将您的依赖项的符号污染命名空间。它类似于 objcopy --prefix-symbols=__private,但具有“智能”的(见下文“如何?”部分)自动化选择要前缀的符号(例如,对 puts 的引用不会被重命名为 private_puts,因为这会导致未解析的符号)。

这有助于解决菱形依赖问题:如果您的库用户想链接到 OpenSSL(它恰好也是您链接的库),您如何确保用户仍能控制他们使用的 OpenSSL 版本,而不会与您的 OpenSSL 使用发生名称冲突?您使用 Bartleby。

如何?

Bartleby 接受一组文件作为输入。这些可以是静态库(.a)或对象(.o)。Bartleby 通过遍历所有这些对象(用户直接提供的或包含在存档中的)来确定哪些符号被认为是私有的或不是私有的,并收集每个找到的符号的可见性和定义性。使用这些信息,以下是如何构建符号映射的

  • 如果在输入文件中的多个位置找到一个符号,并且至少有一个对象实际定义了它,则该符号被标记为已定义。
  • 如果在对象中找到的符号具有全局可见性,则该符号被标记为全局。

最后,对于每个标记为已定义和全局的符号,Bartleby将其视为私有,并为其名称添加前缀,并更改所有对象中对该符号的所有引用。输出是一个包含所有处理对象的单个静态库。

示例

假设我们有以下三个C文件,以及它们的头文件

  • api_1.c:
#include <stdio.h>

#include <external_dep.h>

__attribute__((visibility("default"))) void my_api(void) {
  puts("my_api called, calling external dep");
  char buf[0x41];
  external_api(buf, sizeof(buf));
}

void internal_impl(void) {
  puts("internal implementation, calling `my_api`");
  my_api();
}
  • api_2.c
#include <stdio.h>

#include "api_1.h"

__attribute__((visibility("default"))) void another_api(void) {
  puts("another_api called, calling my_api");
  my_api();
}
  • external_dep.c
#include <stdio.h>
#include <string.h>

__attribute__((visibility("default"))) void external_api(void *src, const size_t n) {
  puts("external_api called, calling external private impl");
  memset(src, 0, n);
}

void external_private_impl(void) {
  puts("external private implementation called");
}

我们编译了API中的前两个对象

$ clang -fvisibility=hidden -c api_1.c api_2.c -isystem.
$ file api_1.o api_2.o
api_1.o: Mach-O 64-bit object arm64
api_2.o: Mach-O 64-bit object arm64

然后,我们创建一个静态库

$ ar rvs libapi.a api_1.o api_2.o

现在,让我们构建我们的简单外部依赖

$ clang -fvisibility=hidden -c external_dep.c
$ ar rvs libexternal.a external_dep.o

libapi.a 是我们想要为其符号添加自定义前缀的库。 libexternal.a 是我们的外部依赖之一,我们希望用户能够链接到 libapi.a,而不会遇到任何C符号冲突,因为 libexternal 可能被用于另一个位置。我们通过提供 libapi.alibexternal.a 作为输入来运行Bartleby。输出将是 libapi_v1.1.a

$ bartleby --if libapi.a \
           --if libexternal.a \
           --of libapi_v1.1.a \
           --prefix __impl_v1.1_
5 symbol(s) prefixed
libapi_v1.1.a produced.

现在,我们使用 nm 来检查生成的存档

$ nm libapi_v1.1.a
pi_1.o:
             	U ___impl_v1.1_external_api
0000000000000064 T ___impl_v1.1_internal_impl
0000000000000000 T ___impl_v1.1_my_api
             	U ___stack_chk_fail
             	U ___stack_chk_guard
             	U _puts
0000000000000084 r l_.str
00000000000000a8 r l_.str.1

api_2.o:
0000000000000000 T ___impl_v1.1_another_api
             	U ___impl_v1.1_my_api
             	U _puts
0000000000000020 r l_.str

external_dep.o:
0000000000000000 T ___impl_v1.1_external_api
000000000000001c T ___impl_v1.1_external_private_impl
             	U _puts
0000000000000038 r l_.str
000000000000006b r l_.str.1

现在我们可以看到已定义的符号已经被添加了前缀 __impl_v1.1。然而,puts 没有添加前缀,因为即使它是一个全局符号,也没有在任何 libapi.alibexternal.a 中找到定义,因此Bartleby没有为其添加前缀。

入门

Bartleby可以用Bazel或CMake编译。

使用CMake

构建Bartleby需要LLVM >= 15.0。请参阅入门指南,以获取有关如何构建LLVM的更多信息,以及LLVM版本发布以获取所有版本。APT软件包也可在LLVM Debian/Ubuntu软件包页面上找到。

$ cmake -B build -DCMAKE_BUILD_TYPE=Release -DLLVM_DIR=/path/to/llvm-15/lib/cmake
$ cmake --build build
$ ./build/bin/bartleby

如果已安装LLVM的更高版本,则必须定义 BARTLEBY_LLVM_VERSION

$ cmake -B build -DCMAKE_BUILD_TYPE=Release -DBARTLEBY_LLVM_VERSION=16.0 -DLLVM_DIR=/path/to/llvm-16/lib/cmake
$ cmake --build build

使用Bazel

强烈推荐使用 bazelisk

$ bazelisk build -c opt bartleby/...
$ ./bazel-bin/bartleby/tools/Bartleby/bartleby

将Bartleby导入您的Bazel项目

您还可以使用 http_archive 在现有的Bazel工作区中导入Bartleby。您可以使用它作为工具,也可以通过bartleby starlark规则使用它。有关更多信息,请参阅examples/bazel

许可

请参阅LICENSE

无运行时依赖