Hacker News new | past | comments | ask | show | jobs | submit login
151-byte static Linux binary in Rust (mainisusuallyafunction.blogspot.com)
191 points by adito on Jan 11, 2015 | hide | past | favorite | 57 comments



reminds me a bit of the "Creating Really Teensy ELF Executables" writeup[1], which got down to 45 bytes.

But honestly... does it really matter how small your executable is? I'd care a lot more about performance characteristics than binary size.

[1] http://www.muppetlabs.com/~breadbox/software/tiny/teensy.htm...


Cache Rules Everything Around Me, and bloated executables generally put more stress on the instruction cache, resulting in poorer performance.

Of course, this is just an approximate general rule, and extremely small binaries (and generally any "pointless" experimentation) like this are mainly interesting from a learning point of view (or for small embedded microcontrollers), e.g. understanding syscalls and program start-up, and, for the write-up you link, the ELF format. (Not everything has to be inherently useful for an actual task.)


I'm going to have to steal your opening quote, for times when I have to keep it real*8. Source?


No idea where I first heard it; I have a feeling it was on IRC from the author of the article, coincidentally.


To play the advocate, given that by default compilation produces a binary that includes over 2KB of Lovecraft quotes¹, this article at least serves to reassure people that you can produce a compact binary with Rust.

¹ https://github.com/rust-lang/rust/issues/13871


It may matter in some embedded applications (for which chipsets there is not even Rust compiler support yet) but the point is to show off how much low-level control you have over code-generation, and that no run-time is needed.


I'd care a lot more about performance characteristics than binary size

The key word here is I'd. The programming world is vast, and the range of hardware is likewise vast. So, so vast. Really huge. Pick a restriction you don't have to worry about; someone else out there does.


> does it really matter how small your executable is?

It demonstrates how much control you can choose to exert over your compiled program.


These exercises may lack practical value, except perhaps to the virus makers, but they do kind of prove there is no extraneous overhead required by the language.

So we already knew that about assembler, now we also know Rust can be twisted quite close to that too.


This is cute, but is there a way to actually create static binaries with the standard tooling and not jumping through hoops?

Hello World in Rust with tip of tree (as of yesterday) is a ~500K binary that depends on glibc and friends.

Switching from println!() to std::io...write_str() actually made it larger!


You can't static link glibc, so our static binaries still have a dynamic dependency on it.


Is there a process for making a fully static binary (say if I were to substitute an alternate libc, or provide my own low level wrappers for syscalls, or whatnot)?


Yes, that's totally possible. You can write a kernel in Rust if you want.


...is it documented anywhere? where?



The example minimal do-nothing program there is much smaller (8k), but still results in a dynamic binary requiring glibc.

The only unresolved symbol is: __libc_start_main@@GLIBC_2.2.5


I believe musl supports static linking


Any reason one couldn't use musl instead for static linking? (http://www.musl-libc.org/)


and for many, there are legal issues with statically linking glibc due to its license.


I'd actually love to ask Linus' on his opinion of the practical uses of Rust in the Linux kernel. Does he think it could have a place, if not, why? Can those issues be addressed, etc?

Even if that means him dismissing me/my question entirely lol.


I gather LLVM has too few backends, and fewer quality ones, to be suitable for such a general purpose OS.

As I recall Rust is currently targeting only Intel and ARM backends.

And it's way too early to see if it'll make it.


Rust seems very promising language , but why on earth should any one use rust in kernel development ? as far as I see this link is just calling write system call from rust program (which is very good) , but don't have anything to do with linus or kernel internal. and if you mean some thing like scripting in kernel ( i think openbsd ddne it with lua ) there is more viable and simpler choice than rust , like lua.


> but why on earth should any one use rust in kernel development

Because that's explicitly one of the things Rust was developed for.

Here's a whole list of kernels and other OS projects that have been created using Rust:

https://github.com/rust-lang/rust/wiki/Operating-system-deve...


Because it allows (in some situations) dynamic memory allocation where the compiler can reason about when the memory becomes unused, thus guaranteeing that it's freed. In other words, you get to use algorithms which allocate off the heap without risking a memory leak. Plus a whole bunch of other useful features like Lisp-style structural macros, parametric polymorphism, compile time bounds checking, and other awesome stuff stolen from the functional programming world.


That main() can still be improved... try this:

    push 1
    pop eax
    mov edi, eax
    mov esi, 400008h
    push 7
    pop edx
    syscall
    push 3ch
    pop eax
    xor edi, edi
    syscall
It should be 10 bytes shorter.


Yes, but the point was that it could be done in rust, using rust's language features (type safety and so forth). Making your main inline assembly isn't really using the language very much.


From the Github page: https://github.com/kmcallister/tiny-rust-demo

All of the machine code comes out of rustc. (Although all of the instructions that survive optimization went in as inline assembly.)

So while his main() is written in (unsafe) Rust, he's basically using it as a macroassembler.


You really should have a look at the rust code this guy wrote. I would not call that last optimization type safe.


How so? The only unsafe part is the transmute which only saves 7 bytes anyways :P


Even that is still type safe, just not portable. After all, every part of the memory is a collection of bytes (that is, a slice of u8) – whether you can access them is another story.


LLVM currently does not optimize for size, so things like the small-integer push/pop trick aren't goign to happen.


Wow. This is seriously impressive. Rust looks like it actually might be capable as a systems programming tool to match C. I have to learn more about it now! :)


dude its great , but not that good, at least not yet .


Care to elaborate why do you think so?


Well for one: at least currently, there's a much better chance of having the c/c++ runtimes on a system than the Rust one I imagine.


You don't need a Rust runtime installed, you just need a glibc.


Even if you don't statically link Rust's runtime?

If so I can see that working for very simple code, but what about wanting to use Tasks/Threads or do I/O. Is that converted to native system calls at compile time?


After a series of recent changes, Rust's runtime has been radically reduced. I/O goes through system calls, and the threading system is just native OS threads. There's a very small amount of support needed for the "Rust runtime", including things like stack guards, but it's much smaller than it was.

The RFC describing the changes is [^1] and the actual commit that finalized them is [^2].

[^1]: https://github.com/rust-lang/rfcs/pull/230

[^2]: https://github.com/rust-lang/rust/commit/0efafac398ff7f28c5f...


You can create freestanding binaries, but you need to implement more things on your own. Check out this page for some interesting projects: https://github.com/rust-lang/rust/wiki/Operating-system-deve...


AFAIK you can compile static binaries with Go and thus eliminate the dependencies.


Oops, apparently I was a little fast there. Apologies!

(Edit: I mistook this for a Go thread.)


That is really impressive. Now for comedy, someone find the size of a similar program in Go.


> Now for comedy, someone find the size of a similar program in Go

As far as I know, Go statically links the runtime into the binary (including the garbage collector, the scheduling system and more)... so for a small program like this sample, it will appear to be abnormally bloated. If it were a larger, more complex example, then the binary size wouldn't change as drastically (and on a surface level might appear to be more justifiable).


It is also bad in following dependencies, and links the whole libraries. The result is like spaghetti, import on library and half of the standard libraries come with it. Some optimizations have been already done to prevent that, but the work is going to be still in progress for several versions.


On the other hand, rust has no standard library ("batteries") by explicit choice, so once the ecosystem will evolve, there would likely be multiple json or http libraries (just to name "low-level" libraries with widespread usage), and you might end up linking a few of them in a binary as indirect dependency.


> On the other hand, rust has no standard library

? http://doc.rust-lang.org/std/

> there would likely be multiple json or http libraries (just to name "low-level" libraries with widespread usage)

The standard library used to build json in, that was actually split out to a standalone libraries for various reasons: https://github.com/rust-lang/rustc-serialize

> and you might end up linking a few of them in a binary as indirect dependency.

That's not a counter-argument to erglkjahlkh's comment. In fact it's pretty much irrelevant:

* Go works at a library level, when you import fmt you don't import encoding/json, and it's not loaded

* The issue he outlines is mostly that Go does little to no dead code elimination, when a library is imported all of its content (and the content of its dependencies) will end up in the final library, even if you only access a small utility function


> Go does little to no dead code elimination

Yet. With the rewrite of the compiler it is safe to assume that there will be many optimizations to come.


Sadly no: one of the primary (if not the primary) goal of Go's developers is compilation speed, and with Go being intrinsically slower than C the post-rewrite available budget for possible optimisations (whatever the acceptance threshold they define) will be lower rather than higher.


Perhaps I'm naive, but I don't see why they couldn't have the option of something like -O3 for those that wanted it.


Because they don't want to.


oops, sorry, downvote brigade. :)


I did something similar in D/Win32:

http://forum.dlang.org/post/qcbicxrtmjmwiljsyhdf@forum.dlang...

The PE format is rather bulkier than ELF, though.


I just had a look. Installed nightly Rust on OSX and ran through it a hello world program - 307K! HW in C with gcc 4.2.1 (LLVM) makes 8.7K.


Rust produces statically linked binaries by default, while GCC and Clang default to dynamic linking. For a better comparison, pass `-static` to GCC/Clang, or pass `-C prefer-dynamic` to rustc.

On Linux, with dynamic linking:

  rustc: 8656 bytes
  clang: 6960 bytes
  gcc:   6688 bytes


Ah of course, thanks! That makes sense. Since I'm on OSX and -static doesn't work with GCC (libs provided aren't static) - I'll believe it instead. Rustc is now 9K, which is really close to 8.7 of GCC.


Is it possible to make actual static binaries in rust? By default it compiles in the rust libraries, but depends on glibc and friends (and is not actually a static binary) -- at least with tip of tree rust from yesterday.


You can drop the glibc dependency pretty easily. One way (used for a lot of toy kernels that have been written in Rust) is to add the #![no_std] attribute and then explicitly pull in any static dependencies you still use (like libcore).




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: