Tiny static x86-64 Linux binaries which do the following:
- print
"hi!\n"to standard output. - exit with an exit code of
0.
The binaries were generated using a variety of languages and build methods, then sorted by size.
The results from this test were used in the following posts on my site:
Plot of all results (Note: log scale X axis):
Plot of tiny results (less than 1k, linear scale):
Raw size results are available in the out/ directory in
CSV format.
Generated using the following languages and build methods:
| Name | Language | Description |
|---|---|---|
go-1.16-default |
Go 1.16 | Default build options. |
go-1.16-ldflags |
Go 1.16 | Built with -ldflags="-s -w". |
go-1.16-default-upx |
Go 1.16 | Built with defaults and packed with upx. |
go-1.16-ldflags-upx |
Go 1.16 | Built with -ldflags="-s -w" and packed with upx. |
go-1.17-default |
Go 1.17 | Default build options. |
go-1.17-ldflags |
Go 1.17 | Built with -ldflags="-s -w". |
go-1.17-default-upx |
Go 1.17 | Built with defaults and packed with upx. |
go-1.17-ldflags-upx |
Go 1.17 | Built with -ldflags="-s -w" and packed with upx. |
go-1.18rc1-default |
Go 1.18rc1 | Default build options. |
go-1.18rc1-ldflags |
Go 1.18rc1 | Built with -ldflags="-s -w". |
go-1.18rc1-default-upx |
Go 1.18rc1 | Built with defaults and packed with upx. |
go-1.18rc1-ldflags-upx |
Go 1.18rc1 | Built with -ldflags="-s -w" and packed with upx. |
tinygo-0.22-default |
TinyGo 0.22.0 | Default build options. |
tinyGo-0.22-nodebug |
TinyGo 0.22.0 | Built with -no-debug. |
tinygo-0.22-default-upx |
TinyGo 0.22.0 | Built with defaults and packed with upx. |
tinygo-0.22-ldflags-upx |
TinyGo 0.22.0 | Built with -no-debug and packed with upx. |
rust-1.57-default |
Rust 1.57 | Built with --release and build.rustflags = "-C target-feature=+crt-static". |
rust-1.57-default-upx |
Rust 1.57 | Built with --release, build.rustflags = "-C target-feature=+crt-static", and packed with upx. |
rust-1.57-abort |
Rust 1.57 | Built with --release and profile.release.panic = "abort". |
rust-1.57-abort-upx |
Rust 1.57 | Built with --release, profile.release.panic = "abort", and packed with upx. |
rust-1.57-strip |
Rust 1.57 | Built with --release and build.rustflags = "-C target-feature=+crt-static", then stripped with strip. |
rust-1.57-strip-upx |
Rust 1.57 | Built with --release and build.rustflags = "-C target-feature=+crt-static", then stripped with strip and packed with upx. |
rust-1.57-lto |
Rust 1.57 | Built with --release, build.rustflags = "-C target-feature=+crt-static", and profile.release.lto = true. |
rust-1.57-lto-upx |
Rust 1.57 | Built with --release, build.rustflags = "-C target-feature=+crt-static", and profile.release.lto = true, then packed with upx. |
rust-1.57-oz |
Rust 1.57 | Built with --release, build.rustflags = "-C target-feature=+crt-static", and profile.release.opt-level = "z". |
rust-1.57-oz-upx |
Rust 1.57 | Built with --release, build.rustflags = "-C target-feature=+crt-static", profile.release.panic = "abort", and profile.release.opt-level = "z", then packed with upx. |
rust-1.57-all |
Rust 1.57 | Built with --release, build.rustflags = "-C target-feature=+crt-static", profile.release.opt-level = "z", profile.release.panic = "abort", and profile.release.lto = true, then stripped with strip. |
rust-1.57-all-upx |
Rust 1.57 | Built with --release, build.rustflags = "-C target-feature=+crt-static", profile.release.opt-level = "z", profile.release.panic = "abort", and profile.release.lto = true, then stripped with strip and packed with upx. |
rust-1.59-default |
Rust 1.59 | Built with --release and build.rustflags = "-C target-feature=+crt-static". |
rust-1.59-default-upx |
Rust 1.59 | Built with --release, build.rustflags = "-C target-feature=+crt-static", and packed with upx. |
rust-1.59-abort |
Rust 1.59 | Built with --release and profile.release.panic = "abort". |
rust-1.59-abort-upx |
Rust 1.59 | Built with --release, profile.release.panic = "abort", and packed with upx. |
rust-1.59-strip |
Rust 1.59 | Built with --release and build.rustflags = "-C target-feature=+crt-static", then stripped with strip. |
rust-1.59-strip-upx |
Rust 1.59 | Built with --release and build.rustflags = "-C target-feature=+crt-static", then stripped with strip and packed with upx. |
rust-1.59-lto |
Rust 1.59 | Built with --release, build.rustflags = "-C target-feature=+crt-static", and profile.release.lto = true. |
rust-1.59-lto-upx |
Rust 1.59 | Built with --release, build.rustflags = "-C target-feature=+crt-static", and profile.release.lto = true, then packed with upx. |
rust-1.59-oz |
Rust 1.59 | Built with --release, build.rustflags = "-C target-feature=+crt-static", and profile.release.opt-level = "z". |
rust-1.59-oz-upx |
Rust 1.59 | Built with --release, build.rustflags = "-C target-feature=+crt-static", profile.release.panic = "abort", and profile.release.opt-level = "z", then packed with upx. |
rust-1.59-all |
Rust 1.59 | Built with --release, build.rustflags = "-C target-feature=+crt-static", profile.release.opt-level = "z", profile.release.panic = "abort", and profile.release.lto = true, then stripped with strip. |
rust-1.59-all-upx |
Rust 1.59 | Built with --release, build.rustflags = "-C target-feature=+crt-static", profile.release.opt-level = "z", profile.release.panic = "abort", and profile.release.lto = true, then stripped with strip and packed with upx. |
c-glibc |
C | Statically linked against glibc. |
c-glibc-upx |
C | Statically linked against glibc and packed with upx. |
c-musl |
C | Statically linked against musl. |
c-asm |
C (w/inline asm) | This is just the asm-opt assembly, ported to horrid gas syntax, and embedded in a largely pointless C wrapper. |
asm-naive |
Assembly | Unoptimized x86-64 assembly, built with nasm and linked with ld. |
asm-opt |
Assembly | Optimized x86-64 assembly, built with nasm. |
asm-elf |
Assembly | Optimized x86-64 assembly, built with nasm. Code is embedded in unverified portions of the ELF and program header. |
Notes:
- Debian only packages Go 1.15.15 as of December 2021. Yeesh.
- Versions of
gcc,upx,ld,strip, andnasmare from the current version of Debian. - C compiled with GCC 10. I also tested with Clang, but the results did not appear to be substantially different.
c-glibc-upxbinary packed withupx --bruteproduced no output, so it usesupx --bestinstead.upxfails withNotCompressiblewhen run againstc-musl.upxrefuses to compress any of theasmbinaries.- Rust optimization options were borrowed from Minimizing Rust Binary Size.
- Rust binaries packed with
upx --bruteproduced no output, so they useupx --bestinstead. - Rust nightly has
cargo-features = ["strip"]but it is not available in stable, so I usedstrip -sinstead. - As of 2021-12-30, Rust nightly fails to build static binaries with
warnings about
getpwuid_randgetaddrinfoin statically linked binaries. SeeDockerfilefor full error. - The
opt-nostdbuild fails in Rust 1.57 and Rust nightly as of 2021-12-31. SeeDockerfilefor full error. - ELF/PH overlap and unverified byte regions used by
asm-elfwere borrowed from Tiny ELF Files: Revisited in 2021. Thanks Nathan!
There is a top-level Dockerfile which you can use to generate all
of the binaries, the output CSV, and the output SVGs.
# build all stages
docker build -t pablotron/tiny-binaries .You can save the generated CSV and SVGs to an output directory like so:
# create output directory and set permissions
mkdir ./out && chmod 777 ./out
# bind mount output directory, then copy generated reports to output directory
docker run --rm -it -v $(pwd)/out:/out pablotron/tiny-binariesYou can inspect the generated binaries in /data/bin like this:
# execute shell in container
docker run --rm -it pablotron/tiny-binaries sh
# switch to output binary directory
cd /data/bin
# install file
apk add file
# verify that binaries are statically linked
file *
# verify binary file sizes
wc -c *