5 ms·
You can make CRT-free Win32 programs, read this guide[1] and you're all set. I've written a couple CLI utilities which are completely CRT-free and weigh just un
by rfl890 11mo ago
You can make CRT-free Win32 programs, read this guide[1] and you're all set. I've written a couple CLI utilities which are completely CRT-free and weigh just under a few kilobytes.
[1]: https://nullprogram.com/blog/2023/02/15/ https://nullprogram.com/blog/2023/02/15/
- forrestthewoods 11mo agoGreat post!
- matheusmoreira 11mo agoAlmost freestanding. It still requires you to link against kernel32 and use the functions it provides. This is because issuing system calls directly to the Windows kernel is not supported. The kernel developers reserve the right to change things like system call numbers, so they can't be hardcoded into the application.
- forrestthewoods 11mo ago> Almost freestanding. It still requires you to link against kernel32 Nitpick: the phrase “link against kernel32” feels like a Linux-ism. If you’re only calling a few function you need to load kernel32.dll and call some functions in it. But that’s a slightly different operation than linking against it. At least how I’ve always used the term link. You’re not wrong in principle. But Linux and Windows do a lot of things differently wrt linking and loading libs. (I think Windows does it waaay better but ymmv)
- matheusmoreira 11mo agoLoading means creating a memory image of the library. Linking means resolving the symbols to addresses within that memory image. Loading a library and calling some functions from it is linking. The function pointer you receive is your link to the library function.
- forrestthewoods 11mo agoYou’re not wrong per se. But it was phrased in a very linuxy way imho. > Linking means resolving the symbols to addresses within that memory image. Well, you can call LoadLibrary and GetProcAddress. Which is arguably linking. But does not use the linker at link time. Although LoadLibrary is in kernel32!
- 1718627440 11mo agoLinker is short for Link Loader, so I don't now what your definition of linking is, if it doesn't include loading.
- 1718627440 11mo ago> (I think Windows does it waaay better but ymmv) Can you elaborate on that? Btw., I don't want to bash Windows here, I think the Windows core OS developers are (one of) the only good developers at Microsoft. The NT kernel is widely praised for its quality and the actual OS seems to be really solid. They just happen to also have lots of shitty company sections that release crappy software and bundle malware, ads and telemetry with the actual OS.
- forrestthewoods 11mo agoWindows 11 Pro with O&O Shutup is perfectly fine. You’re not wrong and the trend is concerning. But on the actual topic. I think “Linux” does a few things way worse. (Technically not Linux but GCC/Clang blah blah blah). Linux does at least three dumb things. 1) Treat static/dynamic linking the same 2) No import line 3) global system shared libraries. All three are bad. Shared/dynamkc libraries should be black boxes. Import libs are just objectively superior to the pure hell that is linking an old version of glibc. And big ball or global shared libraries is such a catastrophic failure that Docker was invented to hack around it.
- 1718627440 11mo agoCan you write that so, that people who are dumb and don't know the Windows way also get it?
- forrestthewoods 11mo ago> Treat static/dynamic linking the same Imagine you have an executable with a random library that has a global variable. Now you have a shared/dynamic library that just so happens to use that library deep in its bowels. It's not in the public API, it's an implementation detail. Is the global variable shared across the exe and shared lib or not? On Linux it's shared, on Windows its not. I think the Windows way is better. Things randomly breaking because different DLLs randomly used the same symbol under the hood is super dumb imho. Treating them as black boxes is better. IMHO. YMMV. > No import lib (typo! lib, not line) In Linux (not the kernal blah blah blah) when you link a shared library - like glibc - you typically link the actual shared library. So on your build machine you pass /path/to/glibc.so as an argument. Then when your program runs it dynamically loads whatever version of glibc.so is on that machine. On Windows you don't link against foo.dll. Instead you link against a thin, small import lib called (ideally) foo.imp.lib. This is better for a few reasons. For one, when you're building a program that intends to use a shared library you shouldn't actually require a full copy of that lib. It's strictly unnecessary by definition. Linux (gcc/clang blah blah blah) makes it really hard to cross-compile and really hard to link against older versions of a library than is on your system. It should be trivial to link against glibc2.15 even if your system is on glibc2.40. > global system shared libraries The Linux Way is to install shared libraries into the global path. This way when openssl has a security vuln you only need to update one library instead of recompile all programs. This architecture has proven - imho objectively - to be an abject and catastrophic failure. It's so bad that the world invented Docker so that a big complicated expensive slow packaging step has to be performed just to reliably run a program with all its dependencies. Linux Dependency Hell is 100x worse than Windows DLL Hell. In Windows the Microsoft system libraries are ultra stable. And virtually nothing gets installed into the global path. Computer programs then simply include the DLLs and dependencies they need. Which is roughly what Docker does. But Docker comes with a lot of other baggage and complexity that honestly just isn't needed. These are my opinions. They are not held by the majority of HN commenters. But I stand by all of them! Not mentioned is that Windows has significantly better profilers and debuggers than Linux. That may change in the next two years. Also, super duper unpopular opinion, but bash sucks and any script longer than 10 lines should be written in a real language with a debugger.
- rfl890 11mo agoKernel32.dll is loaded into all Windows processes by default, so you actually can have a valid, working Windows binary with 0 entries in the import table. See here[1] for a "Hello world" program written as such. [1]: https://gist.github.com/rfl890/195307136c7216cf243f7594832f4bfc https://gist.github.com/rfl890/195307136c7216cf243f7594832f4...
- matheusmoreira 11mo agoThat's interesting. How does it work? PEB *peb = (PEB *)__readgsqword(0x60); LIST_ENTRY *current_entry = peb->Ldr->InMemoryOrderModuleList.Flink->Flink; It just obtains a pointer to the loader's data structures out of nowhere? Is this actually supported by Microsoft or are people going to end up in a Raymond Chen article if they use this?
- rfl890 11mo agoIt's in no way supported by Microsoft (and is flagged by most anti-viruses), it was just to demonstrate that kernel32.dll is available for "free" in all programs. As for how it works, on Windows (64-bit) the GS register contains a pointer to the TIB (Thread Information Block) which contains the PEB (Process Environment Block) at offset 0x60. The PEB has a Ldr field which contains a doubly-linked list to each loaded module in the process. From here I obtain the requested module's base address (here kernel32.dll), parse the PE headers to find the function's address and return it.
- matheusmoreira 11mo agoThat's actually amazing. Similar to the way Linux's vDSO is used. I'm disappointed that it's not supported and regarded as suspicious...