[PATCH v2 07/11] um: add infrastructure to build files using nolibc

Benjamin Berg posted 11 patches 1 week, 5 days ago
There is a newer version of this series
[PATCH v2 07/11] um: add infrastructure to build files using nolibc
Posted by Benjamin Berg 1 week, 5 days ago
From: Benjamin Berg <benjamin.berg@intel.com>

Add NOLIBC_CFLAGS and NOLIBC_OBJS to build files against nolibc rather
than libc. With this it is possible to move to nolibc in smaller steps.

Set NOLIBC_IGNORE_ERRNO, as the nolibc errno implementation is overly
simple and cannot handle threading. nolibc provides sys_* functions that
do not emulate the libc errno behaviour and can be used instead.

Guard the syscall definition as it is a macro in nolibc.

Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>

---
v2:
- Do not include nolibc.h via CFLAGS
- Make syscall guard more explicit
- Remove __UM_NOLIBC__ define, it is not needed
- Fix out-of-tree building
---
 arch/um/Makefile               | 20 +++++++++++++++++++-
 arch/um/include/shared/os.h    |  2 ++
 arch/um/include/shared/user.h  |  1 -
 arch/um/scripts/Makefile.rules |  8 +++++++-
 4 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/arch/um/Makefile b/arch/um/Makefile
index f7c509262568..c3a81df50911 100644
--- a/arch/um/Makefile
+++ b/arch/um/Makefile
@@ -78,6 +78,24 @@ USER_CFLAGS = $(patsubst $(KERNEL_DEFINES),,$(patsubst -I%,,$(KBUILD_CFLAGS))) \
 		-include $(srctree)/include/linux/kern_levels.h \
 		-include $(srctree)/$(ARCH_DIR)/include/shared/user.h
 
+NOLIBC_CFLAGS = $(patsubst $(KERNEL_DEFINES),,$(patsubst -I%,,$(KBUILD_CFLAGS))) \
+		$(ARCH_INCLUDE) $(MODE_INCLUDE) $(filter -I%,$(CFLAGS)) \
+		-I $(srctree)/tools/include \
+		-D__EXPORTED_HEADERS__ \
+		-D__UM_HOST__ \
+		-DNOLIBC_NO_RUNTIME \
+		-DNOLIBC_IGNORE_ERRNO \
+		-nostdlib -nostdinc -static \
+		-I$(srctree)/include/uapi \
+		-I$(srctree)/$(HOST_DIR)/include/uapi \
+		-I$(objtree)/$(HOST_DIR)/include/generated/uapi \
+		-I $(srctree)/tools/include/nolibc \
+		-I $(srctree)/usr/include \
+		-include $(objtree)/include/generated/autoconf.h \
+		-include $(srctree)/tools/include/linux/kconfig.h \
+		-include $(srctree)/include/linux/kern_levels.h \
+		-include $(srctree)/$(ARCH_DIR)/include/shared/user.h
+
 #This will adjust *FLAGS accordingly to the platform.
 include $(srctree)/$(ARCH_DIR)/Makefile-os-Linux
 
@@ -160,4 +178,4 @@ archclean:
 		-o -name '*.gcov' \) -type f -print | xargs rm -f
 	$(Q)$(MAKE) -f $(srctree)/Makefile ARCH=$(HEADER_ARCH) clean
 
-export HEADER_ARCH SUBARCH USER_CFLAGS CFLAGS_NO_HARDENING DEV_NULL_PATH
+export HEADER_ARCH SUBARCH USER_CFLAGS NOLIBC_CFLAGS CFLAGS_NO_HARDENING DEV_NULL_PATH
diff --git a/arch/um/include/shared/os.h b/arch/um/include/shared/os.h
index b35cc8ce333b..a131ae9e00db 100644
--- a/arch/um/include/shared/os.h
+++ b/arch/um/include/shared/os.h
@@ -327,7 +327,9 @@ extern int __ignore_sigio_fd(int fd);
 /* tty.c */
 extern int get_pty(void);
 
+#ifndef NOLIBC
 long syscall(long number, ...);
+#endif
 
 /* irqflags tracing */
 extern void block_signals_trace(void);
diff --git a/arch/um/include/shared/user.h b/arch/um/include/shared/user.h
index c9b853e1282f..e32bdd032029 100644
--- a/arch/um/include/shared/user.h
+++ b/arch/um/include/shared/user.h
@@ -16,7 +16,6 @@
  */
 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
 
-#include <linux/types.h>
 #include <stddef.h>
 
 extern void panic(const char *fmt, ...)
diff --git a/arch/um/scripts/Makefile.rules b/arch/um/scripts/Makefile.rules
index b4a2e0058503..5f07551935c3 100644
--- a/arch/um/scripts/Makefile.rules
+++ b/arch/um/scripts/Makefile.rules
@@ -11,6 +11,12 @@ USER_OBJS := $(foreach file,$(USER_OBJS),$(obj)/$(file))
 $(USER_OBJS:.o=.%): \
 	c_flags = -Wp,-MD,$(depfile) $(USER_CFLAGS) $(CFLAGS_$(basetarget).o)
 
+# Similar USER_OBJS but compiled against nolibc (may include kernel headers?)
+NOLIBC_OBJS := $(foreach file,$(NOLIBC_OBJS),$(obj)/$(file))
+
+$(NOLIBC_OBJS:.o=.%): \
+	c_flags = -Wp,-MD,$(depfile) $(NOLIBC_CFLAGS) $(CFLAGS_$(basetarget).o)
+
 # These are like USER_OBJS but filter USER_CFLAGS through unprofile instead of
 # using it directly.
 UNPROFILE_OBJS := $(foreach file,$(UNPROFILE_OBJS),$(obj)/$(file))
@@ -18,7 +24,7 @@ UNPROFILE_OBJS := $(foreach file,$(UNPROFILE_OBJS),$(obj)/$(file))
 $(UNPROFILE_OBJS:.o=.%): \
 	c_flags = -Wp,-MD,$(depfile) $(call unprofile,$(USER_CFLAGS)) $(CFLAGS_$(basetarget).o)
 
-$(USER_OBJS) $(UNPROFILE_OBJS): \
+$(USER_OBJS) $(NOLIBC_OBJS) $(UNPROFILE_OBJS): \
 	CHECKFLAGS := $(patsubst $(NOSTDINC_FLAGS),,$(CHECKFLAGS))
 
 # The stubs can't try to call mcount or update basic block data
-- 
2.51.0
Re: [PATCH v2 07/11] um: add infrastructure to build files using nolibc
Posted by Willy Tarreau 1 week, 3 days ago
Hi Benjamin,

On Fri, Sep 19, 2025 at 05:34:16PM +0200, Benjamin Berg wrote:
> From: Benjamin Berg <benjamin.berg@intel.com>
> 
> Add NOLIBC_CFLAGS and NOLIBC_OBJS to build files against nolibc rather
> than libc. With this it is possible to move to nolibc in smaller steps.
> 
> Set NOLIBC_IGNORE_ERRNO, as the nolibc errno implementation is overly
> simple and cannot handle threading. nolibc provides sys_* functions that
> do not emulate the libc errno behaviour and can be used instead.

Just for my understanding, in case we can improve portability, why is it
needed to disable errno processing here ? Even if it's limited, it
shouldn't cause trouble. I mean that if a program works with it defined,
logically it should also work without since the only difference is that
the errno global variable will not be defined nor assigned on syscall
returns.

> Guard the syscall definition as it is a macro in nolibc.

This one is interesting:

  --- a/arch/um/include/shared/os.h
  +++ b/arch/um/include/shared/os.h
  @@ -327,7 +327,9 @@ extern int __ignore_sigio_fd(int fd);
   /* tty.c */
   extern int get_pty(void);

  +#ifndef NOLIBC
   long syscall(long number, ...);
  +#endif

In nolibc, the syscall() definition indeed looks like this now:

  #define __syscall_narg(_0, _1, _2, _3, _4, _5, _6, N, ...) N
  #define _syscall_narg(...) __syscall_narg(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)
  #define _syscall(N, ...) __sysret(my_syscall##N(__VA_ARGS__))
  #define _syscall_n(N, ...) _syscall(N, __VA_ARGS__)
  #define syscall(...) _syscall_n(_syscall_narg(__VA_ARGS__), ##__VA_ARGS__)

Except by mapping all syscalls to _syscall(6, ...) and always passing
6 args, I'm not seeing any easy way to dynamically adapt to the number
of arguments if we wanted to move it to a function. Also, a static
function would still conflict with the definition above. I'm wondering
about what extent the documented "long syscall(number, ...)" is valid in
fact, as I doubt it's really implemented anywhere as a generic function
taking the maximum amount of args.

Thus I think that the guard is indeed the only option to reconciliate these
two incompatible approaches. By the way I think it could be more future-
proof to do the guard on the syscall macro definition itself (which would
thus also resist it being passed by "-Dsyscall(x)=(...)" or any other form:

  +#ifndef syscall
   long syscall(long number, ...);
  +#endif

Regards,
Willy
Re: [PATCH v2 07/11] um: add infrastructure to build files using nolibc
Posted by Berg, Benjamin 1 week, 2 days ago
Hi,

On Sun, 2025-09-21 at 10:13 +0200, Willy Tarreau wrote:
> Hi Benjamin,
> 
> On Fri, Sep 19, 2025 at 05:34:16PM +0200, Benjamin Berg wrote:
> > From: Benjamin Berg <benjamin.berg@intel.com>
> > 
> > Add NOLIBC_CFLAGS and NOLIBC_OBJS to build files against nolibc rather
> > than libc. With this it is possible to move to nolibc in smaller steps.
> > 
> > Set NOLIBC_IGNORE_ERRNO, as the nolibc errno implementation is overly
> > simple and cannot handle threading. nolibc provides sys_* functions that
> > do not emulate the libc errno behaviour and can be used instead.
> 
> Just for my understanding, in case we can improve portability, why is it
> needed to disable errno processing here ? Even if it's limited, it
> shouldn't cause trouble. I mean that if a program works with it defined,
> logically it should also work without since the only difference is that
> the errno global variable will not be defined nor assigned on syscall
> returns.
> 
> > Guard the syscall definition as it is a macro in nolibc.
> 
> This one is interesting:
> 
>   --- a/arch/um/include/shared/os.h
>   +++ b/arch/um/include/shared/os.h
>   @@ -327,7 +327,9 @@ extern int __ignore_sigio_fd(int fd);
>    /* tty.c */
>    extern int get_pty(void);
> 
>   +#ifndef NOLIBC
>    long syscall(long number, ...);
>   +#endif
> 
> In nolibc, the syscall() definition indeed looks like this now:
> 
>   #define __syscall_narg(_0, _1, _2, _3, _4, _5, _6, N, ...) N
>   #define _syscall_narg(...) __syscall_narg(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)
>   #define _syscall(N, ...) __sysret(my_syscall##N(__VA_ARGS__))
>   #define _syscall_n(N, ...) _syscall(N, __VA_ARGS__)
>   #define syscall(...) _syscall_n(_syscall_narg(__VA_ARGS__), ##__VA_ARGS__)
> 
> Except by mapping all syscalls to _syscall(6, ...) and always passing
> 6 args, I'm not seeing any easy way to dynamically adapt to the number
> of arguments if we wanted to move it to a function. Also, a static
> function would still conflict with the definition above. I'm wondering
> about what extent the documented "long syscall(number, ...)" is valid in
> fact, as I doubt it's really implemented anywhere as a generic function
> taking the maximum amount of args.
> 
> Thus I think that the guard is indeed the only option to reconciliate these
> two incompatible approaches. By the way I think it could be more future-
> proof to do the guard on the syscall macro definition itself (which would
> thus also resist it being passed by "-Dsyscall(x)=(...)" or any other form:
> 
>   +#ifndef syscall
>    long syscall(long number, ...);
>   +#endif

I had that, but then Thomas suggested the NOLIBC check :-)

Overall, it probably does not really matter. The declaration is used
when this file is included from kernel code to be able to do host
syscalls. That can be useful, but in that case we will not be able to
use nolibc includes and macros anyway. If UML stops linking against
libc we'll need an alternative solution, likely by adding a simple
os_syscall wrapper and using that instead.

Considering that, I think I'll change it to "#ifdef __KERNEL__".

Benjamin
Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928