151-byte static Linux binary in Rust(mainisusuallyafunction.blogspot.com)
mainisusuallyafunction.blogspot.com
151-byte static Linux binary in Rust
http://mainisusuallyafunction.blogspot.com/2015/01/151-byte-static-linux-binary-in-rust.html
9 comments
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.)
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
¹ 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.
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.
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.
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!
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?
http://doc.rust-lang.org/book/unsafe.html#avoiding-the-stand... is the closest we have, currently. https://github.com/rust-lang/rust/wiki/Operating-system-deve... has a bunch of stuff, and https://github.com/charliesome/rustboot and its forks are examples that I know of.
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
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.
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.
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 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.
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?
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...
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...
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).
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
? 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.
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.
oops, sorry, downvote brigade. :)
I did something similar in D/Win32:
http://forum.dlang.org/post/[email protected]...
The PE format is rather bulkier than ELF, though.
http://forum.dlang.org/post/[email protected]...
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:
On Linux, with dynamic linking:
rustc: 8656 bytes
clang: 6960 bytes
gcc: 6688 bytesAh 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).
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...