Hacker News new | ask | show | jobs
by MuffinFlavored 805 days ago
I got bored the other day and tried to achieve something similar on MacOS with Rust:

    #![no_std]
    #![no_main]
    
    use core::panic::PanicInfo;
    
    #[panic_handler]
    fn panic_handler(_panic: &PanicInfo<'_>) -> ! {
        // TODO: write panic message to stderr
        write(2, "Panic occured\n".as_bytes()); // TODO: panic location + message
        unsafe { sc::syscall!(EXIT, 255 as u32) };
        loop {}
    }
    
    fn write(fd: usize, buf: &[u8]) {
        unsafe {
            sc::syscall!(WRITE, fd, buf.as_ptr(), buf.len());
        }
    }
    
    #[no_mangle]
    pub extern "C" fn main() -> u32 {
        write(1, "Hello, world!\n".as_bytes());
        return 0;
    }
Then I inspected the ELF output in Ghidra. No matter what it was about ~16kb. I'm sure some code golf could be done to get it done (which has obviously been done + written about + documented before)
6 comments

A similar program on Windows is 3072 bytes. I compiled it using:

    rustc hello.rs -C panic=abort -C opt-level=3 -C link-arg=/entry:main
Here's the program:

    #![no_std]
    #![no_main]

    #[panic_handler]
    fn panic_handler(_panic: &core::panic::PanicInfo<'_>) -> ! {
        unsafe { ExitProcess(111) };
    }

    #[no_mangle]
    pub extern "C" fn main() -> u32 {
        let msg = b"Hello, world!\n";
        unsafe {
            let stdout = GetStdHandle(-11);
            let mut written = 0;
            WriteFile(
                stdout,
                msg.as_ptr(),
                msg.len() as u32,
                &mut written,
                core::ptr::null_mut(),
            );
        }
        0
    }

    #[link(name = "kernel32")]
    extern "system" {
        fn ExitProcess(uExitCode: u32) -> !;
        fn GetStdHandle(nStdHandle: i32) -> isize;
        fn WriteFile(
            hFile: isize,
            lpBuffer: *const u8,
            nNumberOfBytesToWrite: u32,
            lpNumberOfBytesWritten: *mut u32,
            lpOverlapped: *mut (),
        ) -> i32;
    }
I didn't bother much with the panic handler because there's no reason to in hello world. Though the binary contains a fair bit of padding still so it could have a few more things added to it without increasing the size. Alternatively I could shrink it a bit further by doing crimes but I'm not sure there's much point.

It may be worth noting that the associated pdb (aka debug database) is 208,896 bytes.

Ok, I committed some mild linker crimes and got the same program down to 800 bytes.

   rustc hello.rs -C panic=abort -C opt-level=3 -C link-args="/ENTRY:main /DEBUG:NONE /EMITPOGOPHASEINFO /EMITTOOLVERSIONINFO:NO /ALIGN:16"
Cool. That reminded me of this blog post[0], with Hello World Nim binary in just 150 bytes!

[0] - https://hookrace.net/blog/nim-binary-size/

Don't x64 binaries have to be 4k-aligned or something like that? I remember a video about runnable qr codes where this was a major point because they had to do trickery to make windows run binaries that were less than that.
To achieve the smallest size, you have to ditch main entirely and use _start, along with passing certain linker flags not to align sections. See https://darkcoding.net/software/a-very-small-rust-binary-ind...

You can easily get around 500 bytes this way

it's fun to golf, but how big are pages these days, anyway?

(and if you're using a language with a stack, your executable probably ultimately loads as at least two pages: r/o and r/w)

16kB on a Mac, so that was probably what they were running into. You effectively can’t make a program whose code occupies less than 16kB.
The min-sized-rust project [1], which you may already be aware of, has a lot of optimisations to decrease the size of a Rust binay. I think with all the optimisations, at the end, it was around 8kb for the hello world.

[1]: https://github.com/johnthagen/min-sized-rust

XNU will not load any Mach-Os that are smaller than a page, so unfortunately there is not much fun to be had on that platform.
Rust or not, 16kb is entirely satisfactory. I mean, it fits in a Commodore 64's memory!
Compared to a Hello World program on C64 written in assembler probably not. ;)

I need roughly 24 bytes. 16kb means 16.384 bytes. I can think of better usage of $4000 space in memory, FLI for example.

The majority of these 16384 bytes could be zeroes. They fill a single page, and if so, the only use you could make of those other ~16000 bytes would be to fill it with other code and/or read-only data for the same program, which just wasn't done here.

In that sense, they are not "lost", just merely "unused".

So now it would be interesting to see how much of the 16kB were not padding, though. OP said they looked at it with Ghidra, so did they see 16kB of actual code?