在 macOS 上,是否可以修改已编译的动态库的符号名称?

On macOS, is it possible to modify the symbol names of an already compiled dynamic library?

我的 macOS Objective-C / Swift 应用程序有问题。我使用了一个给我带来问题的库,我不知道什么时候会有新版本。

根据我的发现,我使用的一种方法在库的最新版本中得到了修复,但是这个最新版本在另一种方法中引入了一个错误,该方法在以前的版本中有效。

在考虑可能的解决方案时,我认为理论上可以 link 针对两个库,并在两个库中使用有效的方法,但这当然会导致所有符号都被定义两次,无法编译。

是否可以更改两个版本之一中的符号名称并引入类似前缀的内容,以便我可以决定在不同情况下使用哪个版本?很抱歉,如果这完全是胡说八道,也许这取决于库的具体实现。

您可以修补符号(只需在十六进制编辑器中搜索字符串并更改一些字节就可以解决问题,只要总长度保持不变),但我认为有更优雅的解决方案。

Mach-Os 记录他们要从哪个库导入哪个符号,默认情况下 运行 在非平面命名空间中,即来自不同库的符号不会相互冲突在 运行 时间。
正如您可能已经观察到的那样,它们确实会在 link 时间发生碰撞。但是在 link 时间搞砸事情比修补二进制文件要容易得多。

我假设您的两个库具有相同的“安装名称”(如有疑问,请检查 otool -l your.dylib | fgrep -A2 LC_ID_DYLIB)。如果是这种情况,那么您将不得不重命名其中一个。如果你的dylib原来的安装名称是/usr/local/lib/libstuff.dylib,那么将其中一个重命名为/usr/local/lib/libstuff_alt.dylib和运行这个命令就可以了:

install_name_tool -id /usr/local/lib/libstuff_alt.dylib /usr/local/lib/libstuff_alt.dylib

如果您的图书馆已经或需要签名,您现在需要重新签名:

codesign -f -s - /usr/local/lib/libstuff_alt.dylib

如果您对安装名称的工作原理感到好奇,请参阅 this answer of mine

一旦库的两个版本具有不同的名称,让我们进行一个您可以遵循的设置。我创建了以下 C 文件:

a.c:

int f(void)
{
    return 10;
}

int g(void)
{
    return 11;
}

b.c:

int f(void)
{
    return 20;
}

int g(void)
{
    return 21;
}

并将它们编译成库:

cc -shared -o liba.dylib a.c -Wall -O3
cc -shared -o libb.dylib b.c -Wall -O3

然后我创建了另一个名为 t.c 的文件,它使用 f()g() 函数:

#include <stdio.h>

extern int f(void);
extern int g(void);

int main(void)
{
    printf("%d %d\n", f(), g());
    return 0;
}

如果您针对这两个库进行编译并link,那么它当前将从您首先指定的任何库中导入这两个符号:

% cc -o t t.c -Wall -O3 -L. -la -lb
% ./t
10 11
% cc -o t t.c -Wall -O3 -L. -lb -la
% ./t                              
20 21

所以我们要做的是作弊,使用可用于 linking 的“基于文本的存根文件”而不是实际的 dylibs。

xcrun tapi stubify -o liba.tbd liba.dylib
xcrun tapi stubify -o libb.tbd libb.dylib

这将创建文件 liba.tbdlibb.tbd,如下所示:

--- !tapi-tbd
tbd-version:     4
targets:         [ arm64-macos ]
uuids:
  - target:          arm64-macos
    value:           2AACA829-4039-3B2A-8751-2AB617189F29
flags:           [ not_app_extension_safe ]
install-name:    liba.dylib
current-version: 0
compatibility-version: 0
exports:
  - targets:         [ arm64-macos ]
    symbols:         [ _f, _g ]
...
--- !tapi-tbd
tbd-version:     4
targets:         [ arm64-macos ]
uuids:
  - target:          arm64-macos
    value:           02EE57B9-3074-3EE7-8B3C-EF2BDFA1D26F
flags:           [ not_app_extension_safe ]
install-name:    libb.dylib
current-version: 0
compatibility-version: 0
exports:
  - targets:         [ arm64-macos ]
    symbols:         [ _f, _g ]
...

此时我们可以轻松地从这些文件中删除不需要的符号。在我的例子中,我确保 liba.tbd 只有 [ _f ]libb.tbd 只有 [ _g ]。完成后,我们可以重试:

% cc -o t t.c -Wall -O3 -L. -la -lb
% ./t
10 21

好了。

执行此操作时唯一需要注意的是,您需要确保您使用的函数对它们所来自的库没有任何类型的内部依赖性,例如全局变量。