Generating small debug builds

I am currently trying to build a minimal binary that does not agressively optimize my code (e.g. ReleaseSmall is not applicable).

pub fn fib(n : usize) usize{
    if(n == 0){
        return 0;
    }
    if (n == 1 or n == 2){
        return 1;
    }
    return fib(n-1) + fib(n-2);
}

pub fn main() void {
    _ = fib(10);
}

Notice that i am not importing ‘std’ anywhere to not bloat my executable with code from the stdlib. And while zig build -Doptimize=ReleaseSmall leads to a 5KB binary, doing zig build -Doptimize=Debug leads to a whopping 756KB binary. (edit: the target in question is x86_64_windows, i am not sure how it behaves for linux builds)

I strongly believe that this is due to the runtime-safety functions included in Debug (and ReleaseSafe) builds.

I have done some research into my problem. The build function “addExecutable” does not seem to offer a runtime safety flag.(Zig Documentation).

I think the flag is set here zig/lib/std/debug.zig at 5d2bf96c02b766a6ddeb3bd0e8594f2c70c9bfb6 · ziglang/zig · GitHub but am not entirely sure.

Is there a possibility to globally disable runtime safety checks and still have a “Debug” style binary (meaning without optimizations and unstripped)?

Hello @Cloudst
welcome to ziggit :slight_smile:

You need to use the ReleaseSafe build mode: zig build -Doptimize=ReleaseSafe

The difference is mainly debugging symbols.

If you want to turn off the runtime safety features you can use the ReleaseSmall or the ReleaseFast build mode.

1 Like

Hello @dimdin i appreciate that you responded to my post!

Lets tackle the first two things at once:

With ReleaseSafe the binary is still 500KB in size (as opposed to 5KB on ReleaseSmall. This difference might indeed be due to debugging symbols, but there is still 495KB i want to get rid of. And the main part of the added size is still the runtime-safety functions compiled into the code.

Note that both ReleaseFast and ReleaseSmall indeed yield small binaries, but they are (by default) optimized (i assume it is something like -O3 and -Os flags respectively). Side question: Can i somehow manually change the optimization level passed to the compiler? I reckon that i could do what i want with just omitting the -Os from the command line in a ReleaseSmall build (and adding -fno-strip).

That is entirely correct. However i am looking for the equivalent of a Debug build in terms of optimization that does NOT include the runtime safety features. (see my original question below)

Another idea i had is manually rebuilding the zig binary and editing the runtime safety flag to be false for Debug builds. But i feel like this is not really a “sustainable” solution.

The extra size comes from the code for formatting and stack trace display functionality.
If you don’t want that extra code just use ReleaseSmall.

Welcome to Ziggit @Cloudst!

Why do you want this, in particular?

You can configure runtime safety on a per-scope level by setting @setRuntimeSafety(false). My guess, having never tried it, is that the runtime safety code will still be included in the binary, even if every scope is set to false, but you’re welcome to try it out.

1 Like

Hi @mnemnion!

I have tried your suggestion and it does indeed what you suspected, the overall runtime-safety code is still included in the binary.

As to why i want that particular thing: I was trying to write some beginner CTF challenges in zig. And it can be quite overwhelming for beginners if the binary is almost 1MB in size with hundreds of functions that are not really relevant to the reversing task at hand (on Debug).

On the other hand it is also quite challenging to understand a function that was optimized by the compiler, as they take certain shortcuts that make no intuitive sense but eventually lead to the same output (on ReleaseFast or ReleaseSmall). The pseudocode for the fib function above is reduced to:

int64_t i = arg1;
void* rbx = &data_402108[arg1]; // where uint64_t data_402108 = {0, 1, 1};
int64_t rsi = 0;
    
for (; i > 2; i -= 2)
{
rsi += fib((i - 1));
rbx -= 0x10;
}

return (rsi + *(uint64_t*)rbx);

From which it would be fairly hard to recover the original fib code.

But it seems that for the time being, i am stuck with C for those challenges as it gives me more control over the build process in this particular regard (ironically, because i love zigs build system).

Another idea i had would be, throwing out the -O2 in this line zig/src/Compilation.zig at cf90dfd3098bef5b3c22d5ab026173b3c357f2dd · ziglang/zig · GitHub
However this still would make it necessary to build zig from source, which i want to avoid :smiley:

Upon further investigation, even patching out the -Os and -O2 from the zig binary does not do the trick (desperate attempt at making it work). I think at this point i have to give up and wait until there is a possibility to make custom optimization targets (e.g. DebugUnsafe or something along these lines).

1 Like

Ok, that does make sense.

You may be able to achieve the effect you’re looking for using the right combination of linker flags. Something to try is -ffunction-sections, which isolates each function in its own section, and then --gc-sections to remove unreachable sections. In combination with @setRuntimeSafety(false) this might make the safety-checking code unreachable and therefore removed.

You might be waiting a long time. I suspect that particular permutation of options is not a maintenance burden which the compiler team wants to take on for every platform for all time.

But there are a lot of flags to work with, and the right combination of them might achieve the effect you’re going for.

The proposed combination of flags does not work. While the ordering of the runtime code is changed, the overall binary size stays the same :frowning:

I think i will have to give up for now. Thank you both for your input on the matter!
To not keep a (likely) dead topic open, i will mark the last reply as the solution.

Perhaps in the future i have to look into how the zig compilation works and see whether i can somehow tweak the process to my desires, its open source after all.

2 Likes