Re: [PATCH 1/2] tools/nolibc: add stdarg.h header
From: Thomas Weißschuh
Date: Wed Aug 30 2023 - 02:22:45 EST
On 2023-08-29 14:12:45+0200, Willy Tarreau wrote:
> On Tue, Aug 29, 2023 at 12:16:23PM +0200, Thomas Weißschuh wrote:
> > > OK. But then, doesn't it mean that if we don't provide our stdarg.h,
> > > the compilers' will be used ? I'm asking because we're already using
> > > va_list and va_args, for example in vfprintf() in stdio.h, which
> > > precisely includes <stdarg.h> so it must indeed come from the compiler.
> >
> > It will be used *iff* -nostdinc is *not* passed.
> >
> > I think we need to clarify the definition of the word "provided".
> > For me it means that the compiler ships an implementation of this header
> > file in the compiler-specific include directory.
> >
> > If -nostdinc is passed this include directory is not actually usable.
>
> OK I understand better now. I thought it was always usable.
>
> > If a user wants to avoid the implicit usage of any system-provided
> > headers they need to pass -nostdinc, as far as I know there is no flag
> > to keep only the compiler-specific include directories.
>
> So that means we may also have to implement our own stddef.h to move
> size_t there, and limits.h and move *MAX there as well if we want to
> support this. I'm not necessarily against this, it's just that we need
> to be consistent.
We would have to, *iff* the goal is to provide *all* headers in nolibc.
May goal was more limited:
nolibc should be self-contained, it should be able to work at all
with -nostdinc.
If users need more standard headers for their application they can add
those either as shim, custom implementation or from the compiler.
> Also something is puzzling me. If a normal program builds with -nostdinc,
> it means it does *not* want the libc's (nor the compiler's) headers to be
> included, probably because it comes with its own. In this case why would
> we impose ours ? For example, let's consider this tiny code snippet:
>
> $ cat arg.c
> #include <stdarg.h>
> va_list blah;
>
> $ gcc -c arg.c
> $ gcc -nostdinc -c arg.c
> arg.c:1:20: error: no include path in which to search for stdarg.h
> 1 | #include <stdarg.h>
> | ^
> arg.c:2:1: error: unknown type name 'va_list'
> 2 | va_list blah;
> | ^~~~~~~
> arg.c:1:1: note: 'va_list' is defined in header '<stdarg.h>'; did you forget to '#include <stdarg.h>'?
> +++ |+#include <stdarg.h>
> 1 | #include <stdarg.h>
>
> You see, that's why I'm finding it confusing that we define headers that
> are supposed *not* to be defined with -nostdinc.
I'm confused.
If the user doesn't want to use nolibc they should not explicitly add it
to the include path.
> I think we need to carefully check what is supposed to be defined and
> what not when -nostdinc is used normally so that we defined what programs
> expect and not what they expect us *not* to define. Recently we've been
> empirically fixing nolibc-test build failures but it's just a test program
> that comes with its own biases. Maybe trying to build some portable libs
> that use very little from a libc (e.g. xxhash, liblzo etc) could give us
> some hints about certain basic assumptions that we do not fulfill.
It makes sense to figure out what is needed by larger projects from a
libc. But it feels to me like a bug vs. feature discussion.
Making larger real-world applications work is a feature while making the
following work is a bugfix:
$ cat nolibc.c
#include "nolibc.h"
int main(void)
{
return 0;
}
$ gcc -nostdinc -Isysroot/x86/include -c nolibc.c
In file included from sysroot/x86/include/nolibc.h:98,
from nolibc-test.c:1:
sysroot/x86/include/sys.h:10:10: fatal error: stdarg.h: No such file or directory
10 | #include <stdarg.h>
| ^~~~~~~~~~
> > One usecase is in nolibc-test itself, where Zhangjin ran into weird
> > and inconsistent behavior of system includes being pulled in.
> > By using -nostdinc we avoid this.
>
> I see but a normal libc ought not to build with -nostdinc. I mean, we
> can define whatever we want once we know why we're doing it, but I think
> that as long as we find it confusing between those how are modifying this
> code, it will be very difficult to explain correctly to users. We're
> definitely missing some design rules I think. Maybe -nostdinc should be
> needed only when using -include nolibc.h for example, I don't know, but
> I still find that we're papering over a wider problem.
>
> > I can also see this being useful for normal users.
>
> I agree, that's also my concern actually.
>
> > > > I could not find anybody doing this differently.
> > > > Using builtins seems to me to be the normal way to expose compiler
> > > > implementation specifics.
> > >
> > > OK but it's already what the compiler does itself in its own stdarg that
> > > is provided. That's why I don't understand what specific case we're trying
> > > to cover here, I feel like we're providing an alternate stdarg in case the
> > > compiler doesn't provide one except that I've not seen a compiler not
> > > provide it (even tcc comes with it), it's like stddef.
> >
> > It's all about supporting -nostdinc.
>
> But unless I'm mistaken (and my example above seems to support this),
> a normal libc doesn't build with -nostdinc. That's the point I'd like
> us to clarify.
musl:
$ cat /usr/lib/musl/lib/musl-gcc.specs
...
*cc1:
%(cc1_cpu) -nostdinc -isystem /usr/lib/musl/include -isystem include%s
...
dietlibc:
$ cat Makefile
...
DEFAULTCFLAGS=-pipe -nostdinc -D_REENTRANT $(EXTRACFLAGS)
...
klibc re-adds the compilers include path,
This is an alternative we could also use:
$ cat Makefile
...
NOSTDINC_FLAGS := -nostdlib -nostdinc -isystem $(shell $(CC) -print-file-name=include)
...
(these are all I checked)
> > FYI stdint.h is also provided by nolibc, gcc and glibc.
>
> True but that one didn't surprise me because it came with C99 and was
> usually shipped by the libc when compilers targetting previous versions
> were used, so I didn't see this as a replacement for the compiler's
> definition actually.
>
> I don't know what dictates what goes in the compiler and what in the
> libc. I'm fine with having to redefine everything that's missing if
> that's needed, but as indicated above, stddef.h and limits.h are
> missing despite being quite common.
I think it's not really clearly defined what goes where.
There was also a longer discussion on LKML about linux/stdarg.h [0]
The gcc authors argue that Linux should not ship a custom stdarg.h.
But in reality Linux, musl, dietlibc (and probably some more) today are
shipping their own stdarg.h.
> We have an interesting comment at the top of nolibc.h which says:
>
> * The available standard (but limited) include files are:
> * ctype.h, errno.h, signal.h, stdio.h, stdlib.h, string.h, time.h
This is out of date. It's missing signal.h, stdint.h, unistd.h.
> *
> * In addition, the following ones are expected to be provided by the compiler:
> * float.h, stdarg.h, stddef.h
What does "expected" mean here?
nolibc itself is perfectly fine without float.h and stddef.h.
> *
> * The following ones which are part to the C standard are not provided:
> * assert.h, locale.h, math.h, setjmp.h, limits.h
While true, a lot of other headers are also not provided.
> I think I draw the line based on what my compilers have always provided.
> That's definitely something we can redefine (and update the comment),
> I'm just seeking consistency, and I think you can understand :-/
I do understand.
To reiterate it here explicitly, in my opinion it's a worthwhile and
consistent goal to make "nolibc usable standalone with -nostdinc" for
maximal control by the user.
If not, I'd like to use the "-nostdinc -I$(cc -print-file-name=include)"
method to avoid dependencies on system header for nolibc-test
specifically.
Thomas
[0] https://lore.kernel.org/lkml/CAHk-=wgoX0pVqNMMOcrhq=nuOfoZB_3qihyHB3y1S8qo=MDs6w@xxxxxxxxxxxxxx/