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



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

根据我的发现,我使用的一个方法在上一个版本的库中已经修复了,但是这个最新版本在另一个方法中引入了一个错误,该方法在上一个版本中工作。

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

有没有可能改变其中一个版本的符号名称,并引入前缀之类的东西,这样我就可以决定在不同的情况下使用哪个版本?对不起,如果这是完全没有意义的,也许这取决于库的具体实现。

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

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

我假设你的两个库都有相同的"安装名"。(如有疑问,检查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

如果你对安装名是如何工作的感到好奇,请看我的回答。

一旦两个版本的库具有不同的名称,让我们做一个您可以遵循的设置。我创建了以下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 %dn", f(), g());
return 0;
}

如果你编译并链接这两个库,那么它将从你首先指定的库中导入这两个符号:

% 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

所以我们要做的是稍微欺骗一下,使用"基于文本的存根文件"。可用于链接而不是实际的dylib。

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

就这样了。

这样做的唯一警告是,你需要确保你使用的函数对它们来自的库没有任何类型的内部依赖,比如全局变量。

最新更新