Bug #20601
closedConfiguration flags are not properly propagated to assembler
Description
Looking into #18061, one of the issues is that the .S
files are not processed with the correct flags. For example to have the CET enabled, the -fcf-protection
should be used to preprocess the coroutine/amd64/Context.S
.
First I thought there is something wrong on Fedora side, therefore I have proposed to export the ASFLAGS
1. However, as it turns out, $(ASFLAGS)
are used by GNU make default rule and passed to $(AS)
. And indeed, Ruby had historically override of this rule, but it does not do this anymore since:
https://github.com/ruby/ruby/commit/091422388e943de1e67ace6faac3d71ed08c14d2
https://github.com/ruby/ruby/commit/42575570a908aac979a80b89266804c4c688dd7c
As can be seen, while previously $(AS)
was used to process the .s
file, it was replaced by the compiler. This however means that the .S
files are not preprocessed with the $(CFLAGS)
, which contains -fcf-protection
.
Updated by vo.x (Vit Ondruch) 5 months ago
This might be the medicine:
$ git diff
diff --git a/template/Makefile.in b/template/Makefile.in
index 2d232d7925..31e5ae5edb 100644
--- a/template/Makefile.in
+++ b/template/Makefile.in
@@ -449,7 +449,7 @@ $(srcdir)/enc/jis/props.h: enc/jis/props.kwd
.$(ASMEXT).$(OBJEXT):
@$(ECHO) assembling $<
- $(Q) $(CC) $(ASFLAGS) -DSYMBOL_PREFIX=$(SYMBOL_PREFIX) -o $@ -c $<
+ $(Q) $(CC) $(ASFLAGS) -DSYMBOL_PREFIX=$(SYMBOL_PREFIX) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@ -o $@ -c $<
.c.$(ASMEXT):
@$(ECHO) translating $<
But I am not entirely convinced, if leaving the $(ASFLAGS)
around is of any use.
Updated by kjtsanaktsidis (KJ Tsanaktsidis) 5 months ago
I think we should do what we did for the pac-ret stuff, that Florian recommended, right? Run tests against CFLAGS in configure.ac, and then define special macros?
https://github.com/ruby/ruby/blob/93b19d56de64fdee790a96ddf96fcd08d889ac93/configure.ac#L1010-L1061
I'll have a bash at doing this tonight on my x86_64 machine.
Updated by vo.x (Vit Ondruch) 5 months ago
Is something like that really needed? From my POV, the issue is that originally, there were .s
files processed by Assembler, therefore the $(ASFLAGS)
were needed. But now we have .S
files pre-processed by compiler:
https://github.com/ruby/ruby/commit/e64f71f812324d098bed12ed68c2bc1d6e780c90
https://github.com/ruby/ruby/commit/42575570a908aac979a80b89266804c4c688dd7c
and therefore the same (or possibly extended) set of flags as for other .c
files should be used. My understanding is that we should rather drop the $(ASFLAGS)
usage.
Updated by kjtsanaktsidis (KJ Tsanaktsidis) 5 months ago ยท Edited
OK, I poked at this a bit tonight. I have quite a few thoughts but they're kind of disorganised.
The short version of this is: I think you're right; we should pass the full set of $CFLAGS $CPPFLAGS
when calling $(CC)
on a .S file, and in fact I would go a step furthre and delete $ASFLAGS
in configure.ac
and Makefile.in
entirely. It's late now but I'll try and put up a PR for this tomorrow or Friday.
The long verison...
Detecting control flow protection with configure tests¶
So I started on a PR to deal with -fcf-protection
in intel context.S in the same way we deal with -mbranch-protection
in arm context.S: https://github.com/ruby/ruby/compare/master...KJTsanaktsidis:ruby:ktsanaktsidis/fcf_prot.
This works, but it doesn't generate the build notes in the object file to declare that it has control flow protection. I was about to go and do that (in the same way we do it for ARM here https://github.com/ruby/ruby/blob/9aa62bda46bf7f9de91a95c31fa09dafd23def37/coroutine/arm64/Context.S#L109) but then I realised that the <cet.h>
header being included already does this! It has something like this in it on my system
#ifdef __CET__
.pushsection ".note.gnu.property", "a"
// Lots of stuff ...
#endif
So including this header automatically emits the build notes, but only if the CET macro is defined, which will only be if gcc context.S
is invoked as gcc -fcf-protection=full context.S
instead. We could replicate the property stuff like we do for pac-ret, but it seems pretty silly to copy stuff out of the system headers like this.
If we do what you propose and invoke gcc
as an assembler with $CFLAGS
, this whole thing sorts itself out (and we can drastically simplify how we handle arm64 pac-ret too!)
Rant about GNU Make conventions¶
The conventions here are all kind of messed up. GCC (and compatible compiler drivers) support...
- Passing options related to C compilation directly to the compiler (e.g.
-fno-omit-frame-pointer
). These get passed to thecc1
compiler process (I think) - Passing options related to the preprocesser via
-Wp,
(e.g. you can spell-DFOO=1
as-Wp,-DFOO=1
if you wanted to). These get passed tocpp
preprocessor - Passing options related to the assembler via
-Wa,
(e.g.-Wa,--generate-missing-build-notes=yes
). These get passed to thegas
assembler - Passing options related to the linker via
-Wl,
(e.g.-Wl,-z,now
). These get passed to theld
linker
The GNU make conventions however are that...
- C files are turned into Object files by running
$(CC) $(CPPFLAGS) $(CFLAGS) -c $<
.-
$(CC)
is usuallygcc
or some compatible compiler driver. -
$CFLAGS
obviously should contain the C-compiler related flags. -
$CPPFLAGS
should contain flags related to the preprocessor. They could be spelled with the-Wp,
form, but the common preprocessor flags (-I
,-U
,-D
, etc) are not required to be and usually aren't. - Internally, there is both a compilation step (C to assembly) and an assembling step (assembly to object), even though I guess gcc doesn't actually generate a textual form of the assembly unless you asked for it. You might want to provide some options to this inline assembler; you can achieve this by passing e.g.
-Wa,--defsym,foo=bar
togcc
. However, according to the make conventions, you'd put that in$CFLAGS
(since that's what gets passed to gcc).
-
- Assembler files are turned into Object by running
$(AS) $(ASFLAGS) $<
.-
$(AS)
is usuallygas
or some actual assembler - NOT a compiler driver -
$ASFLAGS
therefore has to be options spelled as they would be passed togas
; e.g.--defsym foo=bar
instead of-Wa,--defsym,foo=bar
-
- Object files are turned into an executable by running
$(CC) $(LDFLAGS) $^
- Because
$LDFLAGS
are passed togcc
, they need to be spelled in the-Wl,-z,now
format as GCC expects them, not in the-z now
format thatld
expects them
- Because
I guess this is all really kind of inconsistent:
-
$CFLAGS
are passed to gcc (makes sense) -
$ASFLAGS
as passed to gas (makes sense) -
$LDFLAGS
are passed to gcc, not ld (???) -
$CPPFLAGS
are passed to gcc, not cpp (???)
But what you're saying is that Ruby does not assemble with $(AS) $(ASFLAGS)
, but rather by calling $(CC) $(ASFLAGS)
. That would require that $ASFLAGS
be spelt as -Wa,...
, not as bare options like gas (and a "vanilla" GNU make setup) would expect! I think this is one of the reasons we should delete ASFLAGS - they're not passed to the assembler in the way that they "conventionally" should be, but rather more in a manner analagous to how LDFLAGS is handled.
Updated by kjtsanaktsidis (KJ Tsanaktsidis) 5 months ago
Just one more thing: I was worried for a moment that trying to assemble with $CFLAGS
would cause problems if you had some C-language specific things in cflags, but actually it seems to work fine - even if you have something like -std=gnu11
or some such.
Updated by vo.x (Vit Ondruch) 5 months ago
kjtsanaktsidis (KJ Tsanaktsidis) wrote in #note-4:
and in fact I would go a step furthre and delete
$ASFLAGS
inconfigure.ac
andMakefile.in
entirely. It's late now but I'll try and put up a PR for this tomorrow or Friday.
I totally support removal of $ASFLAGS
. I mulled over the idea, but I was not sure. But your analysis makes sense and provides the justification.
but then I realised that the <cet.h> header being included already does this!
Correct. That is also my understanding.
If we do what you propose and invoke gcc as an assembler with $CFLAGS, this whole thing sorts itself out (and we can drastically simplify how we handle arm64 pac-ret too!)
๐
Updated by Anonymous 5 months ago
- Status changed from Open to Closed
Applied in changeset git|b18701a7ae0a71c339906ef0db4910fb43645b45.
Remove $(ASFLAGS) from build system and assemble with $(CFLAGS) instead
We already assemble our assembly files using the $(CC) compiler driver,
rather than the actual $(AS) assembler. This means that
- The C preprocessor gets run on the assembly file
- It's valid to pass gcc-style flags to it, like e.g.
-mbranch-protection or -fcf-protection - If you do so, the relevant preprocessor macros like CET get set
- If you really wanted to pass assembler flags, you would need to do
that using -Wa,... anyway
So I think it makes sense to pass "$(XCFLAGS) $(CFLAGS) $(CPPFLAGS)" to
gcc/clang/etc when assembling, rather than passing $(ASFLAGS) (since
the flags are not actually passed to as
, but cc
!).
The side effect of this is that if there are mitigation flags like
-fcf-protection in $CFLAGS, then the relevant macros like CET will
be defined when assembling the files.
[Bug #20601]