How can one C object span two symbols in the first place for this assumption to be invalid?
How can one C object span two symbols in the first place for this assumption to be invalid?
Because Linux isn’t written in C. It’s written in C, assembler, and linker script (.lds). In both assembly and linker script, one can lay out multiple symbols with a defined relationship. Unfortunately, C doesn’t have a way to say “give me a pointer to the object 10 bytes past the address p. Yes, I know it’s valid and it is not the same object as p.”
Right - I'm saying that if they need this macro then their program is not a defined C program in the first place. They should fix that, rather than trying to use a macro to work around the problem. The macro may break in the future if GCC adds other optimisations.
They call it a 'miscompilation' - how can you 'miscompile' something which is undefined!
That GCC may break your program in the future is one of the ways that people get locked into specific versions.
1) you have rightly pointed out all the authors of the Linux kernel are total idiots who don't understand C.
2) It turns out writing a kernel isn't possible in pure C, and the kernel requires some carefully designed and considered methods of accessing all of raw memory, in various unusual ways.
This one is the case. Writing a kernel is not possible in pure C. I'm asking - given this, why are they trying to do that? Write the parts which aren't expressible in C using assembly, where they can get the semantics they want.
While technically this means your kernel is "gcc c" rather than "true c", this doesn't cause a practical problem. I know gcc, clang and visual studio's c++ standard libraries require a flat address space and 8 bit bytes, for example, but I've never seen anyone bothered by this.
How about this as a compromise: use GCC, with all its flags, to generate the assembly, and just use that? Skip the messy transition step.
Instead we could just mark some files as "These C files have to be run with GCC versions X to Y", problem solved. Then why not just compile everything with those versions of GCC?
Is it solved? You'd still have to check the generated assembly of the entire file after every change you make, to make sure it didn't have some knock-on effect that optimised something differently, even if you kept the version of the compiler the same.
And is GCC deterministic? I don't know - do you? Did you know it has a flag `-frandom-seed`?
[Edit: I am almost sure that the only part of Linux on i386 and amd64 that is written in assembly is the bootloader stub at the beginning of "Linux kernel image" file format. And it is somewhat funny and relevant that this part depends on abusing gas to generate 16bit code]
The "correct" way to do this is to write the bare minimum assembly needed to present this view to a programmer in C (so there's no need to worry about undefined behavior), then write standards compliant code from there. Writing all this code in C is just papering over the fact that what you're doing is architecture-specific and should be treated as such.
Obviously at the application level it's frowned upon, but in the kernel? You do what you have to with the tools you have.
Think about Java. Yea sure, keep telling us sun.misc.Unsafe isn't legit. It's going to get use until that capability exists elsewhere.
I'd rather my kernel not have a security hole in it because a compiler decided to optimize out undefined code: https://lwn.net/Articles/342330/
I appreciate using software that is actually written with security first, performance second in mind.
Today, if you were starting a kernel project from scratch, I'd think Rust would deserve a good, hard look.
It simply implies that they consider themselves, rather than the C standard, as the authority on how C should behave.
char symbolA[10];
char symbolB[10];
symbolA[15] = 0; // actually writes to symbolB> gcc assumes that if you add something on to the address of a symbol, the resulting address is still inside the bounds of the symbol
I'm asking - how can you write a valid C program which adds something on to the address of a valid symbol and get something outside the bounds of the symbol?
You have a bunch of objects, defined in different files, and no code is aware of what the complete list of objects is. You want to iterate over an array of all these objects. How to make the array, with minimal overhead?
A common technique is for each file defining on the these objects to to tell the linker to put it in a special section. The result of this is that there's then an "array" of objects, which according to the C language could be anywhere in memory, but because of how the linker has been told to lay them out, we know that actually they're all consecutive. So any code which wants to iterate over then can do so easily.
Except... this is isn't actually a valid C program, and is actually undefined behaviour, so the compiler might "optimise" things in a way which breaks it. Hence the RELOC_HIDE macro which obscures things from the compiler and prevents it from applying "optimisations" we don't want.
I am guessing here, but my guess is that they're using C but actually want to do something very low level. As in they believe (correctly?) that they know the exact memory layout relative to something.
char x[N],*p=x+sizeof x;Reading or writing the pointer p will not be a valid C program.