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
, andnasm
are 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-upx
binary packed withupx --brute
produced no output, so it usesupx --best
instead.upx
fails withNotCompressible
when run againstc-musl
.upx
refuses to compress any of theasm
binaries.- Rust optimization options were borrowed from Minimizing Rust Binary Size.
- Rust binaries packed with
upx --brute
produced no output, so they useupx --best
instead. - Rust nightly has
cargo-features = ["strip"]
but it is not available in stable, so I usedstrip -s
instead. - As of 2021-12-30, Rust nightly fails to build static binaries with
warnings about
getpwuid_r
andgetaddrinfo
in statically linked binaries. SeeDockerfile
for full error. - The
opt-nostd
build fails in Rust 1.57 and Rust nightly as of 2021-12-31. SeeDockerfile
for full error. - ELF/PH overlap and unverified byte regions used by
asm-elf
were 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-binaries
You 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 *