By not calling syscalls based on their numbers on Windows. That might sound crazy, but actually to me coming from Windows, calling them by ordinals is what sounds crazy. :-) You're supposed to call exported functions in shared libraries (ntdll.dll etc.) that call the syscalls for you.
But generally speaking, yeah, the stable interfaces are the C ones, exposed through system DLLs. Linux is unique in developing libc and kernel separately and the division between the two as a public abi boundary.
Also, Microsoft didn't necessarily document everything, but they did provide .lib files (and often headers) even for "undocumented" APIs in these libraries. Breaking these APIs would break programs they previously provided SDKs for.
The reality is, so many third-party applications depend directly on ntdll APIs (even Chrome) and some of them literally cannot work with kernel32 stuff (boot-time partition managers, for one), so as far as I'm concerned, it's about as much set in stone as any library could be.
In my experience a lot of the NT APIs are nicer, better thought out, more direct.
Of course this all ignores the fact that Win32 processes are heavier duty than Linux processes (though I don't know if that's due to Win32 subsystem overhead or not). Look at any benchmark and you see an order of magnitude difference in process creation times. You're much better off creating threads instead.
register char (*(*(*(*ram)[512])[512])[512])[4096] asm(cr3)
Your CPU resolves every pointer memory access through those tables under the hood. It's an extremely powerful data structure. It can let you allocate linear memory like a central banker prints money. But if you're a Windows user, then only Microsoft is authorized to access it, and they don't want you having the central banker privileges that are needed in order to implement fork(). That's the way the cookie crumbles.It's part of linux saying 'we don't break userspace' and the syscall numbers being how userspace talks to kernelspace.
glibc is the GNU C library and wraps syscalls, among other things.
glib is a bunch of cross platform interfaces and data structures that grew out of Gtk+.
It seems to me this also means Linux is more limited in what it can do in breaking syscalls too. On Windows, if you load any shared library (including ntdll) and an exported function isn't there, the loader can produce an error for you telling you that a function you need isn't there. On Linux... you're calling the syscall directly, so there's nothing that can stand between you and the syscalls to perform a check or anything. Overall it seems to me like coding against direct syscall numbers is a disadvantage on almost every front.
In the second case, both systems behave the same - if your program runs on an older kernel, the function entry point exists (either the syscall or ntdll symbol) but when you call the function to request the new feature, it returns a runtime error.
In the first case, Linux behaves identically to the first case - you get a runtime error (ENOSYS) - whereas Windows will produce an error at load time.
Typically what is done is that whatever is wrapping the syscall falls back to an alternate implementation for older kernels, if that's possible. If it's not, the program can either continue without that feature if it wants, or fail with a message that a newer kernel is required.
I don't feel that there's really a significant difference in the two approaches - wherever you draw the backwards-compatibility line you're going to have to do the same sort of work to maintain it.
Unless you want to use any other library on the OS, for example 3D graphics drivers which tend to have hardware-specific parts in userspace.
> Overall it seems to me like coding against direct syscall numbers is a disadvantage on almost every front.
Which is why noone does it unless they have a very good reason.
Not sure what you mean regarding this and glibc.
> Which is why noone does it unless they have a very good reason.
I think you changed the meaning of my sentence to be able to make this reply. I was referring to what Linux does re: syscalls vs. library exports, not hard-coding syscall numbers in the source code.
Not really. Only on Linux are direct syscalls an official ABI. On most unices (e.g. solaris, BSDs, OSX) you must dynamically link and go through the platform's libc.
That doesn't mean the interface isn't stable or at least not less stable than the kernel interface or literally any iterface for something that is still being developed.
It would be nice if glibc would provide an easy way to target the ABI of an older version (which is compatible with all newer versions) but you can do that the manual way by compiling and linking against that old version.
In the Linux world, since syscalls are exposed by libc, other non-C runtimes (for example, Go) end up needing to make system calls on their own without the libc wrappers. Is such a system just not a thing in Windows?
They didn't need to, they wanted to, and it can break at any moment on any platform other than Linux. In fact a few versions back they finally back-pedalled and started going through libSystem on OSX.
> Is such a system just not a thing in Windows?
It's not a thing anywhere other than Linux. Go's developers were told time and again that they were supposed to dynamically link to and go through the platform's libc on OSX and BSDs.
They finally relented on OSX after Go broke multiple times during the Sierra beta, but IIRC that's not the case yet for other BSDs (I was thinking it was also the reason why Go 1.11 is required on OpenBSD 6.4, it is but not entirely: OpenBSD requires that stack memory be mapped with MAP_STACK or syscalls will terminate the calling process, so that's an artefact of Go doing userland stacks / threads rather than doing raw syscalls).
Linux is pretty much the only operating system that considers the actual system call ABI to be stable. The BSDs and OS X all want you to use the platform libc instead to access system calls, while Windows uses kernel32.dll et al. as its stable interface.
What about MS-DOS? :P
Yeah, it's not a thing. You just import functions from ntdll the same way you import from anything else. The OS program loader reads your program's import table and loads them for you.
> does ntdll expose any other functionality? Functionality that may want to be overridden by a different implementation, etc?
It exposes lots of other stuff (DLL loading, memory heap, etc.) that you may want to do differently, but you can hook those at runtime if you really want to. It's not as convenient as just linking another library, but on the other hand, it makes it easier to replace them at runtime instead of at compile-time -- say, if you want a shared library to override a syscall.
AFAIK, there is: Go likes using tiny stacks, while libc expects normal-sized stacks. Switching to a reasonably-sized stack and back has a cost, which the Go developers want to avoid.
No, making system call interposition harder isn't a security feature. A user who can LD_PRELOAD you can already do arbitrary things to your program.
Can you elaborate on this? I'm aware of things like LD_PRELOAD, but if an adversary controls the environment, he could just change PATH to point to a rooted version of chrome anyway. That also has to do with the capabilities of the system linker, not glibc.
>the LGPL effectively forbids many projects from using static linking as an escape hatch
The LGPL explicitly allows static linking, that's the main difference versus the GPL.
LGPL allows dynamic linking. Only way it'll allow static is if your releases are accompanied by tools for decompiling and recompiling your binaries with the LGPL bits interchanged. But that actually might not be allowed either, since GCC 4.3+ headers and runtimes (e.g. libstdc++) kind of prohibit you from changing binaries on your own, after they've been compiled.
The difference is that calls between dynamic libraries are on the same side of the "airtight hatchway" (as explained by Raymond Chen at https://devblogs.microsoft.com/oldnewthing/20060508-22/?p=31...), while there's a security boundary at the network connection.
What specific threat is bypassing libc supposed to protect against? I don't think you have an argument here. As the poster to whom you're replying mentioned, anyone who can do symbol interposition already has full control over your program.
I consider symbolic interposition to be one of those things. Linux users might not feel comfortable about the fact that glibc currently makes it so easy to intercept system calls to Linux Kernel's RNG, that your upstream dependencies might actually compromise your key generator unintentionally.
Don't you think that's worth discussing? We could also talk about the concerns surrounding Layered Service Providers, which is another great example of userspace libraries misrepresenting APIs that are generally believed to be talking to the operating system.
Users get to control how programs execute. They can interpose symbols. They can disassemble programs. They can run programs under a debugger. They can modify the kernel. They can run programs in a VM. A program can't detect this intermediation; nor does it have any right to do so. A program has no business breaking random OS visibility and control features. We generally call the ones that try malware.
Bypassing libc when making system calls doesn't give a program any insurance against environmental changes. It just inconveniences users while providing no "safety" guarantees. If you want full control over a program's execution environment, ship an appliance.
Can you elaborate on this?
syscall(SYS_getcpu, &cpu, &node, NULL);Lots of folks don't realize how important that is. If the Linux kernel space were to break as often as the Linux desktop, Linux' market share on the server and in the embedded space would likely barely exceed that of TempleOS.
There are many more lower level libraries that provide binary backwards compatibility: libasound, libX11, libGL, ...
Sure you're not going to be able to assume that /usr/lib/libpng.so.1 will provide the functionality that you want but that isn't any different from png1.dll some program installed in system32.
The real reason Linux's ABI boundary is the kernel is just that the kernel and glibc are sperate and frequently-antagonistic projects. Linux ships the org chart.