Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Polyglink

Polyglink is a Rust rewrite of apelink from the Cosmopolitan project. It uses Clap for its command-line interface and turns architecture-specific ELF executables into one Actually Portable Executable (APE).

One source file. Three CPU builds. One final file. Copy that file to another supported operating system and run it directly—there is no archive to unpack, VM to start, or language runtime to install.

          +--> x86-64 ELF  --+
hello.c --+--> AArch64 ELF ---+--> hello-polyglink-riscv.com
          +--> RISC-V ELF  --+       +--> Linux / BSD / macOS / Windows
                                     +--> including RISC-V 64 Linux

Build Polyglink

Polyglink requires a Rust toolchain with Rust 2024 edition support. Run these commands from the Polyglink repository root; the demo also needs curl and unzip for the one-time compiler download.

command -v cargo
command -v curl
command -v unzip
cargo build --release
export POLYGLINK="$PWD/target/release/polyglink"
"$POLYGLINK" --version

The commands below assume a POSIX shell on Linux, macOS, or WSL. Building in WSL and then copying the resulting .com file to native Windows is the least surprising Windows workflow. The RISC-V extension uses a Debian or Ubuntu x86-64 builder with a RISC-V cross compiler.

The polyglot moment

1. Install the Cosmopolitan toolchain

Polyglink performs the final link. The C compilers, libc, and APE loaders come from the official Cosmopolitan toolchain. If it is already installed, skip the download and set COSMOCC to its absolute directory.

export COSMOCC="$HOME/.local/opt/cosmocc"
mkdir -p "$COSMOCC"
curl --fail --location https://cosmo.zip/pub/cosmocc/cosmocc.zip --output "$COSMOCC/cosmocc.zip"
(cd "$COSMOCC" && unzip -q -o cosmocc.zip)
test -x "$COSMOCC/bin/x86_64-unknown-cosmo-cc"
test -x "$COSMOCC/bin/aarch64-unknown-cosmo-cc"
test -x "$COSMOCC/bin/apelink"
test -f "$COSMOCC/bin/ape-x86_64.elf"
test -f "$COSMOCC/bin/ape-aarch64.elf"
test -f "$COSMOCC/bin/ape-m1.c"

This one-time download is large. The unversioned cosmocc.zip URL is the stable installation URL documented in the Cosmopolitan README.

2. Create the smallest useful program

The demo uses a temporary directory, so it does not dirty the repository and can be run again safely.

export DEMO="${TMPDIR:-/tmp}/polyglink-demo"
mkdir -p "$DEMO"
cd "$DEMO"
cat > hello.c <<'EOF'
#include <stdio.h>

int main(void) {
  puts("Hello from one polyglot binary!");
  return 0;
}
EOF

3. Compile the same source for two CPU architectures

Use the public *-unknown-cosmo-cc drivers. The similarly named *-linux-cosmo-gcc programs are low-level implementation details and do not configure the Cosmopolitan headers when called directly.

"$COSMOCC/bin/x86_64-unknown-cosmo-cc" -g -o hello-x86_64.elf hello.c
"$COSMOCC/bin/aarch64-unknown-cosmo-cc" -g -o hello-aarch64.elf hello.c

At this point there are two normal ELF executables containing the same program for different CPUs.

4. Link them with Polyglink

"$POLYGLINK" \
  -G \
  -l "$COSMOCC/bin/ape-x86_64.elf" \
  -l "$COSMOCC/bin/ape-aarch64.elf" \
  -M "$COSMOCC/bin/ape-m1.c" \
  -o hello-polyglink.com \
  hello-x86_64.elf hello-aarch64.elf
chmod +x hello-polyglink.com
./hello-polyglink.com

The output should be:

Hello from one polyglot binary!

-G makes the demonstration self-contained: the generated file uses its embedded APE loaders instead of searching PATH for a system-wide ape command. -M embeds the Apple Silicon loader source so macOS can compile it on the first run.

5. Link the same inputs with the original apelink

This is a useful compatibility check. Only the linker executable and output name change.

"$COSMOCC/bin/apelink" \
  -G \
  -l "$COSMOCC/bin/ape-x86_64.elf" \
  -l "$COSMOCC/bin/ape-aarch64.elf" \
  -M "$COSMOCC/bin/ape-m1.c" \
  -o hello-apelink.com \
  hello-x86_64.elf hello-aarch64.elf
chmod +x hello-apelink.com
./hello-apelink.com

It prints the same line. Loader-compressed files are not required to be byte-for-byte identical because Polyglink uses a pure-Rust DEFLATE backend; their uncompressed contents and executable behavior are equivalent.

6. Add the RISC-V 64 payload

The stable cosmocc.zip currently supplies x86-64 and AArch64 tools. For the third payload, use a Linux RISC-V cross compiler and the RISC-V APE loader from the rustsbi/cosmopolitan fork. The loader work is kept on its feat/riscv branch.

On Debian or Ubuntu, install the additional tools once:

sudo apt-get update
sudo apt-get install -y git make gcc-riscv64-linux-gnu libc6-dev-riscv64-cross
command -v git
command -v make
command -v riscv64-linux-gnu-gcc

Fetch the loader source and build both the RISC-V-aware reference apelink and the RISC-V loader:

export COSMOPOLITAN_RISCV="$HOME/.local/src/rustsbi-cosmopolitan"
mkdir -p "$(dirname "$COSMOPOLITAN_RISCV")"
test -d "$COSMOPOLITAN_RISCV/.git" || git clone --depth 1 --branch feat/riscv https://github.com/rustsbi/cosmopolitan.git "$COSMOPOLITAN_RISCV"
git -C "$COSMOPOLITAN_RISCV" switch feat/riscv
git -C "$COSMOPOLITAN_RISCV" pull --ff-only
make -C "$COSMOPOLITAN_RISCV" -j2 o//tool/build/apelink o/riscv64/ape/ape.elf
test -x "$COSMOPOLITAN_RISCV/o//tool/build/apelink"
test -x "$COSMOPOLITAN_RISCV/o/riscv64/ape/ape.elf"

Compile the same hello.c as a static RISC-V Linux ELF. The explicit 0x400000 load address is required: userspace ELF segments below 2 MiB are rejected by apelink and Polyglink.

riscv64-linux-gnu-gcc \
  -static -no-pie -g \
  -Wl,-Ttext-segment=0x400000 \
  -o hello-riscv64.elf \
  hello.c

Now link all three CPU architectures with Polyglink:

"$POLYGLINK" \
  -G \
  -l "$COSMOCC/bin/ape-x86_64.elf" \
  -l "$COSMOCC/bin/ape-aarch64.elf" \
  -l "$COSMOPOLITAN_RISCV/o/riscv64/ape/ape.elf" \
  -M "$COSMOCC/bin/ape-m1.c" \
  -o hello-polyglink-riscv.com \
  hello-x86_64.elf hello-aarch64.elf hello-riscv64.elf
chmod +x hello-polyglink-riscv.com
./hello-polyglink-riscv.com

It prints the same line on the x86-64 build machine. Copy that exact file to a RISC-V 64 Linux machine and run it again:

export RISCV_HOST="user@your-riscv64-linux-host"
scp hello-polyglink-riscv.com "$RISCV_HOST:/tmp/"
ssh "$RISCV_HOST" 'chmod +x /tmp/hello-polyglink-riscv.com && /tmp/hello-polyglink-riscv.com'

Both executions should print:

Hello from one polyglot binary!

The RISC-V-aware reference apelink can link the exact same inputs. This is the three-architecture compatibility check:

"$COSMOPOLITAN_RISCV/o//tool/build/apelink" \
  -G \
  -l "$COSMOCC/bin/ape-x86_64.elf" \
  -l "$COSMOCC/bin/ape-aarch64.elf" \
  -l "$COSMOPOLITAN_RISCV/o/riscv64/ape/ape.elf" \
  -M "$COSMOCC/bin/ape-m1.c" \
  -o hello-apelink-riscv.com \
  hello-x86_64.elf hello-aarch64.elf hello-riscv64.elf
chmod +x hello-apelink-riscv.com
./hello-apelink-riscv.com

7. Move one file, not a package

Copy hello-polyglink-riscv.com unchanged to another supported machine. Do not recompile it and do not extract it. If you skipped the RISC-V extension, use hello-polyglink.com from step 4 instead.

Platform Run command
Linux x86-64 or AArch64 chmod +x hello-polyglink-riscv.com then ./hello-polyglink-riscv.com
Linux RISC-V 64 chmod +x hello-polyglink-riscv.com then ./hello-polyglink-riscv.com
FreeBSD, OpenBSD, NetBSD chmod +x hello-polyglink-riscv.com then ./hello-polyglink-riscv.com
macOS on Intel chmod +x hello-polyglink-riscv.com then ./hello-polyglink-riscv.com
macOS on Apple Silicon chmod +x hello-polyglink-riscv.com then ./hello-polyglink-riscv.com
Windows x86-64, PowerShell .\hello-polyglink-riscv.com
Windows x86-64, Command Prompt hello-polyglink-riscv.com

Apple Silicon needs the Xcode command-line compiler on the first run because the example deliberately embeds ape-m1.c. Install it with xcode-select --install if macOS asks for it.

Now inspect the same executable as a ZIP archive:

unzip -l hello-polyglink-riscv.com

The file is simultaneously a shell program, native executable container, and ZIP-compatible asset container. That is the polyglot: different operating systems read different, mutually compatible parts of the same byte sequence.

Troubleshooting the demo

  • Run the build commands in a POSIX shell. On native Windows, use WSL for the build and PowerShell or Command Prompt only for the final execution.
  • If Linux has a broken binfmt_misc or Wine association, run sh ./hello-polyglink-riscv.com. The -G example still uses the embedded loader.
  • Zsh should be version 5.9 or newer. Running the APE with sh is also a reliable fallback for older shells.
  • Do not strip the intermediate ELF files before linking. Polyglink uses their symbols and sections to construct the PE, Mach-O, and embedded symbol data.
  • The stable Cosmopolitan download does not yet contain a RISC-V compiler or loader. Use the feat/riscv fork and riscv64-linux-gnu-gcc commands above; riscv64-unknown-cosmo-cc does not currently exist.
  • Keep the RISC-V payload static, non-PIE, and linked at 0x400000. Removing any of those options can produce an ELF that the linker rejects or the APE loader cannot start.
  • The RISC-V payload in this demonstration targets Linux. It does not claim native RISC-V support for BSD, macOS, or Windows.
  • The target program and supported platforms ultimately depend on the Cosmopolitan toolchain used to build the input ELF files. When diagnosing a runtime issue, compare the Polyglink and apelink outputs made from the exact same inputs.

Command-line interface

polyglink -o OUTPUT [OPTIONS] INPUT...
polyglink --help

The short options are compatible with apelink, including repeatable -l PATH loader inputs, -k KERNEL associations, and repeatable -V BITS support vectors.

Acknowledgements

Many thanks to Justine Tunney and all Cosmopolitan contributors for designing the APE format and publishing apelink as open-source software. Polyglink's file-format behavior and compatibility target would not exist without their work.

License

Polyglink is released under the ISC License. See LICENSE.

Because this project is a rewrite derived from Cosmopolitan's ISC-licensed apelink, the original Cosmopolitan and apelink.c copyright and license notices are retained in THIRD_PARTY_NOTICES.md.

About

Rust rewrite of Cosmopolitan's apelink, linking multi-architecture ELF binaries into a single Actually Portable Executable (APE).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages