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).
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.
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.
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).
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).
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
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.