Minimal Executable Size (Win 32-bit)


#1

Hi!

I’m trying to create a Windows 32-bit exe. For this, I’m trying out a “Hello World” Message box from this gist. It seems that even with -C lto the file size is ~750K. After compression with upx, it’s about 360K.

Are there any ways to reduce the file further?

This is still using llvm, right? Is there a way to use the Visual Studio infrastructure? I’m currenltly on 1.0 stable, but I’m ok with trying out the nightlies.

My Cargo project has this release profile:

[profile.release]
opt-level = 3
debug = false
rpath = false
lto = true
debug-assertions = false
codegen-units = 1

Thanks in advance for your insights!

Cheers,
Kosta


#2

Two older threads about this:

No, you can’t really use MSVC yet. I forget the reason why LTO doesn’t yet strip a Rust executable down as much as it should.

But honestly, why does it matter? It’s not like this is indicative of some multiplicative factor on binary size; it’s the overhead of the standard library. If you compile with prefer-dynamic (forget where exactly to set this), the executable shrinks down dramatically, but then you have to distribute the stdlib DLLs which are probably even bigger.

If you genuinely need to make small executables on the order of 10s of kilobytes, well, you’re probably in the realm of #[no_std], in which case conventional advice no longer applies and you just throw out everything and write only what you actually need.


#3

Hi!

Thanks a lot for your insightful answer and links to the reddit threads!

My use case is: I’m experimenting with replacing a C++ application that is downloaded a lot and thus needs to be rather small in size. The application I’m trying to replace is around 400K including texts in multiple languages, icons etc. I have a harder time justify replacing it with rust if the binary ends up with double the size.

From the two threads you linked, it seems that going with #[no_std] can yield really tiny tiny binaries. Is there a way to selectively link the parts I actually need? I would have expected LTO to do that, but I guess it doesn’t work on the stdlib (yet).

If I want to go down the hard road and go with no_std and use libcore instead, it seems I don’t even have dynamic allocations nor strings. Is there a way to import these “selectively”?

I know I have a kinda special use case there and the rust team probably has more important things to work on at the moment. So in the worst case, I need to find another pet project to really try our rust :slight_smile:

Cheers,
Kosta


#4

libstd, aside from defining a lot of things, also acts as a facade over other, small libraries. For example, libcore, liballoc, libcollections. You can try just linking the sub-std libraries you actually want.

That said, doing so is not explicitly supported, and isn’t possible at all in stable Rust; you’ll need to use a nightly compiler.


#5

Being able to import the different libraries selectively is indeed very cool!.

I would like to try that but it seems I need to use the nightlies for that (stable and beta refuse these “unstable” features). However, the nightly can’t find “ar”:

E:\msgbox>cargo clean && cargo build --verbose --release Compiling msgbox v0.1.0 (file:///E:/msgbox) Running `rustc src\main.rs --crate-name msgbox --crate-type bin -C opt-leve l=3 -C lto --out-dir E:\msgbox\target\release --emit=dep-info,link -L dependency =E:\msgbox\target\release -L dependency=E:\msgbox\target\release\deps` error: could not exec `ar`: The system cannot find the file specified. (os error 2) error: aborting due to previous error Could not compile `msgbox`.

Caused by:
Process didn’t exit successfully: rustc src\main.rs --crate-name msgbox --cra te-type bin -C opt-level=3 -C lto --out-dir E:\msgbox\target\release --emit=dep- info,link -L dependency=E:\msgbox\target\release -L dependency=E:\msgbox\target\ release\deps (exit code: 101)

Is there a way to run a more err… “stable” version of the nightlies? :smile: Preferably one that is identical to 1.0 or maybe 1.1 beta, just with the unstable/experimental stuff enabled. (I tried searching for something like that but failed)

Again, thanks in advance for your insights!

Cheers,
Kosta


#6

Well, you could run the nightly from the same night that stable was built, but it’s the same thing, just without the feature gate stuff. So this AR issue will still happen, I’d guess…unless it was a recently introduced bug.


#7

@steveklabnik Well the same code compiles on stable and beta, so I’d guess it’s a recent bug. Actually, to me it looks like a “file not found” of a crucial tool during linking (or just before or after that). So it must be a rather recent issue…


#8

Yeah, using the nightly from May 16th works fine.
https://static.rust-lang.org/dist/2015-05-16/rust-nightly-i686-pc-windows-gnu.msi

However, I’m not sure I’m using #[no_std] right, as the file size stays the same. On the other hand, my test code uses two strings, so maybe that’s why :smile:

I’ll look into this more tomorrow. Thanks for you help everyone!

Cheers,
Kosta


#9

It might be that it’s #![no_std] (not #[no_std]) and it has to be at the top of the crate root file (so either src/lib.rs or src/main.rs if you’re building a library or binary with cargo). You may also need to remove a feature gate for it, but the compiler should tell you about that one.


#10

@ricky26 Ah thanks, that did the trick :smile:

Here’s an empty message box that is around ~48 KB (~35 KB after upx compression).

However, once I include the “collections” crate, the file size exploded to 660 KB (303 after upx).

Do you think it’s worthwhile copy&pasting the Vec and String parts of the collections crate? I’m not sure if these two are actually the ones blowing up the file size…


#11

Looks like rustc emits debug info even when you don’t ask it to. Try stripping your executable.


#12

I installed MinGW32 from sourceforge and stripped the exe. Now I have the following file sizes when building with stdlib

  • cargo output: 762 KB
  • stripped: 393 KB
  • upx compressed: 139 KB

That is plenty good and I’m happy I can use stdlib and don’t have to descend into madness trying to use Strings and Vec with no_std.

Thanks a lot for noticing, @vadimcn!

Still, what is the right rust command line to not emit debug symbols?

I currently have

Running rustc src\main.rs --crate-name msgbox --crate-type bin -C opt-level=3 -C lto --out-dir E:\msgbox\target\release --emit=dep-info,link -L dependency=E:\msgbox\target\release -L dependency=E:\msgbox\target\release\deps

Or is this a Win32 bug I should file?

Thank you all your help!


#13

Now I tried to run this exe on another computer. I errors out saying it cannot find some libgcc dll. This happens also for the non-stripped, non-upx-compressed file.

Any ideas? I would like to create a redistributable file that can run on any Windows computer.

(Edit: Sorry that my google-fu is so poor with rust. But I couldn’t find anything related to this issue)


#14

Oh yeah… Rust needs that for unwinding support on Windows. There’s no way around it aside from moving to MSVC (which you can’t, just yet).

And don’t forget, if you distribute that file, you have to also distribute the license and make the source code available to anyone who might request it. I forget which license (it’s one of the *GPLs, probably GPL3 with runtime exception).

Additionally, I believe that, due to the way it works, you can’t statically link it, either. Or that might have just been legally impossible… it’s been a while since I tried to work this stuff out and got multiple, contradictory answers on the subject. Edit: see comment below.

Have fun! :smiley:


#15

There’s no way around it aside from moving to MSVC (which you can’t, just yet).

Well, you can link libgcc statically as well: `rustc -Clink-args=-static-libgcc. This breaks cross-module stack unwinding, but in this case you probably don’t care.
However, this will also add ~1MB of libgcc code into your executable, because on Windows ld does not support dead code removal (actually, I’m not sure about the ratio of live/dead code we link from libgcc).

There’s been a bunch of discussion about adding compilation mode that turns panics into aborts (thus removing the need for stack unwinding), but nothing like that has been implemented yet.

And don’t forget, if you distribute that file, you have to also distribute the license and make the source code available to anyone who might request it. I forget which license (it’s one of the *GPLs, probably).

Not quite… libgcc is one of the libraries covered by the linking exception clause, so you are exempted from such requirements for that one.


#16

I went back and re-read the license, but again, I don’t see anything that implies that libgcc is exempt from the distribution conditions. The output of the compiler is, but libgcc isn’t “Target Code”, though any parts of libgcc that end up inside your executable are.

Also there’s an old thread on the GCC mailing list where the answer appears to be a fairly strong: libgcc is not exempt.

Unless I see something from an actual FSF lawyer explicitly saying otherwise, I will continue to operate under the assumption that distributing libgcc without making the source available is illegal.


#17

Also there’s an old thread on the GCC mailing list…

The rationale for creating the exception was “to allow developers to use GCC’s libraries to compile any program, regardless of its license”, so I’ll take that for an answer. Also, FAQ entry #6 answers the question about static vs dynamic linking quite clearly (that is, there is no difference).


#18

Ok, I tried building with -Clink-args=-static-libgcc.

This increases the file size to:

  • Cargo build: 875 KB
  • stripped: 414 KB
  • upx compressed: 148 KB

This is quite acceptable. (Not sure about the GPL implications though, as I’m paid to write close source software - but let’s keep this out of the thread)

The file seems to run on Windows Vista and Windows 8.1.

But it doesn’t run on Windows XP, I get this dialog saying that AquireSRWLockExclusive could not be located in kernel32.dll. Well, it seems it’s not supported…

That is too bad, I don’t think I can do without Windows XP for this project (yeah I know I’ve got some weird requirements…).

Well, I guess tomorrow I’ll try running with no_std again and see if that yields the same error.

Again, thank you all for your really great support!

Cheers,
Kosta


#19

Rust doesn’t support XP generally yet, though we do plan to in the future.


#20

Just to make sure we’re both on the same page here:

(Emphasis mine.)

So static linking: you’re free and clear. Dynamic linking: have fun with that.