Frederick Gotham
2020-10-22 10:48:37 UTC
I have an idea for making a 'fat binary' that doesn't require any kernel
support on Linux or Mac.
So let's say we start off with a small shell script like this:
#!/bin/sh
arch=`uname -m`
if [ $arch = "x86_64" ] ; then
// Do something
elif [ $arch = "aarch64" ] ; then
// Do something
fi
exit 0
The most important line is the last one "exit 0" because it will stop
the script. It is after this line that I will put the x86_64 binary,
followed by the aarch64 binary, so the script will look something like
this:
#!/bin/sh
arch=`uname -m`
if [ $arch = "x86_64" ] ; then
skip=00290
count=08816
elif [ $arch = "aarch64" ] ; then
skip=09106
count=14688
else
exit 1
fi
dd if="$0" of=/tmp/binaryfile bs=1 skip=${skip} count=${count} > /dev/null 2>&1
chmod +x /tmp/binaryfile
/tmp/binaryfile
exit 0
[ - - - x86_64 binary goes here - - - ]
[ - - - aarch64 binary goes here - - - ]
So as a test program to try it out, I'll now write a small C++ program
that prints a number sequence:
#include <iostream> /* cout, endl */
#include <thread> /* this_thread::sleep_for */
#include <chrono> /* seconds */
auto main(void) -> int
{
for ( unsigned i = 0; /* ever */ ; ++i )
{
std::this_thread::sleep_for(std::chrono::seconds(1u));
std::cout << i << std::endl;
}
}
I have compiled this program for two architectures:
g++ main.cpp -DNDEBUG -Os -o prog_x86_64
aarch64-linux-gnu-g++ main.cpp -DNDEBUG -Os -o prog_aarch64
and so I have two binaries as follows:
-rwxr-xr-x 1 root root 8816 Oct 22 10:53 prog_x86_64
-rwxr-xr-x 1 root root 14688 Oct 22 10:52 prog_aarch64
The x86_64 binary is 8816 bytes.
The aarch64 binary is 14688 bytes.
So now I get the size of my small script above:
-rwxr-xr-x 1 root root 290 Oct 22 11:31 myscript.sh
It is 290 bytes, which means I know how much to skip by:
dd if="$0" of=/tmp/binaryfile bs=1 skip=00290 count=08816
Finally I concatenate the 3 files:
cat myscript.sh prog_x86_64 prog_aarch64 > fatbin
And I run my fat binary:
chmod +x fatbin
./fatbin
I've tried this 'fatbin' file on both architectures and it works. You
can download the files here and try it yourself:
http://virjacode.com/experimental/prog.cpp
http://virjacode.com/experimental/prog_aarch64
http://virjacode.com/experimental/prog_x86_64
http://virjacode.com/experimental/myscript.sh
http://virjacode.com/experimental/fatbin
I think it might even be possible to extend this script so that it
doubles as a batch file for Microsft Windows, so maybe we could have two
Linux binaries, a Mac binary, as well as a Microsoft binary all in one
file. It can made threadsafe and re-enterable by using system-supplied
temporary filenames or UUID's instead of "/tmp/binaryfile".
support on Linux or Mac.
So let's say we start off with a small shell script like this:
#!/bin/sh
arch=`uname -m`
if [ $arch = "x86_64" ] ; then
// Do something
elif [ $arch = "aarch64" ] ; then
// Do something
fi
exit 0
The most important line is the last one "exit 0" because it will stop
the script. It is after this line that I will put the x86_64 binary,
followed by the aarch64 binary, so the script will look something like
this:
#!/bin/sh
arch=`uname -m`
if [ $arch = "x86_64" ] ; then
skip=00290
count=08816
elif [ $arch = "aarch64" ] ; then
skip=09106
count=14688
else
exit 1
fi
dd if="$0" of=/tmp/binaryfile bs=1 skip=${skip} count=${count} > /dev/null 2>&1
chmod +x /tmp/binaryfile
/tmp/binaryfile
exit 0
[ - - - x86_64 binary goes here - - - ]
[ - - - aarch64 binary goes here - - - ]
So as a test program to try it out, I'll now write a small C++ program
that prints a number sequence:
#include <iostream> /* cout, endl */
#include <thread> /* this_thread::sleep_for */
#include <chrono> /* seconds */
auto main(void) -> int
{
for ( unsigned i = 0; /* ever */ ; ++i )
{
std::this_thread::sleep_for(std::chrono::seconds(1u));
std::cout << i << std::endl;
}
}
I have compiled this program for two architectures:
g++ main.cpp -DNDEBUG -Os -o prog_x86_64
aarch64-linux-gnu-g++ main.cpp -DNDEBUG -Os -o prog_aarch64
and so I have two binaries as follows:
-rwxr-xr-x 1 root root 8816 Oct 22 10:53 prog_x86_64
-rwxr-xr-x 1 root root 14688 Oct 22 10:52 prog_aarch64
The x86_64 binary is 8816 bytes.
The aarch64 binary is 14688 bytes.
So now I get the size of my small script above:
-rwxr-xr-x 1 root root 290 Oct 22 11:31 myscript.sh
It is 290 bytes, which means I know how much to skip by:
dd if="$0" of=/tmp/binaryfile bs=1 skip=00290 count=08816
Finally I concatenate the 3 files:
cat myscript.sh prog_x86_64 prog_aarch64 > fatbin
And I run my fat binary:
chmod +x fatbin
./fatbin
I've tried this 'fatbin' file on both architectures and it works. You
can download the files here and try it yourself:
http://virjacode.com/experimental/prog.cpp
http://virjacode.com/experimental/prog_aarch64
http://virjacode.com/experimental/prog_x86_64
http://virjacode.com/experimental/myscript.sh
http://virjacode.com/experimental/fatbin
I think it might even be possible to extend this script so that it
doubles as a batch file for Microsft Windows, so maybe we could have two
Linux binaries, a Mac binary, as well as a Microsoft binary all in one
file. It can made threadsafe and re-enterable by using system-supplied
temporary filenames or UUID's instead of "/tmp/binaryfile".