Let's summarize what I have learnt so far:
In order to build a universal binary, all libraries need to be available in universal. Let's say you have 20 SDKs that you want to include and only 10 of them are universal binaries, what do you do?
The reason there are so many libraries in my app comes from the fact that the app supports many manufacturers and the user may choose from these.
Quick fix
Therefore, I have decided to mock the unavailable arm64 libraries and making that visible to the user: If users want to use the arm64 version of the app, they just won't be able to access the devices with non-universal SDKs. If they need support for these devices, they will need to start the app with Rosetta (click on the app executable, show info, select Open using Rosetta), thereby making it transparent which manufacturers are supporting the arm64 platform and which are not.
How do you mock a arm64 version of a x86_64 library? Create a C
file and implement empty functions that return an error code, for
all functions that you use. Build for arm64 and combine with the
actual x86_64 library using the lipo tool.
Example:
#include "AbcSDK.h"
int AbcGetNumOfConnectedDevices() {
return 0;
}
ABC_ERROR_CODE AbcGetDeviceInfo(ABC_DEVICE_INFO *pAbcDeviceInfo, int iDeviceIndex) {
return ABC_ERROR_INVALID_INDEX;
}
...
Makefile:
CC=cc
AR=ar
LIPO=lipo
DEPS = include/AbcDeviceSDK.h
LIB_x64 = lib/x64/libAbcDeviceSDK.a
OBJ = AbcDeviceSDK.o
libAbcDeviceSDK.a: AbcDeviceSDK-arm64.a $(LIB_x64)
$(LIPO) $^ -create -output libAbcDeviceSDK.a
cp libAbcDeviceSDK.a YOURPROJECT/Libraries/AbcDeviceSDK.a
%.o: %.c $(DEPS)
$(CC) -c -o $@ $<
AbcDeviceSDK-arm64.a: $(OBJ)
$(AR) rcs $@ $^
clean:
rm -f *.o *.a
Of course, this does not solve the original problem, but it does at least build universal binaries.
Rosetta ahead-of-time translation
While Rosetta does save pre-translated binaries into /var/db/oah as .aot files and these files actually contain arm64 code, they are not stand alone (they do call the Rosetta runtime). See https://ffri.github.io/ProjectChampollion/ for further reference. This seems to be a dead-end.
Decompile using Ghidra
Ghidra is a great open-source reverse-engineering tool that you can use to understand the library (or any piece of software). It gives you two views into the code that you look at in parallel:
- assembly
- pseudo C
Ghidra is great to iteratively bring the automatically decompiled C code from a state of unnamed functions, variables, wrong types into something more akin to how it would have looked like before compilation. The resulting code will not compile with a compiler, it is meant as a more readable view into the assembly code. Be prepared to invest weeks and bring curiosity of how things work under the hood.
So to accomplish my task, I needed to first understand the code fully with Ghidra and then rewrite the library from scratch in C (or whatever language I prefer). With small libs this might be doable.