Reputation: 1792
I have a problem in my macOS Objective-C / Swift application. I use a library that is giving me problems, and I don't know when a new version will be available.
From my findings, one method that I use is fixed in the last version of the library, but this latest version introduces a bug in another method, that works in the previous version.
While thinking of a possible solution, I thought that it would be in theory possible to link against both libraries, and use in both what works, but that of course leads to all the symbols to be defined twice, and won't compile.
Would it be possible to change the symbols names in one of the two versions and introduce something like a prefix, so that I could decide which version to use in different situations? Sorry if this is completely non-sense, perhaps it depends on the specific implementation of the library.
Upvotes: 2
Views: 884
Reputation: 23900
You could patch the symbols (simply searching for the string in a hex editor and changing some bytes should do the trick, so long as the overall length stays the same), but I think there is a more elegant solution.
Mach-Os record which symbol they want to import from which library, and by default run in a non-flat namespace, i.e. symbols from different libraries won't collide with each other at runtime.
As you may have observed though, they do collide at link-time. But messing with things at link-time is a lot easier than patching binaries.
I'm assuming both of your libraries have the same "install name" (if in doubt, check otool -l your.dylib | fgrep -A2 LC_ID_DYLIB
). If this is the case, then you'll have to rename one of them. If your dylib's original install name is /usr/local/lib/libstuff.dylib
, then rename one of them to /usr/local/lib/libstuff_alt.dylib
and run this command on it:
install_name_tool -id /usr/local/lib/libstuff_alt.dylib /usr/local/lib/libstuff_alt.dylib
If your library was or needs to be signed, you'll now need to re-sign it:
codesign -f -s - /usr/local/lib/libstuff_alt.dylib
If you're curious about how install names work, see this answer of mine.
Once the two versions of the library have different names, let's do a setup that you can follow along. I've created the following C files:
a.c
:
int f(void)
{
return 10;
}
int g(void)
{
return 11;
}
b.c
:
int f(void)
{
return 20;
}
int g(void)
{
return 21;
}
And compiled them both to libraries:
cc -shared -o liba.dylib a.c -Wall -O3
cc -shared -o libb.dylib b.c -Wall -O3
Then I've created another file called t.c
which uses the f()
and g()
functions:
#include <stdio.h>
extern int f(void);
extern int g(void);
int main(void)
{
printf("%d %d\n", f(), g());
return 0;
}
If you compile and link this against the two libraries, then it will currently import both symbols from whatever library you specify first:
% 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
So what we're gonna do is cheat a bit, using the "text-based stub files" that can be used for linking instead of the actual dylibs.
xcrun tapi stubify -o liba.tbd liba.dylib
xcrun tapi stubify -o libb.tbd libb.dylib
This creates the files liba.tbd
and libb.tbd
like so:
--- !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 ]
...
At this point we can trivially delete the symbols we don't want from those files. In my example, I made sure that liba.tbd
only has [ _f ]
and libb.tbd
only has [ _g ]
. Once that's done, we can try again:
% cc -o t t.c -Wall -O3 -L. -la -lb
% ./t
10 21
And there you go.
The only caveat when doing this is that you need to make sure that the functions you're using don't have any kind of internal dependency on the library they're coming from, like global variables.
Upvotes: 4