GCC 15 -fzero-init-padding-bits= option and redzone clobber
Hi!
This is just a FYI, since today GCC 15 no longer zero initializes padding bits in unions where the standard doesn't require it.
So e.g.
void foo (void)
{
union U { int a; long b[64]; };
/* This clears everything including padding bits,
required by at least C23 (note, GCC 15 defaults to -std=gnu23) */
union U u = {};
/* This used to clear everything, but only clears
v.a in GCC 15 by default. */
union U v = {0};
}If you want to keep the old behavior e.g. for security purposes (the whole union can be copied to user etc.), one can use -fzero-init-padding-bits=unions to restore the GCC 14 and older behavior. And, -fzero-init-padding-bits=all can be used to clear padding bits even in cases where the standard doesn't require them even in structures, e.g. void bar (void)
{
struct S { char a; int b; };
/* C23 requires padding bits to be cleared here. */
struct S s = {};
/* But not here. -fzero-init-padding-bits=all does that anyway. */
struct S t = { 1, 2 };
}Note, there is also __builtin_clear_padding builtin to clear padding bits already since GCC 11, though it doesn't clear bits in unions unless they are padding bits for all possible members, as it doesn't know which union member is current.
Another new feature since today that might be relevant to kernel is the "redzone" inline asm clobber.
It can/should be used on inline asm which does or could clobber memory below the stack pointer and so its presence must disable use of redzone (currently on x86_64 and powerpc*), whether because say pushf/pop pair or because the inline asm performs calls without taking into account the red zone (e.g. on x86_64 that would be something like subtracting 128 from %rsp at the start and restoring at the end).
In the past I think kernel used some hacks like clobbering rsp, that is something that really shouldn't be used even if it happened to work, inline asm is of course allowed to change the stack pointer temporarily, but before returning (if it returns at all) it needs to restore it back, and clobbers are not about temporary changes during the execution of inline asm, but about changes from the start to the end of inline asm.
So
asm ("call something" : ... : ... : "redzone");(of course it likely needs tons of other clobbers for call clobbered registers unless it saves them and restores them in the inline asm or in whatever it calls).
Jakub
Quoted 28 lines · Jakub Jelinek
Hi!
This is just a FYI, since today GCC 15 no longer zero initializes padding bits in unions where the standard doesn't require it.
So e.g.
void foo (void){ union U { int a; long b[64]; }; /* This clears everything including padding bits, required by at least C23 (note, GCC 15 defaults to -std=gnu23) */ union U u = {}; /* This used to clear everything, but only clears v.a in GCC 15 by default. */ union U v = {0}; }If you want to keep the old behavior e.g. for security purposes (the whole union can be copied to user etc.), one can use -fzero-init-padding-bits=unions to restore the GCC 14 and older behavior. And, -fzero-init-padding-bits=all can be used to clear padding bits even in cases where the standard doesn't require them even in structures, e.g. void bar (void)
{ struct S { char a; int b; }; /* C23 requires padding bits to be cleared here. */ struct S s = {}; /* But not here. -fzero-init-padding-bits=all does that anyway. */ struct S t = { 1, 2 }; }
Quoted 4 lines
Note, there is also
__builtin_clear_paddingbuiltin to clear padding bits already since GCC 11, though it doesn't clear bits in unions unless they are padding bits for all possible members, as it doesn't know which union member is current.
*groan* I suppose we should enable that flag when present :/
Quoted 5 lines
Another new feature since today that might be relevant to kernel is the "redzone" inline asm clobber.
It can/should be used on inline asm which does or could clobber memory below the stack pointer and so its presence must disable use of redzone (currently onx86_64and powerpc*),
At least on x86_64 we don't currently have a redzone. I'm assuming the "memory" clobber still very much includes everything?
And why was it deemed okay to change behaviour that might break existing code?
Quoted 11 lines
whether because say pushf/pop pair
or because the inline asm performs calls without taking into account the red zone (e.g. onx86_64that would be something like subtracting 128 from %rsp at the start and restoring at the end).In the past I think kernel used some hacks like clobbering rsp, that is something that really shouldn't be used even if it happened to work, inline asm is of course allowed to change the stack pointer temporarily, but before returning (if it returns at all) it needs to restore it back, and clobbers are not about temporary changes during the execution of inline asm, but about changes from the start to the end of inline asm.
Mostly we call a full C function on another stack, I don't think we ever swizzle the stack while inside a C function.
Quoted 5 lines
So
asm ("call something" : ... : ... : "redzone");(of course it likely needs tons of other clobbers for call clobbered registers unless it saves them and restores them in the inline asm or in whatever it calls).
We have this thing:
/*
* This output constraint should be used for any inline asm which has a "call"
* instruction. Otherwise the asm may be inserted before the frame pointer
* gets set up by the containing function. If you forget to do this, objtool
* may print a "call without frame pointer save/setup" warning. */
register unsigned long current_stack_pointer asm(_ASM_SP); #define ASM_CALL_CONSTRAINT "+r" (current_stack_pointer)
which we use a *LOT*.
Quoted 6 lines · Peter Zijlstra
Quoted 4 lines
Note, there is also
__builtin_clear_paddingbuiltin to clear padding bits already since GCC 11, though it doesn't clear bits in unions unless they are padding bits for all possible members, as it doesn't know which union member is current.*groan* I suppose we should enable that flag when present :/
If you want to keep previous behavior, sure. That is why I'posted this FYI.
Quoted 7 lines
Quoted 5 lines
Another new feature since today that might be relevant to kernel is the "redzone" inline asm clobber.
It can/should be used on inline asm which does or could clobber memory below the stack pointer and so its presence must disable use of redzone (currently onx86_64and powerpc*),At least on
x86_64we don't currently have a redzone.
Ah, I see, kernel builds with -mno-red-zone, missed that being mentioned in https://gcc.gnu.org/PR117312 that led to this.
Quoted 2 lines
I'm assuming the
"memory" clobber still very much includes everything?
No. "memory" clobber is about clobbering memory which could be clobbered e.g. by a call to a function one doesn't know anything about and which one passes the inline asm operands to.
So it can certainly clobber global variables, or address taken whose address escaped to global state (or could escape), or memory referenced from the operands of the inline asm.
It can't clobber random stack locations it knows nothing about, say clobbering the saved registers on the stack in the stack frame, return address, non-address taken variables which were just spilled to stack due to register allocator decisions, etc. There would be nothing the compiler could do about that.
Quoted 2 lines
And why was it deemed okay to change behaviour that might break existing code?
You mean for unions? It can only change behavior of invalid code (relying on undefined behavior). Most compiler optimizations change behavior of code with UB in it, otherwise the compiler couldn't optimize anything.
Quoted 11 lines
We have this thing:
/*
* This output constraint should be used for any inline asm which has a "call"
* instruction. Otherwise the asm may be inserted before the frame pointer
* gets set up by the containing function. If you forget to do this, objtool
* may print a "call without frame pointer save/setup" warning. */
register unsigned longcurrent_stack_pointerasm(_ASM_SP); #defineASM_CALL_CONSTRAINT"+r" (current_stack_pointer)
Quoted 1 line
which we use a *LOT*.
And wrongly so. Inline asm really can't change the stack pointer (with the meaning that rsp would be different between the entry to the inline asm and its exit(s) (multiple for asm goto). So telling the compiler it does change is wrong.
In GCC 9 we've added a warning for "rsp" and similar clobbers of inline asm,
warning: listing the stack pointer register ‘rsp’ in a clobber list is deprecated There is none currently for the "+r" (current_stack_pointer), but e.g. doing current_stack_pointer += 16;
orcurrent_stack_pointer = whatever;
will certainly cause UB. The behavior of "+r" (current_stack_pointer) you are relying on certainly isn't guaranteed.
See https://gcc.gnu.org/PR117312 for some details. There are surely other bugs that talk about that.
Jakub
Quoted 11 lines · Jakub Jelinek
Quoted 4 lines · Peter Zijlstra
register unsigned long
current_stack_pointerasm(_ASM_SP); #defineASM_CALL_CONSTRAINT"+r" (current_stack_pointer)which we use a *LOT*.
And wrongly so. Inline asm really can't change the stack pointer (with the meaning that rsp would be different between the entry to the inline asm and its exit(s) (multiple for asm goto). So telling the compiler it does change is wrong.
The value of %rsp doesn't change in the end, but we do read and write it, because that's what a "call" instruction does.
So see that ASM_CALL_CONSTRAINT as us just telling the compiler that we *use* rsp, because otherwise we've had issues with the compiler inserting the inline asm before the frame pointer is set up, which breaks all the usual tracing etc stuff. You can't do function calls without a properly set up frame.
This has happened with both gcc and clang, and telling the compiler that we need the stack pointer fixes it. I don't actually remember who told us to do that, but I think it was a gcc person.
In fact, if I were a betting man, I would have thought it was you ;)
The comment above the ASM_CALL_CONSTRAINT definition actually explains it:
* This output constraint should be used for any inline asm which has a "call"
* instruction. Otherwise the asm may be inserted before the frame pointer
* gets set up by the containing function. If you forget to do this, objtool
* may print a "call without frame pointer save/setup" warning.
but we're open to other ways to tell the compiler that "we need the stack pointer to be set up".
Linus
Quoted 6 lines · Linus Torvalds
This has happened with both gcc and clang, and telling the compiler that we need the stack pointer fixes it. I don't actually remember who told us to do that, but I think it was a gcc person.
In fact, if I were a betting man, I would have thought it was you ;)
Ahh, found it. Not you. Segher. Doing some archeology finds this:
https://gcc.gnu.org/legacy-ml/gcc/2015-07/msg00080.html
and even at the time, Segher suggested maybe having a separate "stack" clobber. But obviously that wasn't available at the time, and afaik has never happened.
So we used that gcc suggestion, and it has worked fine for most of a decade by now.
Linus
Hi!
Quoted 9 lines · Linus Torvalds
Quoted 6 lines · Linus Torvalds
This has happened with both gcc and clang, and telling the compiler that we need the stack pointer fixes it. I don't actually remember who told us to do that, but I think it was a gcc person.
In fact, if I were a betting man, I would have thought it was you ;)
Quoted 7 lines
Ahh, found it. Not you. Segher. Doing some archeology finds this:
https://gcc.gnu.org/legacy-ml/gcc/2015-07/msg00080.htmland even at the time, Segher suggested maybe having a separate "stack" clobber. But obviously that wasn't available at the time, and afaik has never happened.
But that would not mean changing the stack pointer, writing to something on the stack instead. Something like this "redzone" thing maybe. I did say it would have to have precise semantics specified :-)
Quoted 2 lines
So we used that gcc suggestion, and it has worked fine for most of a decade by now.
Interesting. And I was even accused of being "clever" in that thread, wow! :-)
But of course, GCC assumes there is a properly set up stack *everywhere*, it can in principle insert calls *anywhere*. So these asm constraints are totally superfluous anyway!
(Input constraints do not say a reg is read, and output constraints do not say it is written to. Instead, they express the data flow, something that is actually useful to the compiler, and very much required as well).
Segher
Quoted 4 lines · Segher Boessenkool
But of course, GCC assumes there is a properly set up stack *everywhere*, it can in principle insert calls *anywhere*. So these asm constraints are totally superfluous anyway!
Well, I wish that were true. But we used your trick for a reason: it fixed a real and present issue.
So no. It's not superfluous. It's required.
Without that "add sp register as a a input/output reg", at least some versions of gcc did in fact put inline asm in places where the frame was not set up.
You seem to imply that that may be a gcc bug, of course. Par for the course - we have had other odd things in inline asms (or around them) for other gcc bugs.
We do have commentary in some of our commits to that "it's a gcc bug" effect, ie this commit message from "only" seven years ago states:
With GCC 7.2, however, GCC's behavior has changed. It now changes its behavior based on the conversion of the register variable to a global. That somehow convinces it to *always* set up the frame pointer before inserting *any* inline asm. (Therefore, listing the variable as an output constraint is a no-op and is no longer necessary.)
So if it makes you feel any better, the trick now works for a _different_reason_. Just the existence of the global register variable seems to matter to those newer versions of gcc.
But we technically still support those older gcc versions that require the old format (we still support back to gcc-5.1, although IO think we're about to make the jump up to 8.1 based on staid enterprise distro people finally having left some of the ancient stuff behind).
So we *may* be able to remove this hack, if gcc people can actually pinky promise that it's not required with anything newer than 8.1
Linus
Hi!
Quoted 11 lines · Linus Torvalds
Quoted 4 lines · Segher Boessenkool
But of course, GCC assumes there is a properly set up stack *everywhere*, it can in principle insert calls *anywhere*. So these asm constraints are totally superfluous anyway!
Well, I wish that were true. But we used your trick for a reason: it fixed a real and present issue.
So no. It's not superfluous. It's required.
It expresses a fact that is true always anyway. But it is required to get a *side effect*. "It is a hack", if you want.
(And "is true always" means "is a requirement on any program", of course a user can blatantly violate those rules by writing incorrect programs).
Quoted 3 lines
Without that "add sp register as a a input/output reg", at least some versions of gcc did in fact put inline asm in places where the frame was not set up.
Yes. And that was not a bug, either: nothing expressed that there has to be a frame set up for this asm to work, so GCC felt free to make more optimal code (or what it thought was more optimal code, or potentially anyway; "could be more optimal code").
Quoted 3 lines
You seem to imply that that may be a gcc bug, of course. Par for the course - we have had other odd things in inline asms (or around them) for other gcc bugs.
Nope, it is not a GCC bug when users expect things that were not promised to them.
Quoted 8 lines
We do have commentary in some of our commits to that "it's a gcc bug" effect, ie this commit message from "only" seven years ago states:
With GCC 7.2, however, GCC's behavior has changed. It now changes its behavior based on the conversion of the register variable to a global. That somehow convinces it to *always* set up the frame pointer before inserting *any* inline asm. (Therefore, listing the variable as an output constraint is a no-op and is no longer necessary.)
I have absolutely no idea what "conversion of the register variable to a global" would mean, so I cannot parse what this sentence is meant to mean.
There are two kinds of register variable: global register variables, and local register variables. Global register variables are declared at global scope, and local register variables are declared within a function. There obviously is no way the compiler could decide to make a local register variable a global one (that would change semantics!), so it probably means something else, but I have no idea what.
Quoted 3 lines
So if it makes you feel any better, the trick now works for a _different_reason_. Just the existence of the global register variable seems to matter to those newer versions of gcc.
Yeah, and I don't see now. If there was a full testcase I could take a look :-)
Quoted 4 lines
But we technically still support those older gcc versions that require the old format (we still support back to gcc-5.1, although IO think we're about to make the jump up to 8.1 based on staid enterprise distro people finally having left some of the ancient stuff behind).
Why 8.1? 8.5 was a bugfix release (in general, always require the latest version in a release series, or recommend it at least: we cannot fix any bug in older releases, but we can do new releases :-) )
Quoted 2 lines
So we *may* be able to remove this hack, if gcc people can actually pinky promise that it's not required with anything newer than 8.1
The problem is still not completely clear to me. Maybe some other GCC people will do such a promise, but at least I won't. Sorry.
Segher
Quoted 5 lines · Segher Boessenkool
I have absolutely no idea what "conversion of the register variable to a global" would mean, so I cannot parse what this sentence is meant to mean.
That commit was for a clang issue, where clang was unhappy with a local definition of the stack pointer register. gcc didn't care, clang did.
So that commit changed it from a local register variable to a global one, and the commit message talked about how gcc doesn't seem to care any more as long as it's there.
Quoted 2 lines
Yeah, and I don't see now. If there was a full testcase I could take a look :-)
There was at the time, but that "at the time" was 10 years ago. The gmane links no longer work, and unlike the more modern lore.kernel.org archives, the link itself contains no useful information for me to try to find it.
So this is not the kernel one, but a gcc test at the time you replied to:
https://gcc.gnu.org/legacy-ml/gcc/2015-07/msg00079.html
Quoted 6 lines
Quoted 4 lines
But we technically still support those older gcc versions that require the old format (we still support back to gcc-5.1, although IO think we're about to make the jump up to 8.1 based on staid enterprise distro people finally having left some of the ancient stuff behind).
Why 8.1?
All our "we support this compiler version" tend to be about what distros still have.
And "enterprise" in technology means "old and crappy, but some company supports it". So enterprise distros tend to use some god-awful 5+ year old setup, because the companies that pay the big bucks don't like to see version changes.
Quoted 2 lines
The problem is still not completely clear to me. Maybe some other GCC people will do such a promise, but at least I won't. Sorry.
So this is why we are sometimes forced to use hacks. You may not like it, but to *us*, the important part is "this works and doesn't generate buggy code", not "this is documented".
Guess why we have random unnecessary "asm volatile" things too? Yeah. gcc bugs. They have been fixed, but not in older versions.
Linus
Quoted 3 lines · Linus Torvalds
Guess why we have random unnecessary "asm volatile" things too? Yeah. gcc bugs. They have been fixed, but not in older versions.
Btw, don't feel bad. There are clang bugs too. Bugs happen. And they happen more in code bases that push the boundaries.
99% of all projects never use inline asm at all, or do it purely with headers that came with the system. For the kernel, it's non-negotiable.
Linus