rschupp / PAR-Packer

(perl) Generate stand-alone executables, perl scripts and PAR files https://metacpan.org/pod/PAR::Packer
Other
48 stars 13 forks source link

MacOS universal binaries? #57

Closed plk closed 2 years ago

plk commented 2 years ago

I created two pp binaries, one for x86_64 and one for ARM, signed them both, both work. Then combined them into a universal binary with lipo, resulting in new binary. This only works on ARM and the x86_64 fails with errors like this:

Compilation failed in require at /loader/HASH(0x7fdba0884a10)/XSLoader.pm line 118.

Just wanted to confirm that this won't work due to the assumptions pp'ed binaries make about headers etc. I assume that there is no way to create universal binaries with pp?

rschupp commented 2 years ago

Just wanted to confirm that this won't work due to the assumptions pp'ed binaries make about headers etc. I assume that there is no way to create universal binaries with pp?

Sorry, there's no way to create "universal" binaries. See the section "Anatomy of a Self-Contained PAR executable" in PAR::Tutorial: you would somehow have to combine the corresponding parts of two native packed executables separately (e.g. use lipo for the first (execuatble) parts, create a "union" zip from the third (zip) parts), then make sure that when run on architecture A, only the stuff for this architecture from the "union" zip is used etc.

I'm curious why our naive attempt works at all for at least one architecture. My guess is that lipo works by concatenating the two executables and then prepends a new MachO header that references sections in both executables (tagged with architecture) and the MacOS loader picks the correct sections. In this scenario the pp binary the was placed last in the resulting universal binary might actually work (since the "embedded" files are correctly found by offset from the end of the file, same goes for the zip).

plk commented 2 years ago

I think this is correct as in testing using lipo, it's the last one in the universal list that seems to work. It's not that it works on ARM vs x86_64, it's just that the ARM binary was after the x86_64. I wonder if there is a way then to detect universal binaries and adjust the unpacking offsets at runtime.

rschupp commented 2 years ago

I wonder if there is a way then to detect universal binaries and adjust the unpacking offsets at runtime.

There are two problems here:

rschupp commented 2 years ago

AFAIK there's no way to direct Archive::Zip to extract a zip not in the tail position.

Correction: there might be a away, see https://metacpan.org/release/PHRED/Archive-Zip-1.68/source/examples/readScalar.pl

But then we're facing the same problem as for the "embedded" files.

plk commented 2 years ago

I wonder if we can then detect a fat binary, if it's a fat binary, use lipo (which should be on any MacOS I think?) to extract that arch and then run as normal? lipo can do this by extracting the arch (given by the arch command) to a new file - I did a quick test and this works - the pp binary runs as normal after such an extract.

plk commented 2 years ago

There is information in the FAT header about the offsets for the different archictectures:

Screenshot 2022-02-07 at 17 13 50
rschupp commented 2 years ago

You could patch something like the following into myldr/boot.c, directly before the call of extract_embedded_file:

plk commented 2 years ago

Opened a PR doing what was suggested - works on my previously failing tests.

rschupp commented 2 years ago

Closed via #58