在 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

如果您对安装名称的工作方式感到好奇,请参阅 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;
}

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

% 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

就这样吧。

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