From f94da2b42bc84bbcdb5d1f41551295fc946c7f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 9 Apr 2026 18:22:08 +0200 Subject: [PATCH 01/37] tools/nolibc: add assert() and assert.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the standard assert() macro from the assert.h header. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260409-nolibc-assert-v1-1-42da8b367e23@weissschuh.net --- tools/include/nolibc/Makefile | 1 + tools/include/nolibc/assert.h | 36 +++++++++++++++++++++++++++++++++++ tools/include/nolibc/nolibc.h | 1 + 3 files changed, 38 insertions(+) create mode 100644 tools/include/nolibc/assert.h diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile index 7455097cff69..f0e6e71e8335 100644 --- a/tools/include/nolibc/Makefile +++ b/tools/include/nolibc/Makefile @@ -20,6 +20,7 @@ OUTPUT ?= $(CURDIR)/ architectures := arm arm64 loongarch m68k mips powerpc riscv s390 sh sparc x86 arch_files := arch.h $(addsuffix .h, $(addprefix arch-, $(architectures))) all_files := \ + assert.h \ byteswap.h \ compiler.h \ crt.h \ diff --git a/tools/include/nolibc/assert.h b/tools/include/nolibc/assert.h new file mode 100644 index 000000000000..84ff8ad9ab07 --- /dev/null +++ b/tools/include/nolibc/assert.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ +/* + * Assert for NOLIBC + * Copyright (C) 2026 Thomas Weißschuh + */ + +/* make sure to include all global symbols */ +#include "nolibc.h" + +#ifndef _NOLIBC_ASSERT_H +#define _NOLIBC_ASSERT_H + +#include "errno.h" +#include "stdio.h" +#include "stdlib.h" + +#endif /* _NOLIBC_ASSERT_H */ + +/* NDEBUG needs to be evaluated on *each* inclusion */ +#ifdef assert +#undef assert +#endif + +#ifndef NDEBUG +#define assert(expr) \ +({ \ + if (!(expr)) { \ + fprintf(stderr, "%s: %s:%d: %s: Assertion `%s' failed.\n", \ + program_invocation_short_name, __FILE__, __LINE__, __func__, \ + #expr); \ + abort(); \ + } \ +}) +#else +#define assert(expr) ((void)0) +#endif diff --git a/tools/include/nolibc/nolibc.h b/tools/include/nolibc/nolibc.h index f4120f65fe79..4b99795d7a65 100644 --- a/tools/include/nolibc/nolibc.h +++ b/tools/include/nolibc/nolibc.h @@ -133,6 +133,7 @@ #include "err.h" #include "byteswap.h" #include "endian.h" +#include "assert.h" /* Used by programs to avoid std includes */ #define NOLIBC From 80e5de852e3a619ff83e0347a5766b34fdc8aa78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 9 Apr 2026 17:55:28 +0200 Subject: [PATCH 02/37] tools/nolibc: add alloca() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the wide-used alloca() function. As it is highly machine and compiler dependent, just defer to the compiler builtin. This has been available since GCC 4 and clang 3. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260409-nolibc-alloca-v1-1-ed02f68dfaf9@weissschuh.net --- tools/include/nolibc/Makefile | 1 + tools/include/nolibc/alloca.h | 15 +++++++++++++++ tools/include/nolibc/nolibc.h | 1 + tools/testing/selftests/nolibc/nolibc-test.c | 14 ++++++++++++++ 4 files changed, 31 insertions(+) create mode 100644 tools/include/nolibc/alloca.h diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile index f0e6e71e8335..872c318f50d4 100644 --- a/tools/include/nolibc/Makefile +++ b/tools/include/nolibc/Makefile @@ -20,6 +20,7 @@ OUTPUT ?= $(CURDIR)/ architectures := arm arm64 loongarch m68k mips powerpc riscv s390 sh sparc x86 arch_files := arch.h $(addsuffix .h, $(addprefix arch-, $(architectures))) all_files := \ + alloca.h \ assert.h \ byteswap.h \ compiler.h \ diff --git a/tools/include/nolibc/alloca.h b/tools/include/nolibc/alloca.h new file mode 100644 index 000000000000..448233a79e6e --- /dev/null +++ b/tools/include/nolibc/alloca.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ +/* + * alloca() for NOLIBC + * Copyright (C) 2026 Thomas Weißschuh + */ + +/* make sure to include all global symbols */ +#include "nolibc.h" + +#ifndef _NOLIBC_ALLOCA_H +#define _NOLIBC_ALLOCA_H + +#define alloca(size) __builtin_alloca(size) + +#endif /* _NOLIBC_ALLOCA_H */ diff --git a/tools/include/nolibc/nolibc.h b/tools/include/nolibc/nolibc.h index 4b99795d7a65..faa94f247281 100644 --- a/tools/include/nolibc/nolibc.h +++ b/tools/include/nolibc/nolibc.h @@ -134,6 +134,7 @@ #include "byteswap.h" #include "endian.h" #include "assert.h" +#include "alloca.h" /* Used by programs to avoid std includes */ #define NOLIBC diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index d3c4facb54c0..cfb797bf416c 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -45,6 +45,7 @@ #include #include #include +#include #pragma GCC diagnostic ignored "-Wmissing-prototypes" @@ -1516,6 +1517,18 @@ int run_syscall(int min, int max) return ret; } +int test_alloca(void) +{ + uint64_t *x; + + x = alloca(sizeof(*x)); + + *x = 0x1234; + __asm__ ("" : "+r" (x)); + + return *x - 0x1234; +} + int test_difftime(void) { if (difftime(200., 100.) != 100.) @@ -1731,6 +1744,7 @@ int run_stdlib(int min, int max) CASE_TEST(toupper_noop); EXPECT_EQ(1, toupper('A'), 'A'); break; CASE_TEST(abs); EXPECT_EQ(1, abs(-10), 10); break; CASE_TEST(abs_noop); EXPECT_EQ(1, abs(10), 10); break; + CASE_TEST(alloca); EXPECT_ZR(1, test_alloca()); break; CASE_TEST(difftime); EXPECT_ZR(1, test_difftime()); break; CASE_TEST(memchr_foobar6_o); EXPECT_STREQ(1, memchr("foobar", 'o', 6), "oobar"); break; CASE_TEST(memchr_foobar3_b); EXPECT_STRZR(1, memchr("foobar", 'b', 3)); break; From e635b0459ae9533d57f5816aad1793ccb9d7d097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 4 Apr 2026 17:26:05 +0200 Subject: [PATCH 03/37] tools/nolibc: make __nolibc_enosys() a compile time error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Functions which are known at compile-time to result in ENOSYS can be surprising to the user. For example using old UAPI headers might mean that stat() will always fail although the kernel would have the system call available at runtime. Nowadays __nolibc_enosys() should never be called for normal applications. Switch the silent ENOSYS return into a compile-time error, so the user is aware about the issue. Prefer the 'error' attribute as it provides the best diagnostics. If the users defines NOLIBC_COMPILE_TIME_ENOSYS the old, silent fallback is kept. Also add a test which validates that the error can be optimized away. Reported-by: Willy Tarreau Closes: https://lore.kernel.org/lkml/acizRIq2xrFUNHNS@1wt.eu/ Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260404-nolibc-enosys-v1-1-e0aba47bdee4@weissschuh.net --- tools/include/nolibc/sys.h | 18 ++++++++++++++++-- tools/testing/selftests/nolibc/nolibc-test.c | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index 6335fd51f07f..fd7a2ee780e8 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -45,16 +45,30 @@ : __sysret_arg; /* return original value */ \ }) -/* Syscall ENOSYS helper: Avoids unused-parameter warnings and provides a - * debugging hook. +/* Syscall ENOSYS helper: Avoids unused-parameter warnings, provides compile + * time validation and a debugging hook. */ +#if defined(NOLIBC_COMPILE_TIME_ENOSYS) static __inline__ int __nolibc_enosys(const char *syscall, ...) { (void)syscall; return -ENOSYS; } +#elif __nolibc_has_attribute(error) +__attribute__((error("system call not implemented"))) +extern int __nolibc_enosys(const char *syscall, ...); + +#else +static __inline__ int __nolibc_enosys(const char *syscall, ...) +{ + extern int __nolibc_enosys_error; + (void)syscall; + + return __nolibc_enosys_error; +} +#endif /* Functions in this file only describe syscalls. They're declared static so * that the compiler usually decides to inline them while still being allowed diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index cfb797bf416c..eede2887acf6 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1299,6 +1299,23 @@ int test_openat(void) return 0; } +int test_nolibc_enosys(void) +{ + if (true) + return 0; + +#if defined(NOLIBC) + /* + * __nolibc_enosys() will fail the compilation. + * Make sure it can be optimized away if not actually called. + */ + if (__nolibc_enosys("something") != -ENOSYS) + return 1; +#endif + + return 0; +} + int test_namespace(void) { int original_ns, new_ns, ret; @@ -1467,6 +1484,7 @@ int run_syscall(int min, int max) CASE_TEST(munmap_bad); EXPECT_SYSER(1, munmap(NULL, 0), -1, EINVAL); break; CASE_TEST(mmap_munmap_good); EXPECT_SYSZR(1, test_mmap_munmap()); break; CASE_TEST(nanosleep); ts.tv_nsec = -1; EXPECT_SYSER(1, nanosleep(&ts, NULL), -1, EINVAL); break; + CASE_TEST(nolibc_enosys); EXPECT_ZR(is_nolibc, test_nolibc_enosys()); break; CASE_TEST(open_tty); EXPECT_SYSNE(1, tmp = open("/dev/null", O_RDONLY), -1); if (tmp != -1) close(tmp); break; CASE_TEST(open_blah); EXPECT_SYSER(1, tmp = open("/proc/self/blah", O_RDONLY), -1, ENOENT); if (tmp != -1) close(tmp); break; CASE_TEST(openat_dir); EXPECT_SYSZR(1, test_openat()); break; From 4c6826ee7d04f489ad6fcf46ae6dd595fe9c6295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 18 Apr 2026 12:31:54 +0200 Subject: [PATCH 04/37] tools/nolibc: avoid call to wcslen() in _start_c() inserted by clang MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clang may convert the loop to find _auxv into a call to wcslen() which is missing on nolibc. -fsanitize needs to be disabled for this to happen. Use the same pattern as in the nolibc strlen() implementation to avoid the function call generation. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260418-nolibc-wcslen-v1-1-671271b8ea63@weissschuh.net --- tools/include/nolibc/crt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/nolibc/crt.h b/tools/include/nolibc/crt.h index d8ce91fd2e3b..f38590a05adf 100644 --- a/tools/include/nolibc/crt.h +++ b/tools/include/nolibc/crt.h @@ -89,7 +89,7 @@ void _start_c(long *sp) /* find _auxv */ for (auxv = (void *)envp; *auxv++;) - ; + __asm__(""); _auxv = auxv; #ifndef NOLIBC_IGNORE_ERRNO From 9adc1c33a10491b0f97152facce186f073ac91e3 Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Sat, 25 Apr 2026 20:13:14 +0900 Subject: [PATCH 05/37] tools/nolibc: Rename __no_stack_protector to __nolibc_no_stack_protector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To avoid polluting the namespace rename __no_stack_protector to __nolibc_no_stack_protector so its now within the nolibc umbrella. Suggested-by: Willy Tarreau Signed-off-by: Daniel Palmer Link: https://patch.msgid.link/20260425111315.3191461-2-daniel@thingy.jp Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/arch-arm.h | 2 +- tools/include/nolibc/arch-arm64.h | 2 +- tools/include/nolibc/arch-loongarch.h | 2 +- tools/include/nolibc/arch-m68k.h | 2 +- tools/include/nolibc/arch-mips.h | 2 +- tools/include/nolibc/arch-powerpc.h | 8 ++++---- tools/include/nolibc/arch-riscv.h | 2 +- tools/include/nolibc/arch-s390.h | 2 +- tools/include/nolibc/arch-sh.h | 2 +- tools/include/nolibc/arch-sparc.h | 2 +- tools/include/nolibc/arch-x86.h | 4 ++-- tools/include/nolibc/compiler.h | 4 ++-- tools/include/nolibc/stackprotector.h | 2 +- 13 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tools/include/nolibc/arch-arm.h b/tools/include/nolibc/arch-arm.h index a4d3a777a051..72a2b28170e2 100644 --- a/tools/include/nolibc/arch-arm.h +++ b/tools/include/nolibc/arch-arm.h @@ -186,7 +186,7 @@ #ifndef NOLIBC_NO_RUNTIME /* startup code */ -void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _start(void) +void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector _start(void) { __asm__ volatile ( "mov r0, sp\n" /* save stack pointer to %r0, as arg1 of _start_c */ diff --git a/tools/include/nolibc/arch-arm64.h b/tools/include/nolibc/arch-arm64.h index 28b3c7536ad6..814bcc13b4b2 100644 --- a/tools/include/nolibc/arch-arm64.h +++ b/tools/include/nolibc/arch-arm64.h @@ -143,7 +143,7 @@ #ifndef NOLIBC_NO_RUNTIME /* startup code */ -void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _start(void) +void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector _start(void) { __asm__ volatile ( "mov x0, sp\n" /* save stack pointer to x0, as arg1 of _start_c */ diff --git a/tools/include/nolibc/arch-loongarch.h b/tools/include/nolibc/arch-loongarch.h index 86fb34bbf185..3abed96a15e8 100644 --- a/tools/include/nolibc/arch-loongarch.h +++ b/tools/include/nolibc/arch-loongarch.h @@ -144,7 +144,7 @@ #ifndef NOLIBC_NO_RUNTIME /* startup code */ -void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _start(void) +void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector _start(void) { __asm__ volatile ( "move $a0, $sp\n" /* save stack pointer to $a0, as arg1 of _start_c */ diff --git a/tools/include/nolibc/arch-m68k.h b/tools/include/nolibc/arch-m68k.h index 81d34c219a42..341f434a530c 100644 --- a/tools/include/nolibc/arch-m68k.h +++ b/tools/include/nolibc/arch-m68k.h @@ -130,7 +130,7 @@ #ifndef NOLIBC_NO_RUNTIME void _start(void); -void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _start(void) +void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector _start(void) { __asm__ volatile ( "movel %sp, %sp@-\n" diff --git a/tools/include/nolibc/arch-mips.h b/tools/include/nolibc/arch-mips.h index bb9d580ea1b1..2b7a0bd3fc30 100644 --- a/tools/include/nolibc/arch-mips.h +++ b/tools/include/nolibc/arch-mips.h @@ -257,7 +257,7 @@ #ifndef NOLIBC_NO_RUNTIME /* startup code, note that it's called __start on MIPS */ void __start(void); -void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector __start(void) +void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector __start(void) { __asm__ volatile ( "move $a0, $sp\n" /* save stack pointer to $a0, as arg1 of _start_c */ diff --git a/tools/include/nolibc/arch-powerpc.h b/tools/include/nolibc/arch-powerpc.h index ef878868aa4a..111cda70f2cc 100644 --- a/tools/include/nolibc/arch-powerpc.h +++ b/tools/include/nolibc/arch-powerpc.h @@ -177,15 +177,15 @@ * "omit-frame-pointer" fails with __attribute__((no_stack_protector)) but * works with __attribute__((__optimize__("-fno-stack-protector"))) */ -#ifdef __no_stack_protector -#undef __no_stack_protector -#define __no_stack_protector __attribute__((__optimize__("-fno-stack-protector"))) +#ifdef __nolibc_no_stack_protector +#undef __nolibc_no_stack_protector +#define __nolibc_no_stack_protector __attribute__((__optimize__("-fno-stack-protector"))) #endif #endif /* !__powerpc64__ */ #ifndef NOLIBC_NO_RUNTIME /* startup code */ -void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _start(void) +void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector _start(void) { #ifdef __powerpc64__ #if _CALL_ELF == 2 diff --git a/tools/include/nolibc/arch-riscv.h b/tools/include/nolibc/arch-riscv.h index 386ebb9f5b08..1e84ed2e63b3 100644 --- a/tools/include/nolibc/arch-riscv.h +++ b/tools/include/nolibc/arch-riscv.h @@ -141,7 +141,7 @@ #ifndef NOLIBC_NO_RUNTIME /* startup code */ -void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _start(void) +void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector _start(void) { __asm__ volatile ( ".option push\n" diff --git a/tools/include/nolibc/arch-s390.h b/tools/include/nolibc/arch-s390.h index 4e69123ae484..3f05c01aecc6 100644 --- a/tools/include/nolibc/arch-s390.h +++ b/tools/include/nolibc/arch-s390.h @@ -145,7 +145,7 @@ #ifndef NOLIBC_NO_RUNTIME /* startup code */ -void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _start(void) +void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector _start(void) { __asm__ volatile ( "lgr %r2, %r15\n" /* save stack pointer to %r2, as arg1 of _start_c */ diff --git a/tools/include/nolibc/arch-sh.h b/tools/include/nolibc/arch-sh.h index b5a64ceeec97..a32378fd621f 100644 --- a/tools/include/nolibc/arch-sh.h +++ b/tools/include/nolibc/arch-sh.h @@ -143,7 +143,7 @@ #ifndef NOLIBC_NO_RUNTIME /* startup code */ void _start_wrapper(void); -void __attribute__((weak,noreturn)) __nolibc_entrypoint __no_stack_protector _start_wrapper(void) +void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector _start_wrapper(void) { __asm__ volatile ( ".global _start\n" /* The C function will have a prologue, */ diff --git a/tools/include/nolibc/arch-sparc.h b/tools/include/nolibc/arch-sparc.h index 240539d069a8..ddae9bc10dfe 100644 --- a/tools/include/nolibc/arch-sparc.h +++ b/tools/include/nolibc/arch-sparc.h @@ -154,7 +154,7 @@ #ifndef NOLIBC_NO_RUNTIME /* startup code */ -void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _start(void) +void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector _start(void) { __asm__ volatile ( /* diff --git a/tools/include/nolibc/arch-x86.h b/tools/include/nolibc/arch-x86.h index 769ba01a8629..db5ccba772d0 100644 --- a/tools/include/nolibc/arch-x86.h +++ b/tools/include/nolibc/arch-x86.h @@ -165,7 +165,7 @@ * 2) The deepest stack frame should be set to zero * */ -void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _start(void) +void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector _start(void) { __asm__ volatile ( "xor %ebp, %ebp\n" /* zero the stack frame */ @@ -333,7 +333,7 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _s * 2) The deepest stack frame should be zero (the %rbp). * */ -void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _start(void) +void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector _start(void) { __asm__ volatile ( "xor %ebp, %ebp\n" /* zero the stack frame */ diff --git a/tools/include/nolibc/compiler.h b/tools/include/nolibc/compiler.h index b56570bf9f69..f2d7a81d0d7c 100644 --- a/tools/include/nolibc/compiler.h +++ b/tools/include/nolibc/compiler.h @@ -36,9 +36,9 @@ #endif /* defined(__SSP__) ... */ #if __nolibc_has_attribute(no_stack_protector) -# define __no_stack_protector __attribute__((no_stack_protector)) +# define __nolibc_no_stack_protector __attribute__((no_stack_protector)) #else -# define __no_stack_protector __attribute__((__optimize__("-fno-stack-protector"))) +# define __nolibc_no_stack_protector __attribute__((__optimize__("-fno-stack-protector"))) #endif /* __nolibc_has_attribute(no_stack_protector) */ #if __nolibc_has_attribute(__fallthrough__) diff --git a/tools/include/nolibc/stackprotector.h b/tools/include/nolibc/stackprotector.h index ae8b1d3a374d..e11c20c75465 100644 --- a/tools/include/nolibc/stackprotector.h +++ b/tools/include/nolibc/stackprotector.h @@ -40,7 +40,7 @@ void __stack_chk_fail_local(void) __attribute__((weak,used,section(".data.nolibc_stack_chk"))) uintptr_t __stack_chk_guard; -static __no_stack_protector void __stack_chk_init(void) +static __nolibc_no_stack_protector void __stack_chk_init(void) { __nolibc_syscall3(__NR_getrandom, &__stack_chk_guard, sizeof(__stack_chk_guard), 0); /* a bit more randomness in case getrandom() fails, ensure the guard is never 0 */ From 5b1528c33befa323c783bbaf9a8a67371e3bcbd5 Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Sat, 25 Apr 2026 20:13:15 +0900 Subject: [PATCH 06/37] tools/nolibc: Don't use stack protector before setting it up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stack protector is configured in _start_c() so we shouldn't use it before then. Add __nolibc_no_stack_protector to _start_c() to avoid the compiler generating stack protector code for _start_c() and thus using it before its configured. Signed-off-by: Daniel Palmer Link: https://patch.msgid.link/20260425111315.3191461-3-daniel@thingy.jp Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/crt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/nolibc/crt.h b/tools/include/nolibc/crt.h index f38590a05adf..78124b6598a7 100644 --- a/tools/include/nolibc/crt.h +++ b/tools/include/nolibc/crt.h @@ -47,7 +47,7 @@ char *__nolibc_program_invocation_short_name(char *long_name) #endif /* NOLIBC_IGNORE_ERRNO */ void _start_c(long *sp); -__attribute__((weak,used)) __nolibc_no_sanitize_undefined +__attribute__((weak,used)) __nolibc_no_sanitize_undefined __nolibc_no_stack_protector void _start_c(long *sp) { long argc; From 0cb18b5b27b9a61298cb9c5e6f7f87e27b4529c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sun, 19 Apr 2026 17:29:03 +0200 Subject: [PATCH 07/37] selftests/nolibc: drop unnecessary 'mode' argument to open() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mode is unnecessary here, drop it. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260419-nolibc-open-mode-v1-1-8dc5a960daa7@weissschuh.net --- tools/testing/selftests/nolibc/nolibc-test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index eede2887acf6..598897141edf 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -798,7 +798,7 @@ int test_getdents64(const char *dir) int fd, ret; int err; - ret = fd = open(dir, O_RDONLY | O_DIRECTORY, 0); + ret = fd = open(dir, O_RDONLY | O_DIRECTORY); if (ret < 0) return ret; From 7ffb6e99c3662322fd9ffdd4417c2aabd4af95b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sun, 19 Apr 2026 17:29:04 +0200 Subject: [PATCH 08/37] tools/nolibc: add creat() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit creat() is a simple wrapper around open(). Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260419-nolibc-open-mode-v1-2-8dc5a960daa7@weissschuh.net --- tools/include/nolibc/fcntl.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/include/nolibc/fcntl.h b/tools/include/nolibc/fcntl.h index ed2f5553c65a..56650a36f856 100644 --- a/tools/include/nolibc/fcntl.h +++ b/tools/include/nolibc/fcntl.h @@ -66,4 +66,14 @@ int open(const char *path, int flags, ...) return __sysret(_sys_open(path, flags, mode)); } +/* + * int creat(const char *path, mode_t mode); + */ + +static __attribute__((unused)) +int creat(const char *path, mode_t mode) +{ + return open(path, O_CREAT | O_WRONLY | O_TRUNC, mode); +} + #endif /* _NOLIBC_FCNTL_H */ From f38a0bb74576f418f264b8d407be8de7aac1f42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 18 Apr 2026 12:19:56 +0200 Subject: [PATCH 09/37] tools/nolibc: also handle _llseek system call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On some architectures the llseek system call contains a leading underscore. Treat it the same way as llseek and prefer it over the plain lseek system call as is necessary for 64-bit offset handling. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260418-nolibc-largefile-v1-1-b91f0775bac3@weissschuh.net --- tools/include/nolibc/sys.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index fd7a2ee780e8..841158eb07c3 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -611,12 +611,18 @@ int link(const char *old, const char *new) static __attribute__((unused)) off_t _sys_lseek(int fd, off_t offset, int whence) { -#if defined(__NR_llseek) +#if defined(__NR_llseek) || defined(__NR__llseek) __kernel_loff_t loff = 0; + int ret, nr_llseek; off_t result; - int ret; - ret = __nolibc_syscall5(__NR_llseek, fd, offset >> 32, (uint32_t)offset, &loff, whence); +#if defined(__NR_llseek) + nr_llseek = __NR_llseek; +#else + nr_llseek = __NR__llseek; +#endif + + ret = __nolibc_syscall5(nr_llseek, fd, offset >> 32, (uint32_t)offset, &loff, whence); if (ret < 0) result = ret; else From 32db83195f75bda670ca9282d9868ad02b1d6185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 18 Apr 2026 12:19:57 +0200 Subject: [PATCH 10/37] tools/nolibc: add __nolibc_arg_to_reg() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the architecture specific system call glue, all arguments are currently casted to 'long' to fit into registers. This works for pointers as 'long' has the same size as pointers. However the system call registers for X32 and MIPS N32 need to be 'long long' to work correctly for 64-bit values expected by the system call ABI. Casting a pointer to a 'long long' will produce a compiler warning while casting 64-bit integers to 'long' will truncate those. Add a helper which can be used to correctly cast both pointers and integers into 'long long' registers. Cast the pointers through 'unsigned' to avoid any sign extensions. Both builtins have been available since at least GCC 3 and clang 3. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260418-nolibc-largefile-v1-2-b91f0775bac3@weissschuh.net --- tools/include/nolibc/crt.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/include/nolibc/crt.h b/tools/include/nolibc/crt.h index 78124b6598a7..714256228914 100644 --- a/tools/include/nolibc/crt.h +++ b/tools/include/nolibc/crt.h @@ -7,6 +7,10 @@ #ifndef _NOLIBC_CRT_H #define _NOLIBC_CRT_H +#define __nolibc_arg_to_reg(_a) \ + __builtin_choose_expr(__builtin_classify_type(_a) == __builtin_classify_type(NULL), \ + (unsigned long)(_a), (_a)) + #ifndef NOLIBC_NO_RUNTIME #include "compiler.h" From c503c3deb9b56645413d8f8560dcc24efe7cea59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 18 Apr 2026 12:19:58 +0200 Subject: [PATCH 11/37] tools/nolibc: cast pointers returned from system calls through integers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently all system call wrappers return 'long' integers which can be directly cast to 'void *' if the returned value is actually a pointer. An upcoming change will change the system call wrappers to sometimes return 'long long' which can not be cast to a pointer directly. Add explicit cast through 'long' to prepare for this. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260418-nolibc-largefile-v1-3-b91f0775bac3@weissschuh.net --- tools/include/nolibc/sys.h | 2 +- tools/include/nolibc/sys/mman.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index 841158eb07c3..33f9c970ae57 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -101,7 +101,7 @@ static __inline__ int __nolibc_enosys(const char *syscall, ...) static __attribute__((unused)) void *_sys_brk(void *addr) { - return (void *)__nolibc_syscall1(__NR_brk, addr); + return (void *)(unsigned long)__nolibc_syscall1(__NR_brk, addr); } static __attribute__((unused)) diff --git a/tools/include/nolibc/sys/mman.h b/tools/include/nolibc/sys/mman.h index 91d77a51412d..72bc1d43d1d4 100644 --- a/tools/include/nolibc/sys/mman.h +++ b/tools/include/nolibc/sys/mman.h @@ -27,7 +27,7 @@ void *_sys_mmap(void *addr, size_t length, int prot, int flags, int fd, n = __NR_mmap; #endif - return (void *)__nolibc_syscall6(n, addr, length, prot, flags, fd, offset); + return (void *)(unsigned long)__nolibc_syscall6(n, addr, length, prot, flags, fd, offset); } #endif @@ -46,8 +46,8 @@ void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) static __attribute__((unused)) void *_sys_mremap(void *old_address, size_t old_size, size_t new_size, int flags, void *new_address) { - return (void *)__nolibc_syscall5(__NR_mremap, old_address, old_size, - new_size, flags, new_address); + return (void *)(unsigned long)__nolibc_syscall5(__NR_mremap, old_address, old_size, + new_size, flags, new_address); } static __attribute__((unused)) From 7070421c9f3324446805e9ab3883deb40ba47c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 18 Apr 2026 12:19:59 +0200 Subject: [PATCH 12/37] tools/nolibc: handle 64-bit system call arguments on x32 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The x32 system call ABI expects 64-bit values directly in registers. This does not work on nolibc currently, as a 'long' is only 32 bits wide. Switch the system call wrappers to use 'long long' instead which can handle 64-bit values on x32. As on x86_64 'long' and 'long long' are the same, this does not change the behavior there. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260418-nolibc-largefile-v1-4-b91f0775bac3@weissschuh.net --- tools/include/nolibc/arch-x86.h | 70 ++++++++++++++++----------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/tools/include/nolibc/arch-x86.h b/tools/include/nolibc/arch-x86.h index db5ccba772d0..fe152ac2650b 100644 --- a/tools/include/nolibc/arch-x86.h +++ b/tools/include/nolibc/arch-x86.h @@ -202,8 +202,8 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote #define __nolibc_syscall0(num) \ ({ \ - long _ret; \ - register long _num __asm__ ("rax") = (num); \ + long long _ret; \ + register long long _num __asm__ ("rax") = (num); \ \ __asm__ volatile ( \ "syscall\n" \ @@ -216,9 +216,9 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote #define __nolibc_syscall1(num, arg1) \ ({ \ - long _ret; \ - register long _num __asm__ ("rax") = (num); \ - register long _arg1 __asm__ ("rdi") = (long)(arg1); \ + long long _ret; \ + register long long _num __asm__ ("rax") = (num); \ + register long long _arg1 __asm__ ("rdi") = __nolibc_arg_to_reg(arg1); \ \ __asm__ volatile ( \ "syscall\n" \ @@ -232,10 +232,10 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote #define __nolibc_syscall2(num, arg1, arg2) \ ({ \ - long _ret; \ - register long _num __asm__ ("rax") = (num); \ - register long _arg1 __asm__ ("rdi") = (long)(arg1); \ - register long _arg2 __asm__ ("rsi") = (long)(arg2); \ + long long _ret; \ + register long long _num __asm__ ("rax") = (num); \ + register long long _arg1 __asm__ ("rdi") = __nolibc_arg_to_reg(arg1); \ + register long long _arg2 __asm__ ("rsi") = __nolibc_arg_to_reg(arg2); \ \ __asm__ volatile ( \ "syscall\n" \ @@ -249,11 +249,11 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote #define __nolibc_syscall3(num, arg1, arg2, arg3) \ ({ \ - long _ret; \ - register long _num __asm__ ("rax") = (num); \ - register long _arg1 __asm__ ("rdi") = (long)(arg1); \ - register long _arg2 __asm__ ("rsi") = (long)(arg2); \ - register long _arg3 __asm__ ("rdx") = (long)(arg3); \ + long long _ret; \ + register long long _num __asm__ ("rax") = (num); \ + register long long _arg1 __asm__ ("rdi") = __nolibc_arg_to_reg(arg1); \ + register long long _arg2 __asm__ ("rsi") = __nolibc_arg_to_reg(arg2); \ + register long long _arg3 __asm__ ("rdx") = __nolibc_arg_to_reg(arg3); \ \ __asm__ volatile ( \ "syscall\n" \ @@ -267,12 +267,12 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote #define __nolibc_syscall4(num, arg1, arg2, arg3, arg4) \ ({ \ - long _ret; \ - register long _num __asm__ ("rax") = (num); \ - register long _arg1 __asm__ ("rdi") = (long)(arg1); \ - register long _arg2 __asm__ ("rsi") = (long)(arg2); \ - register long _arg3 __asm__ ("rdx") = (long)(arg3); \ - register long _arg4 __asm__ ("r10") = (long)(arg4); \ + long long _ret; \ + register long long _num __asm__ ("rax") = (num); \ + register long long _arg1 __asm__ ("rdi") = __nolibc_arg_to_reg(arg1); \ + register long long _arg2 __asm__ ("rsi") = __nolibc_arg_to_reg(arg2); \ + register long long _arg3 __asm__ ("rdx") = __nolibc_arg_to_reg(arg3); \ + register long long _arg4 __asm__ ("r10") = __nolibc_arg_to_reg(arg4); \ \ __asm__ volatile ( \ "syscall\n" \ @@ -286,13 +286,13 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote #define __nolibc_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ ({ \ - long _ret; \ - register long _num __asm__ ("rax") = (num); \ - register long _arg1 __asm__ ("rdi") = (long)(arg1); \ - register long _arg2 __asm__ ("rsi") = (long)(arg2); \ - register long _arg3 __asm__ ("rdx") = (long)(arg3); \ - register long _arg4 __asm__ ("r10") = (long)(arg4); \ - register long _arg5 __asm__ ("r8") = (long)(arg5); \ + long long _ret; \ + register long long _num __asm__ ("rax") = (num); \ + register long long _arg1 __asm__ ("rdi") = __nolibc_arg_to_reg(arg1); \ + register long long _arg2 __asm__ ("rsi") = __nolibc_arg_to_reg(arg2); \ + register long long _arg3 __asm__ ("rdx") = __nolibc_arg_to_reg(arg3); \ + register long long _arg4 __asm__ ("r10") = __nolibc_arg_to_reg(arg4); \ + register long long _arg5 __asm__ ("r8") = __nolibc_arg_to_reg(arg5); \ \ __asm__ volatile ( \ "syscall\n" \ @@ -306,14 +306,14 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote #define __nolibc_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ ({ \ - long _ret; \ - register long _num __asm__ ("rax") = (num); \ - register long _arg1 __asm__ ("rdi") = (long)(arg1); \ - register long _arg2 __asm__ ("rsi") = (long)(arg2); \ - register long _arg3 __asm__ ("rdx") = (long)(arg3); \ - register long _arg4 __asm__ ("r10") = (long)(arg4); \ - register long _arg5 __asm__ ("r8") = (long)(arg5); \ - register long _arg6 __asm__ ("r9") = (long)(arg6); \ + long long _ret; \ + register long long _num __asm__ ("rax") = (num); \ + register long long _arg1 __asm__ ("rdi") = __nolibc_arg_to_reg(arg1); \ + register long long _arg2 __asm__ ("rsi") = __nolibc_arg_to_reg(arg2); \ + register long long _arg3 __asm__ ("rdx") = __nolibc_arg_to_reg(arg3); \ + register long long _arg4 __asm__ ("r10") = __nolibc_arg_to_reg(arg4); \ + register long long _arg5 __asm__ ("r8") = __nolibc_arg_to_reg(arg5); \ + register long long _arg6 __asm__ ("r9") = __nolibc_arg_to_reg(arg6); \ \ __asm__ volatile ( \ "syscall\n" \ From 02937a241c811cd31e052a16c0ea036b5071d052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 18 Apr 2026 12:20:00 +0200 Subject: [PATCH 13/37] tools/nolibc: handle 64-bit system call arguments on MIPS N32 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The N32 system call ABI expects 64-bit values directly in registers. This does not work on nolibc currently, as a 'long' is only 32 bits wide. Switch the system call wrappers to use 'long long' instead which can handle 64-bit values on N32. As on N64 'long' and 'long long' are the same, this does not change the behavior there. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260418-nolibc-largefile-v1-5-b91f0775bac3@weissschuh.net --- tools/include/nolibc/arch-mips.h | 94 +++++++++++++++++--------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/tools/include/nolibc/arch-mips.h b/tools/include/nolibc/arch-mips.h index 2b7a0bd3fc30..1400653c76c1 100644 --- a/tools/include/nolibc/arch-mips.h +++ b/tools/include/nolibc/arch-mips.h @@ -55,6 +55,8 @@ #define _NOLIBC_SYSCALL_STACK_RESERVE "addiu $sp, $sp, -32\n" #define _NOLIBC_SYSCALL_STACK_UNRESERVE "addiu $sp, $sp, 32\n" +#define _NOLIBC_SYSCALL_REG register long + #else /* _ABIN32 || _ABI64 */ /* binutils, GCC and clang disagree about register aliases, use numbers instead. */ @@ -66,12 +68,14 @@ #define _NOLIBC_SYSCALL_STACK_RESERVE #define _NOLIBC_SYSCALL_STACK_UNRESERVE +#define _NOLIBC_SYSCALL_REG register long long + #endif /* _ABIO32 */ #define __nolibc_syscall0(num) \ ({ \ - register long _num __asm__ ("v0") = (num); \ - register long _arg4 __asm__ ("a3"); \ + _NOLIBC_SYSCALL_REG _num __asm__ ("v0") = (num); \ + _NOLIBC_SYSCALL_REG _arg4 __asm__ ("a3"); \ \ __asm__ volatile ( \ _NOLIBC_SYSCALL_STACK_RESERVE \ @@ -86,9 +90,9 @@ #define __nolibc_syscall1(num, arg1) \ ({ \ - register long _num __asm__ ("v0") = (num); \ - register long _arg1 __asm__ ("a0") = (long)(arg1); \ - register long _arg4 __asm__ ("a3"); \ + _NOLIBC_SYSCALL_REG _num __asm__ ("v0") = (num); \ + _NOLIBC_SYSCALL_REG _arg1 __asm__ ("a0") = __nolibc_arg_to_reg(arg1); \ + _NOLIBC_SYSCALL_REG _arg4 __asm__ ("a3"); \ \ __asm__ volatile ( \ _NOLIBC_SYSCALL_STACK_RESERVE \ @@ -104,10 +108,10 @@ #define __nolibc_syscall2(num, arg1, arg2) \ ({ \ - register long _num __asm__ ("v0") = (num); \ - register long _arg1 __asm__ ("a0") = (long)(arg1); \ - register long _arg2 __asm__ ("a1") = (long)(arg2); \ - register long _arg4 __asm__ ("a3"); \ + _NOLIBC_SYSCALL_REG _num __asm__ ("v0") = (num); \ + _NOLIBC_SYSCALL_REG _arg1 __asm__ ("a0") = __nolibc_arg_to_reg(arg1); \ + _NOLIBC_SYSCALL_REG _arg2 __asm__ ("a1") = __nolibc_arg_to_reg(arg2); \ + _NOLIBC_SYSCALL_REG _arg4 __asm__ ("a3"); \ \ __asm__ volatile ( \ _NOLIBC_SYSCALL_STACK_RESERVE \ @@ -123,11 +127,11 @@ #define __nolibc_syscall3(num, arg1, arg2, arg3) \ ({ \ - register long _num __asm__ ("v0") = (num); \ - register long _arg1 __asm__ ("a0") = (long)(arg1); \ - register long _arg2 __asm__ ("a1") = (long)(arg2); \ - register long _arg3 __asm__ ("a2") = (long)(arg3); \ - register long _arg4 __asm__ ("a3"); \ + _NOLIBC_SYSCALL_REG _num __asm__ ("v0") = (num); \ + _NOLIBC_SYSCALL_REG _arg1 __asm__ ("a0") = __nolibc_arg_to_reg(arg1); \ + _NOLIBC_SYSCALL_REG _arg2 __asm__ ("a1") = __nolibc_arg_to_reg(arg2); \ + _NOLIBC_SYSCALL_REG _arg3 __asm__ ("a2") = __nolibc_arg_to_reg(arg3); \ + _NOLIBC_SYSCALL_REG _arg4 __asm__ ("a3"); \ \ __asm__ volatile ( \ _NOLIBC_SYSCALL_STACK_RESERVE \ @@ -143,11 +147,11 @@ #define __nolibc_syscall4(num, arg1, arg2, arg3, arg4) \ ({ \ - register long _num __asm__ ("v0") = (num); \ - register long _arg1 __asm__ ("a0") = (long)(arg1); \ - register long _arg2 __asm__ ("a1") = (long)(arg2); \ - register long _arg3 __asm__ ("a2") = (long)(arg3); \ - register long _arg4 __asm__ ("a3") = (long)(arg4); \ + _NOLIBC_SYSCALL_REG _num __asm__ ("v0") = (num); \ + _NOLIBC_SYSCALL_REG _arg1 __asm__ ("a0") = __nolibc_arg_to_reg(arg1); \ + _NOLIBC_SYSCALL_REG _arg2 __asm__ ("a1") = __nolibc_arg_to_reg(arg2); \ + _NOLIBC_SYSCALL_REG _arg3 __asm__ ("a2") = __nolibc_arg_to_reg(arg3); \ + _NOLIBC_SYSCALL_REG _arg4 __asm__ ("a3") = __nolibc_arg_to_reg(arg4); \ \ __asm__ volatile ( \ _NOLIBC_SYSCALL_STACK_RESERVE \ @@ -165,12 +169,12 @@ #define __nolibc_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ ({ \ - register long _num __asm__ ("v0") = (num); \ - register long _arg1 __asm__ ("a0") = (long)(arg1); \ - register long _arg2 __asm__ ("a1") = (long)(arg2); \ - register long _arg3 __asm__ ("a2") = (long)(arg3); \ - register long _arg4 __asm__ ("a3") = (long)(arg4); \ - register long _arg5 = (long)(arg5); \ + _NOLIBC_SYSCALL_REG _num __asm__ ("v0") = (num); \ + _NOLIBC_SYSCALL_REG _arg1 __asm__ ("a0") = __nolibc_arg_to_reg(arg1); \ + _NOLIBC_SYSCALL_REG _arg2 __asm__ ("a1") = __nolibc_arg_to_reg(arg2); \ + _NOLIBC_SYSCALL_REG _arg3 __asm__ ("a2") = __nolibc_arg_to_reg(arg3); \ + _NOLIBC_SYSCALL_REG _arg4 __asm__ ("a3") = __nolibc_arg_to_reg(arg4); \ + _NOLIBC_SYSCALL_REG _arg5 = __nolibc_arg_to_reg(arg5); \ \ __asm__ volatile ( \ _NOLIBC_SYSCALL_STACK_RESERVE \ @@ -187,13 +191,13 @@ #define __nolibc_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ ({ \ - register long _num __asm__ ("v0") = (num); \ - register long _arg1 __asm__ ("a0") = (long)(arg1); \ - register long _arg2 __asm__ ("a1") = (long)(arg2); \ - register long _arg3 __asm__ ("a2") = (long)(arg3); \ - register long _arg4 __asm__ ("a3") = (long)(arg4); \ - register long _arg5 = (long)(arg5); \ - register long _arg6 = (long)(arg6); \ + _NOLIBC_SYSCALL_REG _num __asm__ ("v0") = (num); \ + _NOLIBC_SYSCALL_REG _arg1 __asm__ ("a0") = __nolibc_arg_to_reg(arg1); \ + _NOLIBC_SYSCALL_REG _arg2 __asm__ ("a1") = __nolibc_arg_to_reg(arg2); \ + _NOLIBC_SYSCALL_REG _arg3 __asm__ ("a2") = __nolibc_arg_to_reg(arg3); \ + _NOLIBC_SYSCALL_REG _arg4 __asm__ ("a3") = __nolibc_arg_to_reg(arg4); \ + _NOLIBC_SYSCALL_REG _arg5 = __nolibc_arg_to_reg(arg5); \ + _NOLIBC_SYSCALL_REG _arg6 = __nolibc_arg_to_reg(arg6); \ \ __asm__ volatile ( \ _NOLIBC_SYSCALL_STACK_RESERVE \ @@ -214,12 +218,12 @@ #define __nolibc_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ ({ \ - register long _num __asm__ ("v0") = (num); \ - register long _arg1 __asm__ ("$4") = (long)(arg1); \ - register long _arg2 __asm__ ("$5") = (long)(arg2); \ - register long _arg3 __asm__ ("$6") = (long)(arg3); \ - register long _arg4 __asm__ ("$7") = (long)(arg4); \ - register long _arg5 __asm__ ("$8") = (long)(arg5); \ + _NOLIBC_SYSCALL_REG _num __asm__ ("v0") = (num); \ + _NOLIBC_SYSCALL_REG _arg1 __asm__ ("$4") = __nolibc_arg_to_reg(arg1); \ + _NOLIBC_SYSCALL_REG _arg2 __asm__ ("$5") = __nolibc_arg_to_reg(arg2); \ + _NOLIBC_SYSCALL_REG _arg3 __asm__ ("$6") = __nolibc_arg_to_reg(arg3); \ + _NOLIBC_SYSCALL_REG _arg4 __asm__ ("$7") = __nolibc_arg_to_reg(arg4); \ + _NOLIBC_SYSCALL_REG _arg5 __asm__ ("$8") = __nolibc_arg_to_reg(arg5); \ \ __asm__ volatile ( \ "syscall\n" \ @@ -233,13 +237,13 @@ #define __nolibc_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ ({ \ - register long _num __asm__ ("v0") = (num); \ - register long _arg1 __asm__ ("$4") = (long)(arg1); \ - register long _arg2 __asm__ ("$5") = (long)(arg2); \ - register long _arg3 __asm__ ("$6") = (long)(arg3); \ - register long _arg4 __asm__ ("$7") = (long)(arg4); \ - register long _arg5 __asm__ ("$8") = (long)(arg5); \ - register long _arg6 __asm__ ("$9") = (long)(arg6); \ + _NOLIBC_SYSCALL_REG _num __asm__ ("v0") = (num); \ + _NOLIBC_SYSCALL_REG _arg1 __asm__ ("$4") = __nolibc_arg_to_reg(arg1); \ + _NOLIBC_SYSCALL_REG _arg2 __asm__ ("$5") = __nolibc_arg_to_reg(arg2); \ + _NOLIBC_SYSCALL_REG _arg3 __asm__ ("$6") = __nolibc_arg_to_reg(arg3); \ + _NOLIBC_SYSCALL_REG _arg4 __asm__ ("$7") = __nolibc_arg_to_reg(arg4); \ + _NOLIBC_SYSCALL_REG _arg5 __asm__ ("$8") = __nolibc_arg_to_reg(arg5); \ + _NOLIBC_SYSCALL_REG _arg6 __asm__ ("$9") = __nolibc_arg_to_reg(arg6); \ \ __asm__ volatile ( \ "syscall\n" \ From ce7677458e680d0742fb7f86c0e27272e4c56205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 18 Apr 2026 12:20:01 +0200 Subject: [PATCH 14/37] tools/nolibc: open files with O_LARGEFILE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nolibc can natively handle large files. Tell this to the kernel by always using O_LARGEFILE when opening files. This is also how other libcs do it. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260418-nolibc-largefile-v1-6-b91f0775bac3@weissschuh.net --- tools/include/nolibc/fcntl.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/include/nolibc/fcntl.h b/tools/include/nolibc/fcntl.h index 56650a36f856..014910a8e928 100644 --- a/tools/include/nolibc/fcntl.h +++ b/tools/include/nolibc/fcntl.h @@ -29,6 +29,8 @@ int openat(int dirfd, const char *path, int flags, ...) { mode_t mode = 0; + flags |= O_LARGEFILE; + if (flags & O_CREAT) { va_list args; @@ -55,6 +57,8 @@ int open(const char *path, int flags, ...) { mode_t mode = 0; + flags |= O_LARGEFILE; + if (flags & O_CREAT) { va_list args; From 266df86c4893fed1a7f027e767fe1c7f6456b100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 18 Apr 2026 12:20:02 +0200 Subject: [PATCH 15/37] selftests/nolibc: test large file support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make sure nolibc correctly handles large files. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260418-nolibc-largefile-v1-7-b91f0775bac3@weissschuh.net --- tools/testing/selftests/nolibc/nolibc-test.c | 48 ++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index 598897141edf..08610cacf030 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -2,6 +2,7 @@ #define _GNU_SOURCE #define _LARGEFILE64_SOURCE +#define _FILE_OFFSET_BITS 64 /* libc-specific include files * The program may be built in 3 ways: @@ -1382,6 +1383,52 @@ int test_namespace(void) return ret; } +int test_large_file(void) +{ + off_t large_seek = ((off_t)UINT32_MAX) + 100; + int fd, ret, saved_errno; + ssize_t written; + off_t off; + +#if defined(__mips__) && defined(_ABIN32) + /* https://lore.kernel.org/qemu-devel/fed03914-a95a-4522-a432-f129264cb2ac@t-8ch.de/ */ + if (getpid() != 1) + return 0; +#endif + + if (large_seek < UINT32_MAX) { + errno = EOVERFLOW; + return -1; + } + + fd = open("/tmp", O_TMPFILE | O_RDWR, 0644); + if (fd == -1) + return -1; + + off = lseek(fd, large_seek, SEEK_CUR); + if (off == -1) { + ret = off; + goto out; + } else if (off != large_seek) { + errno = ERANGE; + ret = -1; + goto out; + } + + written = write(fd, "1", 1); + if (written == -1) { + ret = written; + goto out; + } + + ret = 0; +out: + saved_errno = errno; + close(fd); + errno = saved_errno; + return ret; +} + /* Run syscall tests between IDs and . * Return 0 on success, non-zero on failure. */ @@ -1527,6 +1574,7 @@ int run_syscall(int min, int max) CASE_TEST(_syscall_noargs); EXPECT_SYSEQ(is_nolibc, _syscall(__NR_getpid), getpid()); break; CASE_TEST(_syscall_args); EXPECT_SYSEQ(is_nolibc, _syscall(__NR_statx, 0, NULL, 0, 0, NULL), -EFAULT); break; CASE_TEST(namespace); EXPECT_SYSZR(euid0 && proc, test_namespace()); break; + CASE_TEST(largefile); EXPECT_SYSZR(1, test_large_file()); break; case __LINE__: return ret; /* must be last */ /* note: do not set any defaults so as to permit holes above */ From 89b0d6386a4b48890397fb95978a6a049616204f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 29 Apr 2026 22:58:35 +0200 Subject: [PATCH 16/37] selftests/nolibc: align QEMU_ARCH_mips32be MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The variable is slightly misaligned. Fix that. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260429-nolibc-qemu-arch-v1-1-a2ca07eab297@weissschuh.net --- tools/testing/selftests/nolibc/Makefile.nolibc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index f30bc68470cc..509ab0dfd2ca 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -142,7 +142,7 @@ QEMU_ARCH_arm64 = aarch64 QEMU_ARCH_arm = arm QEMU_ARCH_armthumb = arm QEMU_ARCH_mips32le = mipsel # works with malta_defconfig -QEMU_ARCH_mips32be = mips +QEMU_ARCH_mips32be = mips QEMU_ARCH_mipsn32le = mips64el QEMU_ARCH_mipsn32be = mips64 QEMU_ARCH_mips64le = mips64el From 3c1ee4a323bb701e2f7e64bface5b552dab786ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 29 Apr 2026 22:58:36 +0200 Subject: [PATCH 17/37] selftests/nolibc: drop riscv configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The riscv configuration is just a duplication of the riscv64 one. Remove it. Passing ARCH=riscv will be rerouted to riscv64 anyways. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260429-nolibc-qemu-arch-v1-2-a2ca07eab297@weissschuh.net --- tools/testing/selftests/nolibc/Makefile.nolibc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index 509ab0dfd2ca..e9370f7a89de 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -83,7 +83,6 @@ IMAGE_mips64be = vmlinuz IMAGE_ppc = vmlinux IMAGE_ppc64 = vmlinux IMAGE_ppc64le = arch/powerpc/boot/zImage -IMAGE_riscv = arch/riscv/boot/Image IMAGE_riscv32 = arch/riscv/boot/Image IMAGE_riscv64 = arch/riscv/boot/Image IMAGE_s390x = arch/s390/boot/bzImage @@ -112,7 +111,6 @@ DEFCONFIG_mips64be = malta_defconfig generic/64r2.config generic/eb.config DEFCONFIG_ppc = pmac32_defconfig DEFCONFIG_ppc64 = powernv_be_defconfig DEFCONFIG_ppc64le = powernv_defconfig -DEFCONFIG_riscv = defconfig DEFCONFIG_riscv32 = rv32_defconfig DEFCONFIG_riscv64 = defconfig DEFCONFIG_s390x = defconfig @@ -150,7 +148,6 @@ QEMU_ARCH_mips64be = mips64 QEMU_ARCH_ppc = ppc QEMU_ARCH_ppc64 = ppc64 QEMU_ARCH_ppc64le = ppc64 -QEMU_ARCH_riscv = riscv64 QEMU_ARCH_riscv32 = riscv32 QEMU_ARCH_riscv64 = riscv64 QEMU_ARCH_s390x = s390x @@ -190,7 +187,6 @@ QEMU_ARGS_mips64be = -M malta -cpu 5KEc -append "panic=-1 $(TEST:%=NOLIBC_TEST QEMU_ARGS_ppc = -M g3beige -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_ppc64 = -M powernv -append "console=hvc0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_ppc64le = -M powernv -append "console=hvc0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" -QEMU_ARGS_riscv = -M virt -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_riscv32 = -M virt -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_riscv64 = -M virt -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_s390x = -M s390-ccw-virtio -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" From c906341912898c0b53b46d759f6c788767d315df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 29 Apr 2026 22:58:37 +0200 Subject: [PATCH 18/37] selftests/nolibc: use QEMU_ARCH for QEMU_ARCH_USER MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current logic forces the XARCH to QEMU_ARCH mapping to contain entries for all architectures. This will change. To avoid duplication of that logic, reuse the already computed QEMU_ARCH variable. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260429-nolibc-qemu-arch-v1-3-a2ca07eab297@weissschuh.net --- tools/testing/selftests/nolibc/Makefile.nolibc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index e9370f7a89de..6b424716a9f8 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -161,7 +161,7 @@ QEMU_ARCH = $(QEMU_ARCH_$(XARCH)) QEMU_ARCH_USER_ppc64le = ppc64le QEMU_ARCH_USER_mipsn32le = mipsn32el QEMU_ARCH_USER_mipsn32be = mipsn32 -QEMU_ARCH_USER = $(or $(QEMU_ARCH_USER_$(XARCH)),$(QEMU_ARCH_$(XARCH))) +QEMU_ARCH_USER = $(or $(QEMU_ARCH_USER_$(XARCH)),$(QEMU_ARCH)) QEMU_BIOS_DIR = /usr/share/edk2/ QEMU_BIOS_loongarch = $(QEMU_BIOS_DIR)/loongarch64/OVMF_CODE.fd From e00e6cf22b044e669a1531e2dac62a442713b988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 29 Apr 2026 22:58:38 +0200 Subject: [PATCH 19/37] selftests/nolibc: trim QEMU_ARCH mappings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For many configurations QEMU_ARCH is the same as XARCH. Slim down the table by automatically falling back to XARCH. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260429-nolibc-qemu-arch-v1-4-a2ca07eab297@weissschuh.net --- tools/testing/selftests/nolibc/Makefile.nolibc | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index 6b424716a9f8..57bbc28c52f4 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -132,12 +132,9 @@ EXTRACONFIG = $(EXTRACONFIG_$(XARCH)) TEST = # QEMU_ARCH: arch names used by qemu -QEMU_ARCH_i386 = i386 -QEMU_ARCH_x86_64 = x86_64 QEMU_ARCH_x32 = x86_64 QEMU_ARCH_x86 = x86_64 QEMU_ARCH_arm64 = aarch64 -QEMU_ARCH_arm = arm QEMU_ARCH_armthumb = arm QEMU_ARCH_mips32le = mipsel # works with malta_defconfig QEMU_ARCH_mips32be = mips @@ -145,18 +142,10 @@ QEMU_ARCH_mipsn32le = mips64el QEMU_ARCH_mipsn32be = mips64 QEMU_ARCH_mips64le = mips64el QEMU_ARCH_mips64be = mips64 -QEMU_ARCH_ppc = ppc -QEMU_ARCH_ppc64 = ppc64 QEMU_ARCH_ppc64le = ppc64 -QEMU_ARCH_riscv32 = riscv32 -QEMU_ARCH_riscv64 = riscv64 -QEMU_ARCH_s390x = s390x QEMU_ARCH_loongarch = loongarch64 QEMU_ARCH_sparc32 = sparc -QEMU_ARCH_sparc64 = sparc64 -QEMU_ARCH_m68k = m68k -QEMU_ARCH_sh4 = sh4 -QEMU_ARCH = $(QEMU_ARCH_$(XARCH)) +QEMU_ARCH = $(or $(QEMU_ARCH_$(XARCH)),$(XARCH)) QEMU_ARCH_USER_ppc64le = ppc64le QEMU_ARCH_USER_mipsn32le = mipsn32el From 69940c74e613ce2d261278092b93e47244b99616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 29 Apr 2026 22:58:39 +0200 Subject: [PATCH 20/37] selftests/nolibc: trim DEFCONFIG mappings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For many configurations DEFCONFIG is simply 'defconfig'. Slim down the table by automatically falling back to 'defconfig'. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260429-nolibc-qemu-arch-v1-5-a2ca07eab297@weissschuh.net --- tools/testing/selftests/nolibc/Makefile.nolibc | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index 57bbc28c52f4..1db1c7d9eeee 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -95,11 +95,6 @@ IMAGE = $(objtree)/$(IMAGE_$(XARCH)) IMAGE_NAME = $(notdir $(IMAGE)) # default kernel configurations that appear to be usable -DEFCONFIG_i386 = defconfig -DEFCONFIG_x86_64 = defconfig -DEFCONFIG_x32 = defconfig -DEFCONFIG_x86 = defconfig -DEFCONFIG_arm64 = defconfig DEFCONFIG_arm = multi_v7_defconfig DEFCONFIG_armthumb = multi_v7_defconfig DEFCONFIG_mips32le = malta_defconfig @@ -112,14 +107,11 @@ DEFCONFIG_ppc = pmac32_defconfig DEFCONFIG_ppc64 = powernv_be_defconfig DEFCONFIG_ppc64le = powernv_defconfig DEFCONFIG_riscv32 = rv32_defconfig -DEFCONFIG_riscv64 = defconfig -DEFCONFIG_s390x = defconfig -DEFCONFIG_loongarch = defconfig DEFCONFIG_sparc32 = sparc32_defconfig DEFCONFIG_sparc64 = sparc64_defconfig DEFCONFIG_m68k = virt_defconfig DEFCONFIG_sh4 = rts7751r2dplus_defconfig -DEFCONFIG = $(DEFCONFIG_$(XARCH)) +DEFCONFIG = $(or $(DEFCONFIG_$(XARCH)),defconfig) EXTRACONFIG_x32 = -e CONFIG_X86_X32_ABI EXTRACONFIG_arm = -e CONFIG_NAMESPACES From bdcdb8ff5bf76da84b05e35d1bc119db6946093b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 29 Apr 2026 22:58:40 +0200 Subject: [PATCH 21/37] selftests/nolibc: trim IMAGE mappings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For many configurations QEMU_ARCH is simply 'vmlinux'. Slim down the table by automatically falling back to 'vmlinux'. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260429-nolibc-qemu-arch-v1-6-a2ca07eab297@weissschuh.net --- tools/testing/selftests/nolibc/Makefile.nolibc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index 1db1c7d9eeee..b728c3ef31a1 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -80,8 +80,6 @@ IMAGE_mipsn32le = vmlinuz IMAGE_mipsn32be = vmlinuz IMAGE_mips64le = vmlinuz IMAGE_mips64be = vmlinuz -IMAGE_ppc = vmlinux -IMAGE_ppc64 = vmlinux IMAGE_ppc64le = arch/powerpc/boot/zImage IMAGE_riscv32 = arch/riscv/boot/Image IMAGE_riscv64 = arch/riscv/boot/Image @@ -89,9 +87,8 @@ IMAGE_s390x = arch/s390/boot/bzImage IMAGE_loongarch = arch/loongarch/boot/vmlinuz.efi IMAGE_sparc32 = arch/sparc/boot/image IMAGE_sparc64 = arch/sparc/boot/image -IMAGE_m68k = vmlinux IMAGE_sh4 = arch/sh/boot/zImage -IMAGE = $(objtree)/$(IMAGE_$(XARCH)) +IMAGE = $(objtree)/$(or $(IMAGE_$(XARCH)),vmlinux) IMAGE_NAME = $(notdir $(IMAGE)) # default kernel configurations that appear to be usable From 92f1d3340a65173f5c921a3e8d2830cd2dc88881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 29 Apr 2026 22:58:41 +0200 Subject: [PATCH 22/37] selftests/nolibc: use vmlinux for MIPS tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QEMU for MIPS can also load 'vmlinux'. Slim down the table by using that from the fallback. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260429-nolibc-qemu-arch-v1-7-a2ca07eab297@weissschuh.net --- tools/testing/selftests/nolibc/Makefile.nolibc | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index b728c3ef31a1..41d616a01db5 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -74,12 +74,6 @@ IMAGE_x86 = arch/x86/boot/bzImage IMAGE_arm64 = arch/arm64/boot/Image IMAGE_arm = arch/arm/boot/zImage IMAGE_armthumb = arch/arm/boot/zImage -IMAGE_mips32le = vmlinuz -IMAGE_mips32be = vmlinuz -IMAGE_mipsn32le = vmlinuz -IMAGE_mipsn32be = vmlinuz -IMAGE_mips64le = vmlinuz -IMAGE_mips64be = vmlinuz IMAGE_ppc64le = arch/powerpc/boot/zImage IMAGE_riscv32 = arch/riscv/boot/Image IMAGE_riscv64 = arch/riscv/boot/Image From 97d6655082757fe9ce39e110e7853127a0840542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 15 Apr 2026 11:11:31 +0200 Subject: [PATCH 23/37] tools/nolibc: add support for OpenRISC / or1k MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for OpenRISC / or1k to nolibc. _start() uses the same wrapper construct as in arch-sh.h. libgcc is necessary as OpenRISC is missing 64-bit multiplication. Signed-off-by: Thomas Weißschuh Acked-by: Stafford Horne Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260429-nolibc-openrisc-v2-1-8d7d7a2f3fec@weissschuh.net --- tools/include/nolibc/Makefile | 2 +- tools/include/nolibc/arch-openrisc.h | 160 ++++++++++++++++++ tools/include/nolibc/arch.h | 2 + .../testing/selftests/nolibc/Makefile.nolibc | 4 + tools/testing/selftests/nolibc/run-tests.sh | 4 +- 5 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 tools/include/nolibc/arch-openrisc.h diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile index 872c318f50d4..2cb4d610fe53 100644 --- a/tools/include/nolibc/Makefile +++ b/tools/include/nolibc/Makefile @@ -17,7 +17,7 @@ endif # it defaults to this nolibc directory. OUTPUT ?= $(CURDIR)/ -architectures := arm arm64 loongarch m68k mips powerpc riscv s390 sh sparc x86 +architectures := arm arm64 loongarch m68k mips openrisc powerpc riscv s390 sh sparc x86 arch_files := arch.h $(addsuffix .h, $(addprefix arch-, $(architectures))) all_files := \ alloca.h \ diff --git a/tools/include/nolibc/arch-openrisc.h b/tools/include/nolibc/arch-openrisc.h new file mode 100644 index 000000000000..5ef82fd9cc64 --- /dev/null +++ b/tools/include/nolibc/arch-openrisc.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ +/* + * OpenRISC specific definitions for NOLIBC + * Copyright (C) 2026 Thomas Weißschuh + */ + +#ifndef _NOLIBC_ARCH_OPENRISC_H +#define _NOLIBC_ARCH_OPENRISC_H + +#include "compiler.h" +#include "crt.h" + +/* + * Syscalls for OpenRISC: + * - syscall number is passed in r11 + * - arguments are in r3, r4, r5, r6, r7, r8 + * - the system call is performed by calling l.sys 1 + * - syscall return value is in r11 + */ + +#define _NOLIBC_SYSCALL_CLOBBERLIST \ + "r12", "r13", "r15", "r17", "r19", "r21", "r23", "r25", "r27", "r29", "r31", "memory" + +#define __nolibc_syscall0(num) \ +({ \ + register long _num __asm__ ("r11") = (num); \ + \ + __asm__ volatile ( \ + "l.sys 1\n" \ + : "+r"(_num) \ + : \ + : "r3", "r4", "r5", "r6", "r7", "r8", \ + _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _num; \ +}) + +#define __nolibc_syscall1(num, arg1) \ +({ \ + register long _num __asm__ ("r11") = (num); \ + register long _arg1 __asm__ ("r3") = (long)(arg1); \ + \ + __asm__ volatile ( \ + "l.sys 1\n" \ + : "+r"(_num) \ + : "r"(_arg1) \ + : "r4", "r5", "r6", "r7", "r8", _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _num; \ +}) + +#define __nolibc_syscall2(num, arg1, arg2) \ +({ \ + register long _num __asm__ ("r11") = (num); \ + register long _arg1 __asm__ ("r3") = (long)(arg1); \ + register long _arg2 __asm__ ("r4") = (long)(arg2); \ + \ + __asm__ volatile ( \ + "l.sys 1\n" \ + : "+r"(_num) \ + : "r"(_arg1), "r"(_arg2) \ + : "r5", "r6", "r7", "r8", _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _num; \ +}) + +#define __nolibc_syscall3(num, arg1, arg2, arg3) \ +({ \ + register long _num __asm__ ("r11") = (num); \ + register long _arg1 __asm__ ("r3") = (long)(arg1); \ + register long _arg2 __asm__ ("r4") = (long)(arg2); \ + register long _arg3 __asm__ ("r5") = (long)(arg3); \ + \ + __asm__ volatile ( \ + "l.sys 1\n" \ + : "+r"(_num) \ + : "r"(_arg1), "r"(_arg2), "r"(_arg3) \ + : "r6", "r7", "r8", _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _num; \ +}) + +#define __nolibc_syscall4(num, arg1, arg2, arg3, arg4) \ +({ \ + register long _num __asm__ ("r11") = (num); \ + register long _arg1 __asm__ ("r3") = (long)(arg1); \ + register long _arg2 __asm__ ("r4") = (long)(arg2); \ + register long _arg3 __asm__ ("r5") = (long)(arg3); \ + register long _arg4 __asm__ ("r6") = (long)(arg4); \ + \ + __asm__ volatile ( \ + "l.sys 1\n" \ + : "+r"(_num) \ + : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4) \ + : "r7", "r8", _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _num; \ +}) + +#define __nolibc_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ +({ \ + register long _num __asm__ ("r11") = (num); \ + register long _arg1 __asm__ ("r3") = (long)(arg1); \ + register long _arg2 __asm__ ("r4") = (long)(arg2); \ + register long _arg3 __asm__ ("r5") = (long)(arg3); \ + register long _arg4 __asm__ ("r6") = (long)(arg4); \ + register long _arg5 __asm__ ("r7") = (long)(arg5); \ + \ + __asm__ volatile ( \ + "l.sys 1\n" \ + : "+r"(_num) \ + : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5) \ + : "r8", _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _num; \ +}) + +#define __nolibc_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ +({ \ + register long _num __asm__ ("r11") = (num); \ + register long _arg1 __asm__ ("r3") = (long)(arg1); \ + register long _arg2 __asm__ ("r4") = (long)(arg2); \ + register long _arg3 __asm__ ("r5") = (long)(arg3); \ + register long _arg4 __asm__ ("r6") = (long)(arg4); \ + register long _arg5 __asm__ ("r7") = (long)(arg5); \ + register long _arg6 __asm__ ("r8") = (long)(arg6); \ + \ + __asm__ volatile ( \ + "l.sys 1\n" \ + : "+r"(_num) \ + : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \ + "r"(_arg6) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _num; \ +}) + +#ifndef NOLIBC_NO_RUNTIME +/* startup code */ +void _start_wrapper(void); +void __attribute__((weak,noreturn)) +__nolibc_entrypoint __nolibc_no_stack_protector +_start_wrapper(void) +{ + __asm__ volatile ( + ".global _start\n" /* The C function will have a prologue, */ + ".type _start, @function\n" /* corrupting "sp/r1" */ + ".weak _start\n" + "_start:\n" + + "l.jal _start_c\n" /* transfer to c runtime */ + "l.or r3,r1,r1\n" /* save stack pointer to r3, as arg1 of _start_c */ + + ".size _start, .-_start\n" + ); + __nolibc_entrypoint_epilogue(); +} +#endif /* NOLIBC_NO_RUNTIME */ + +#endif /* _NOLIBC_ARCH_OPENRISC_H */ diff --git a/tools/include/nolibc/arch.h b/tools/include/nolibc/arch.h index a3adaf433f2c..55009dcafe9e 100644 --- a/tools/include/nolibc/arch.h +++ b/tools/include/nolibc/arch.h @@ -28,6 +28,8 @@ #include "arch-m68k.h" #elif defined(__sh__) #include "arch-sh.h" +#elif defined(__or1k__) +#include "arch-openrisc.h" #else #error Unsupported Architecture #endif diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index 41d616a01db5..26cc97653625 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -102,6 +102,7 @@ DEFCONFIG_sparc32 = sparc32_defconfig DEFCONFIG_sparc64 = sparc64_defconfig DEFCONFIG_m68k = virt_defconfig DEFCONFIG_sh4 = rts7751r2dplus_defconfig +DEFCONFIG_openrisc = virt_defconfig DEFCONFIG = $(or $(DEFCONFIG_$(XARCH)),defconfig) EXTRACONFIG_x32 = -e CONFIG_X86_X32_ABI @@ -128,6 +129,7 @@ QEMU_ARCH_mips64be = mips64 QEMU_ARCH_ppc64le = ppc64 QEMU_ARCH_loongarch = loongarch64 QEMU_ARCH_sparc32 = sparc +QEMU_ARCH_openrisc = or1k QEMU_ARCH = $(or $(QEMU_ARCH_$(XARCH)),$(XARCH)) QEMU_ARCH_USER_ppc64le = ppc64le @@ -167,6 +169,7 @@ QEMU_ARGS_sparc32 = -M SS-5 -m 256M -append "console=ttyS0,115200 panic=-1 $( QEMU_ARGS_sparc64 = -M sun4u -append "console=ttyS0,115200 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_m68k = -M virt -append "console=ttyGF0,115200 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_sh4 = -M r2d -serial file:/dev/stdout -append "console=ttySC1,115200 panic=-1 $(TEST:%=NOLIBC_TEST=%)" +QEMU_ARGS_openrisc = -M virt -m 512M -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS = -m 1G $(QEMU_ARGS_$(XARCH)) $(QEMU_ARGS_BIOS) $(QEMU_ARGS_EXTRA) # OUTPUT is only set when run from the main makefile, otherwise @@ -201,6 +204,7 @@ CFLAGS_XARCH = $(CFLAGS_$(XARCH)) endif LDLIBS_ppc = $(if $(LLVM),,-lgcc) +LDLIBS_openrisc = $(if $(LLVM),,-lgcc) LDLIBS = $(LDLIBS_$(XARCH)) include Makefile.include diff --git a/tools/testing/selftests/nolibc/run-tests.sh b/tools/testing/selftests/nolibc/run-tests.sh index cd439096fdf3..c25ee4be2df4 100755 --- a/tools/testing/selftests/nolibc/run-tests.sh +++ b/tools/testing/selftests/nolibc/run-tests.sh @@ -21,6 +21,7 @@ all_archs=( i386 x86_64 x32 arm64 arm armthumb mips32le mips32be mipsn32le mipsn32be mips64le mips64be + openrisc ppc ppc64 ppc64le riscv32 riscv64 s390x @@ -107,6 +108,7 @@ crosstool_arch() { case "$1" in arm64) echo aarch64;; armthumb) echo arm;; + openrisc) echo or1k;; ppc) echo powerpc;; ppc64) echo powerpc64;; ppc64le) echo powerpc64;; @@ -185,7 +187,7 @@ test_arch() { exit 1 esac printf '%-15s' "$arch:" - if [ "$arch" = "m68k" -o "$arch" = "sh4" ] && [ "$llvm" = "1" ]; then + if [ "$arch" = "m68k" -o "$arch" = "sh4" -o "$arch" = "openrisc" ] && [ "$llvm" = "1" ]; then echo "Unsupported configuration" return fi From d4534e087311523bafd164eb5437cefa1d6097d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 7 Apr 2026 14:04:08 +0200 Subject: [PATCH 24/37] selftests/nolibc: avoid function pointer comparisons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upcoming parisc support would require libgcc to implement function pointer comparisons. As we try to avoid the libgcc dependency rework the logic to work without such comparisons. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260428-nolibc-hppa-v5-1-d843d573111a@weissschuh.net --- tools/testing/selftests/nolibc/nolibc-test.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index 08610cacf030..1db6e8d55c16 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -649,20 +649,25 @@ int expect_str_buf_eq(size_t expr, const char *buf, size_t val, int llen, const return 0; } +enum strtox_func { + strtox_func_strtol, + strtox_func_strtoul, +}; + #define EXPECT_STRTOX(cond, func, input, base, expected, chars, expected_errno) \ - do { if (!(cond)) result(llen, SKIPPED); else ret += expect_strtox(llen, func, input, base, expected, chars, expected_errno); } while (0) + do { if (!(cond)) result(llen, SKIPPED); else ret += expect_strtox(llen, strtox_func_ ## func, input, base, expected, chars, expected_errno); } while (0) static __attribute__((unused)) -int expect_strtox(int llen, void *func, const char *input, int base, intmax_t expected, int expected_chars, int expected_errno) +int expect_strtox(int llen, enum strtox_func func, const char *input, int base, intmax_t expected, int expected_chars, int expected_errno) { char *endptr; int actual_errno, actual_chars; intmax_t r; errno = 0; - if (func == strtol) { + if (func == strtox_func_strtol) { r = strtol(input, &endptr, base); - } else if (func == strtoul) { + } else if (func == strtox_func_strtoul) { r = strtoul(input, &endptr, base); } else { result(llen, FAIL); From d4fe68aea3d9fa66534dc2485b8ab998514ca0fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 6 Apr 2026 23:43:05 +0200 Subject: [PATCH 25/37] tools/nolibc: add support for 32-bit parisc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend nolibc to target the 32-bit parisc architecture. 64-bit is not yet supported. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch/msgid.link/20260428-nolibc-hppa-v5-2-d843d573111a@weissschuh.net --- tools/include/nolibc/Makefile | 2 +- tools/include/nolibc/arch-parisc.h | 185 ++++++++++++++++++ tools/include/nolibc/arch.h | 2 + .../testing/selftests/nolibc/Makefile.nolibc | 4 + tools/testing/selftests/nolibc/run-tests.sh | 8 +- 5 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 tools/include/nolibc/arch-parisc.h diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile index 2cb4d610fe53..00fd2e566d75 100644 --- a/tools/include/nolibc/Makefile +++ b/tools/include/nolibc/Makefile @@ -17,7 +17,7 @@ endif # it defaults to this nolibc directory. OUTPUT ?= $(CURDIR)/ -architectures := arm arm64 loongarch m68k mips openrisc powerpc riscv s390 sh sparc x86 +architectures := arm arm64 loongarch m68k mips openrisc parisc powerpc riscv s390 sh sparc x86 arch_files := arch.h $(addsuffix .h, $(addprefix arch-, $(architectures))) all_files := \ alloca.h \ diff --git a/tools/include/nolibc/arch-parisc.h b/tools/include/nolibc/arch-parisc.h new file mode 100644 index 000000000000..417043ecbc53 --- /dev/null +++ b/tools/include/nolibc/arch-parisc.h @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ +/* + * parisc/hppa (32-bit) specific definitions for NOLIBC + * Copyright (C) 2026 Thomas Weißschuh + */ + +#ifndef _NOLIBC_ARCH_PARISC_H +#define _NOLIBC_ARCH_PARISC_H + +#if defined(__LP64__) +#error 64-bit not supported +#endif + +#include "compiler.h" +#include "crt.h" + +/* Syscalls for parisc : + * - syscall number is passed in r20 + * - arguments are in r26 to r21 + * - the system call is performed by calling "ble 0x100(%sr2, %r0)", + * the instruction after that is in the delay slot and executed before + * the jump to 0x100 actually happens, use it to load the syscall number + * - syscall return comes in r28 + * - the arguments are cast to long and assigned into the target + * registers which are then simply passed as registers to the asm code, + * so that we don't have to experience issues with register constraints. + */ + +#define _NOLIBC_SYSCALL_CLOBBERLIST \ + "memory", "%r1", "%r2", "%r4", "%r20", "%r29", "%r31" + +#define __nolibc_syscall0(num) \ +({ \ + register long _ret __asm__ ("r28"); \ + \ + __asm__ volatile ( \ + "ble 0x100(%%sr2, %%r0)\n\t" \ + "copy %1, %%r20\n\t" \ + : "=r"(_ret) \ + : "r"(num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST, \ + "%r21", "%r22", "%r23", "%r24", "%r25", "%r26" \ + ); \ + _ret; \ +}) + +#define __nolibc_syscall1(num, arg1) \ +({ \ + register long _ret __asm__ ("r28"); \ + register long _arg1 __asm__ ("r26") = (long)(arg1); \ + \ + __asm__ volatile ( \ + "ble 0x100(%%sr2, %%r0)\n\t" \ + "copy %2, %%r20\n\t" \ + : "=r"(_ret), \ + "+r"(_arg1) \ + : "r"(num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST, \ + "%r21", "%r22", "%r23", "%r24", "%r25" \ + ); \ + _ret; \ +}) + +#define __nolibc_syscall2(num, arg1, arg2) \ +({ \ + register long _ret __asm__ ("r28"); \ + register long _arg1 __asm__ ("r26") = (long)(arg1); \ + register long _arg2 __asm__ ("r25") = (long)(arg2); \ + \ + __asm__ volatile ( \ + "ble 0x100(%%sr2, %%r0)\n\t" \ + "copy %3, %%r20\n\t" \ + : "=r"(_ret), \ + "+r"(_arg1), "+r"(_arg2) \ + : "r"(num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST, \ + "%r21", "%r22", "%r23", "%r24" \ + ); \ + _ret; \ +}) + +#define __nolibc_syscall3(num, arg1, arg2, arg3) \ +({ \ + register long _ret __asm__ ("r28"); \ + register long _arg1 __asm__ ("r26") = (long)(arg1); \ + register long _arg2 __asm__ ("r25") = (long)(arg2); \ + register long _arg3 __asm__ ("r24") = (long)(arg3); \ + \ + __asm__ volatile ( \ + "ble 0x100(%%sr2, %%r0)\n\t" \ + "copy %4, %%r20\n\t" \ + : "=r"(_ret), \ + "+r"(_arg1), "+r"(_arg2), "+r"(_arg3) \ + : "r"(num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST, \ + "%r21", "%r22", "%r23" \ + ); \ + _ret; \ +}) + +#define __nolibc_syscall4(num, arg1, arg2, arg3, arg4) \ +({ \ + register long _ret __asm__ ("r28"); \ + register long _arg1 __asm__ ("r26") = (long)(arg1); \ + register long _arg2 __asm__ ("r25") = (long)(arg2); \ + register long _arg3 __asm__ ("r24") = (long)(arg3); \ + register long _arg4 __asm__ ("r23") = (long)(arg4); \ + \ + __asm__ volatile ( \ + "ble 0x100(%%sr2, %%r0)\n\t" \ + "copy %5, %%r20\n\t" \ + : "=r"(_ret), \ + "+r"(_arg1), "+r"(_arg2), "+r"(_arg3), "+r"(_arg4) \ + : "r"(num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST, \ + "%r21", "%r22" \ + ); \ + _ret; \ +}) + +#define __nolibc_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ +({ \ + register long _ret __asm__ ("r28"); \ + register long _arg1 __asm__ ("r26") = (long)(arg1); \ + register long _arg2 __asm__ ("r25") = (long)(arg2); \ + register long _arg3 __asm__ ("r24") = (long)(arg3); \ + register long _arg4 __asm__ ("r23") = (long)(arg4); \ + register long _arg5 __asm__ ("r22") = (long)(arg5); \ + \ + __asm__ volatile ( \ + "ble 0x100(%%sr2, %%r0)\n\t" \ + "copy %6, %%r20\n\t" \ + : "=r"(_ret), \ + "+r"(_arg1), "+r"(_arg2), "+r"(_arg3), "+r"(_arg4), \ + "+r"(_arg5) \ + : "r"(num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST, \ + "%r21" \ + ); \ + _ret; \ +}) + +#define __nolibc_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ +({ \ + register long _ret __asm__ ("r28"); \ + register long _arg1 __asm__ ("r26") = (long)(arg1); \ + register long _arg2 __asm__ ("r25") = (long)(arg2); \ + register long _arg3 __asm__ ("r24") = (long)(arg3); \ + register long _arg4 __asm__ ("r23") = (long)(arg4); \ + register long _arg5 __asm__ ("r22") = (long)(arg5); \ + register long _arg6 __asm__ ("r21") = (long)(arg6); \ + \ + __asm__ volatile ( \ + "ble 0x100(%%sr2, %%r0)\n\t" \ + "copy %7, %%r20\n\t" \ + : "=r"(_ret), \ + "+r"(_arg1), "+r"(_arg2), "+r"(_arg3), "+r"(_arg4), \ + "+r"(_arg5), "+r"(_arg6) \ + : "r"(num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _ret; \ +}) + +#ifndef NOLIBC_NO_RUNTIME +/* startup code */ +void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector _start(void) +{ + __asm__ volatile ( + ".import $global$\n" /* Set up the dp register */ + "ldil L%$global$, %dp\n" + "ldo R%$global$(%r27), %dp\n" + + "b _start_c\n" /* Call _start_c, the load below is executed first */ + + "ldo -4(%r24), %r26\n" /* The sp register is special on parisc. + * r24 points to argv. Subtract 4 to get &argc. + * Pass that as first argument to _start_c. + */ + ); + __nolibc_entrypoint_epilogue(); +} +#endif /* NOLIBC_NO_RUNTIME */ + +#endif /* _NOLIBC_ARCH_PARISC_H */ diff --git a/tools/include/nolibc/arch.h b/tools/include/nolibc/arch.h index 55009dcafe9e..b69d9c5ec5c6 100644 --- a/tools/include/nolibc/arch.h +++ b/tools/include/nolibc/arch.h @@ -30,6 +30,8 @@ #include "arch-sh.h" #elif defined(__or1k__) #include "arch-openrisc.h" +#elif defined(__hppa__) +#include "arch-parisc.h" #else #error Unsupported Architecture #endif diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index 26cc97653625..c6dcd54078f2 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -64,6 +64,7 @@ ARCH_s390x = s390 ARCH_sparc32 = sparc ARCH_sparc64 = sparc ARCH_sh4 = sh +ARCH_parisc32 = parisc ARCH := $(or $(ARCH_$(XARCH)),$(XARCH)) # kernel image names by architecture @@ -130,6 +131,7 @@ QEMU_ARCH_ppc64le = ppc64 QEMU_ARCH_loongarch = loongarch64 QEMU_ARCH_sparc32 = sparc QEMU_ARCH_openrisc = or1k +QEMU_ARCH_parisc32 = hppa QEMU_ARCH = $(or $(QEMU_ARCH_$(XARCH)),$(XARCH)) QEMU_ARCH_USER_ppc64le = ppc64le @@ -170,6 +172,7 @@ QEMU_ARGS_sparc64 = -M sun4u -append "console=ttyS0,115200 panic=-1 $(TEST:%= QEMU_ARGS_m68k = -M virt -append "console=ttyGF0,115200 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_sh4 = -M r2d -serial file:/dev/stdout -append "console=ttySC1,115200 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_openrisc = -M virt -m 512M -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" +QEMU_ARGS_parisc32 = -M B160L -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS = -m 1G $(QEMU_ARGS_$(XARCH)) $(QEMU_ARGS_BIOS) $(QEMU_ARGS_EXTRA) # OUTPUT is only set when run from the main makefile, otherwise @@ -186,6 +189,7 @@ CFLAGS_i386 = $(call cc-option,-m32) CFLAGS_x32 = -mx32 CFLAGS_arm = -marm CFLAGS_armthumb = -mthumb -march=armv6t2 +CFLAGS_parisc32 = -mfast-indirect-calls CFLAGS_ppc = -m32 -mbig-endian -mno-vsx $(call cc-option,-mmultiple) CFLAGS_ppc64 = -m64 -mbig-endian -mno-vsx $(call cc-option,-mmultiple) CFLAGS_ppc64le = -m64 -mlittle-endian -mno-vsx $(call cc-option,-mabi=elfv2) diff --git a/tools/testing/selftests/nolibc/run-tests.sh b/tools/testing/selftests/nolibc/run-tests.sh index c25ee4be2df4..6460e25001de 100755 --- a/tools/testing/selftests/nolibc/run-tests.sh +++ b/tools/testing/selftests/nolibc/run-tests.sh @@ -29,6 +29,7 @@ all_archs=( sparc32 sparc64 m68k sh4 + parisc32 ) archs="${all_archs[@]}" @@ -118,6 +119,7 @@ crosstool_arch() { s390*) echo s390;; sparc*) echo sparc64;; x32*) echo x86_64;; + parisc32) echo hppa;; *) echo "$1";; esac } @@ -175,6 +177,10 @@ test_arch() { fi MAKE=(make -f Makefile.nolibc -j"${nproc}" XARCH="${arch}" CROSS_COMPILE="${cross_compile}" LLVM="${llvm}" O="${build_dir}") + if [ "$arch" = "parisc32" ]; then + MAKE+=("CROSS32CC=${cross_compile}gcc") + fi + case "$test_mode" in 'system') test_target=run @@ -187,7 +193,7 @@ test_arch() { exit 1 esac printf '%-15s' "$arch:" - if [ "$arch" = "m68k" -o "$arch" = "sh4" -o "$arch" = "openrisc" ] && [ "$llvm" = "1" ]; then + if [ "$arch" = "m68k" -o "$arch" = "sh4" -o "$arch" = "openrisc" -o "$arch" = "parisc32" ] && [ "$llvm" = "1" ]; then echo "Unsupported configuration" return fi From ced7994fc87d2d1c162d41ff32be876b09c8ad2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 14 May 2026 14:05:11 +0200 Subject: [PATCH 26/37] tools/nolibc: split implicit open flags into a macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This logic is duplicated and its current form will be in the way of some upcoming simplificiations. Move it into a macro to avoid the duplication and enable some cleanups. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260514-nolibc-open-tmpfile-v2-1-b4c6c5efa266@weissschuh.net --- tools/include/nolibc/fcntl.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tools/include/nolibc/fcntl.h b/tools/include/nolibc/fcntl.h index 014910a8e928..46f591cf82fd 100644 --- a/tools/include/nolibc/fcntl.h +++ b/tools/include/nolibc/fcntl.h @@ -14,6 +14,8 @@ #include "types.h" #include "sys.h" +#define __nolibc_open_flags(_flags) ((_flags) | O_LARGEFILE) + /* * int openat(int dirfd, const char *path, int flags[, mode_t mode]); */ @@ -29,8 +31,6 @@ int openat(int dirfd, const char *path, int flags, ...) { mode_t mode = 0; - flags |= O_LARGEFILE; - if (flags & O_CREAT) { va_list args; @@ -39,7 +39,7 @@ int openat(int dirfd, const char *path, int flags, ...) va_end(args); } - return __sysret(_sys_openat(dirfd, path, flags, mode)); + return __sysret(_sys_openat(dirfd, path, __nolibc_open_flags(flags), mode)); } /* @@ -57,8 +57,6 @@ int open(const char *path, int flags, ...) { mode_t mode = 0; - flags |= O_LARGEFILE; - if (flags & O_CREAT) { va_list args; @@ -67,7 +65,7 @@ int open(const char *path, int flags, ...) va_end(args); } - return __sysret(_sys_open(path, flags, mode)); + return __sysret(_sys_open(path, __nolibc_open_flags(flags), mode)); } /* From a8856027e29c432b2889aad45580d430ca002c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 14 May 2026 14:05:12 +0200 Subject: [PATCH 27/37] tools/nolibc: split open mode handling into a macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This logic is duplicated and some upcoming extensions would require even more duplicated logic. Move it into a macro to avoid the duplication and allow cleaner changes. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260514-nolibc-open-tmpfile-v2-2-b4c6c5efa266@weissschuh.net --- tools/include/nolibc/fcntl.h | 40 ++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/tools/include/nolibc/fcntl.h b/tools/include/nolibc/fcntl.h index 46f591cf82fd..d7ea02e1332d 100644 --- a/tools/include/nolibc/fcntl.h +++ b/tools/include/nolibc/fcntl.h @@ -16,6 +16,21 @@ #define __nolibc_open_flags(_flags) ((_flags) | O_LARGEFILE) +#define __nolibc_open_mode(_flags) \ +({ \ + mode_t _mode = 0; \ + \ + if ((_flags) & O_CREAT) { \ + va_list args; \ + \ + va_start(args, (_flags)); \ + _mode = va_arg(args, mode_t); \ + va_end(args); \ + } \ + \ + _mode; \ +}) + /* * int openat(int dirfd, const char *path, int flags[, mode_t mode]); */ @@ -29,17 +44,8 @@ int _sys_openat(int dirfd, const char *path, int flags, mode_t mode) static __attribute__((unused)) int openat(int dirfd, const char *path, int flags, ...) { - mode_t mode = 0; - - if (flags & O_CREAT) { - va_list args; - - va_start(args, flags); - mode = va_arg(args, mode_t); - va_end(args); - } - - return __sysret(_sys_openat(dirfd, path, __nolibc_open_flags(flags), mode)); + return __sysret(_sys_openat(dirfd, path, __nolibc_open_flags(flags), + __nolibc_open_mode(flags))); } /* @@ -55,17 +61,7 @@ int _sys_open(const char *path, int flags, mode_t mode) static __attribute__((unused)) int open(const char *path, int flags, ...) { - mode_t mode = 0; - - if (flags & O_CREAT) { - va_list args; - - va_start(args, flags); - mode = va_arg(args, mode_t); - va_end(args); - } - - return __sysret(_sys_open(path, __nolibc_open_flags(flags), mode)); + return __sysret(_sys_open(path, __nolibc_open_flags(flags), __nolibc_open_mode(flags))); } /* From 219d120c195e53dd33076be05641fda0e36bff37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 14 May 2026 14:05:13 +0200 Subject: [PATCH 28/37] tools/nolibc: always pass mode to open syscall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When O_TMPFILE is set, the open mode needs to be passed to the kernel as per the documentation. Currently this is not done. Instead of checking for O_TMPFILE explicitly and making the conditionals more complex, just always pass the mode to the kernel. If no value was passed the mode will be garbage, but the kernel will ignore it anyways. Fixes: a7604ba149e7 ("tools/nolibc/sys: make open() take a vararg on the 3rd argument") Suggested-by: Willy Tarreau Link: https://lore.kernel.org/lkml/afRfjdovT6pNtwtP@1wt.eu/ Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260514-nolibc-open-tmpfile-v2-3-b4c6c5efa266@weissschuh.net --- tools/include/nolibc/fcntl.h | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tools/include/nolibc/fcntl.h b/tools/include/nolibc/fcntl.h index d7ea02e1332d..d4b6af60d4cc 100644 --- a/tools/include/nolibc/fcntl.h +++ b/tools/include/nolibc/fcntl.h @@ -18,15 +18,12 @@ #define __nolibc_open_mode(_flags) \ ({ \ - mode_t _mode = 0; \ + mode_t _mode; \ + va_list args; \ \ - if ((_flags) & O_CREAT) { \ - va_list args; \ - \ - va_start(args, (_flags)); \ - _mode = va_arg(args, mode_t); \ - va_end(args); \ - } \ + va_start(args, (_flags)); \ + _mode = va_arg(args, mode_t); \ + va_end(args); \ \ _mode; \ }) From 2ee39c5fb3a0cdc5da5026e78718ac7d92ea48c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 14 May 2026 14:05:14 +0200 Subject: [PATCH 29/37] selftests/nolibc: test open mode handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a selftest for the new O_TMPFILE open mode handling. While O_CREAT or openat() are not tested, the code is the same, so assume these also work. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260514-nolibc-open-tmpfile-v2-4-b4c6c5efa266@weissschuh.net --- tools/testing/selftests/nolibc/nolibc-test.c | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index 1db6e8d55c16..c3867cc570c6 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1305,6 +1305,28 @@ int test_openat(void) return 0; } +int test_open_mode(void) +{ + const mode_t mode = 0444; + struct stat stat_buf; + int fd, ret; + + fd = open("/tmp", O_TMPFILE | O_RDWR, mode); + if (fd == -1) + return -1; + + ret = fstat(fd, &stat_buf); + close(fd); + + if (ret == -1) + return -1; + + if ((stat_buf.st_mode & 0777) != mode) + return -1; + + return 0; +} + int test_nolibc_enosys(void) { if (true) @@ -1540,6 +1562,7 @@ int run_syscall(int min, int max) CASE_TEST(open_tty); EXPECT_SYSNE(1, tmp = open("/dev/null", O_RDONLY), -1); if (tmp != -1) close(tmp); break; CASE_TEST(open_blah); EXPECT_SYSER(1, tmp = open("/proc/self/blah", O_RDONLY), -1, ENOENT); if (tmp != -1) close(tmp); break; CASE_TEST(openat_dir); EXPECT_SYSZR(1, test_openat()); break; + CASE_TEST(open_mode); EXPECT_SYSZR(1, test_open_mode()); break; CASE_TEST(pipe); EXPECT_SYSZR(1, test_pipe()); break; CASE_TEST(poll_null); EXPECT_SYSZR(1, poll(NULL, 0, 0)); break; CASE_TEST(poll_stdout); EXPECT_SYSNE(1, ({ struct pollfd fds = { 1, POLLOUT, 0}; poll(&fds, 1, 0); }), -1); break; From 136ca91411b0b637e862eb7b1cce2a56853edd17 Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Wed, 20 May 2026 20:19:31 +0900 Subject: [PATCH 30/37] tools/nolibc: getopt: Fix potential out of bounds access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Running clang-tidy on a program that uses getopt() from nolibc this warning appears: getopt.h:80:6: warning: Out of bound access to memory after the end of the string literal [clang-analyzer-security.ArrayBound] 80 | if (optstring[i] == ':') { This looks like a very unlikely case that an argument inside of argv is being changed between getopt() calls. Adding a check for d becoming 0 in the guard after the loop stops getopt() getting far enough to access beyond the end of the array and seems to correct the issue. Fixes: bae3cd708e8a ("tools/nolibc: add getopt()") Assisted-by: Claude:claude-4.6-sonnet # reproducer Signed-off-by: Daniel Palmer Link: https://patch.msgid.link/20260520111931.1027758-1-daniel@thingy.jp [Thomas: clean up commit message a bit] Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/getopt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/nolibc/getopt.h b/tools/include/nolibc/getopt.h index 87565e3b6a33..3ad140f692df 100644 --- a/tools/include/nolibc/getopt.h +++ b/tools/include/nolibc/getopt.h @@ -71,7 +71,7 @@ int getopt(int argc, char * const argv[], const char *optstring) d = optstring[i++]; } while (d && d != c); - if (d != c || c == ':') { + if (!d || d != c || c == ':') { optopt = c; if (optstring[0] != ':' && opterr) fprintf(stderr, "%s: unrecognized option: %c\n", argv[0], *optchar); From b882d807fa443b529ae8bf917d7b640a8d555437 Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Fri, 22 May 2026 18:07:26 +0900 Subject: [PATCH 31/37] tools/nolibc: stackprotector: Avoid stalling program startup if crng is not init yet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We are using the getrandom syscall to get a random seed for the stack protector canary but we are calling it with no flags which means it'll block until there is some real randomness to return. This means that if the crng is not ready yet program startup will block and if you are unlucky that could be for a long time and look like the program has crashed. Even if the call to getrandom does not yield any random data, we will still initialize the canary. Fixes: 7188d4637e95 ("tools/nolibc: add support for stack protector") Signed-off-by: Daniel Palmer Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260522090726.726985-1-daniel@thingy.jp Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/stackprotector.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/include/nolibc/stackprotector.h b/tools/include/nolibc/stackprotector.h index e11c20c75465..916a92062ba0 100644 --- a/tools/include/nolibc/stackprotector.h +++ b/tools/include/nolibc/stackprotector.h @@ -42,7 +42,8 @@ uintptr_t __stack_chk_guard; static __nolibc_no_stack_protector void __stack_chk_init(void) { - __nolibc_syscall3(__NR_getrandom, &__stack_chk_guard, sizeof(__stack_chk_guard), 0); + __nolibc_syscall3(__NR_getrandom, &__stack_chk_guard, sizeof(__stack_chk_guard), + GRND_INSECURE | GRND_NONBLOCK); /* a bit more randomness in case getrandom() fails, ensure the guard is never 0 */ if (__stack_chk_guard != (uintptr_t) &__stack_chk_guard) __stack_chk_guard ^= (uintptr_t) &__stack_chk_guard; From f66d6bc35e45dfd71c95f3f4e0407081db2c0c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 21 May 2026 19:31:02 +0200 Subject: [PATCH 32/37] selftests/nolibc: enable CONFIG_TMPFS for sparc32 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An upcoming selftest will use memfd_create() which require tmpfs. Enable that. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Reviewed-by: Daniel Palmer Link: https://patch.msgid.link/20260521-nolibc-ftruncate-v1-1-5384a83b2402@weissschuh.net --- tools/testing/selftests/nolibc/Makefile.nolibc | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index c6dcd54078f2..06f881e2e90c 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -109,6 +109,7 @@ DEFCONFIG = $(or $(DEFCONFIG_$(XARCH)),defconfig) EXTRACONFIG_x32 = -e CONFIG_X86_X32_ABI EXTRACONFIG_arm = -e CONFIG_NAMESPACES EXTRACONFIG_armthumb = -e CONFIG_NAMESPACES +EXTRACONFIG_sparc32 = -e CONFIG_TMPFS EXTRACONFIG_m68k = -e CONFIG_BLK_DEV_INITRD EXTRACONFIG_sh4 = -e CONFIG_BLK_DEV_INITRD -e CONFIG_CMDLINE_FROM_BOOTLOADER EXTRACONFIG = $(EXTRACONFIG_$(XARCH)) From acbbec15195b40adefd24993661c93022f6de2b7 Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Thu, 21 May 2026 19:31:03 +0200 Subject: [PATCH 33/37] tools/nolibc: add a helper to split a 64-bit argument into 32-bit halves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On 32-bit architectures some system calls require a single 64-bit argument to be passed as two 32-bit halves. Add a helper to easily split such arguments. This works on little and bit endian. Signed-off-by: Daniel Palmer Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260521-nolibc-ftruncate-v1-2-5384a83b2402@weissschuh.net Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/sys.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index 33f9c970ae57..548f94d96ed2 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -70,6 +70,17 @@ static __inline__ int __nolibc_enosys(const char *syscall, ...) } #endif + +/* + * Helper for 32-bit machines where a 64-bit syscall arg needs to be split into + * two 32-bit parts while making sure the order of the low/high parts are correct + * for the endianness: + * __NOLIBC_LLARGPART(x, 0), __NOLIBC_LLARGPART(x, 1) + */ +#define __NOLIBC_LLARGPART(_arg, _part) \ + (((union { long long ll; long l[2]; }) { .ll = _arg }).l[_part]) + + /* Functions in this file only describe syscalls. They're declared static so * that the compiler usually decides to inline them while still being allowed * to pass a pointer to one of their instances. Each syscall exists in two From 835fa43a4d36bd66ad0dd052f9fa15f7bd365fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 21 May 2026 19:31:04 +0200 Subject: [PATCH 34/37] tools/nolibc: add ftruncate() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On architectures with 32-bit longs, call the compat syscall __NR_ftruncate64. As off_t is 64-bit it must be split into 2 registers. Unlike llseek() which passes the high and low parts in explicitly named arguments, the order here is endian independent. Some architectures (arm, mips, ppc) require this pair of registers to be aligned to an even register, so add custom _sys_ftruncate64() wrappers for those. A test case for ftruncate is added which validates negative length or invalid fd return the appropriate error, and checks the length is correct on success. Co-developed-by: Jordan Richards Signed-off-by: Jordan Richards Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Reviewed-by: Daniel Palmer Link: https://patch.msgid.link/20260521-nolibc-ftruncate-v1-3-5384a83b2402@weissschuh.net --- tools/include/nolibc/arch-arm.h | 10 ++++ tools/include/nolibc/arch-mips.h | 12 +++++ tools/include/nolibc/arch-powerpc.h | 12 +++++ tools/include/nolibc/unistd.h | 24 +++++++++ tools/testing/selftests/nolibc/nolibc-test.c | 52 ++++++++++++++++++++ 5 files changed, 110 insertions(+) diff --git a/tools/include/nolibc/arch-arm.h b/tools/include/nolibc/arch-arm.h index 72a2b28170e2..8681922e05ca 100644 --- a/tools/include/nolibc/arch-arm.h +++ b/tools/include/nolibc/arch-arm.h @@ -7,8 +7,11 @@ #ifndef _NOLIBC_ARCH_ARM_H #define _NOLIBC_ARCH_ARM_H +#include + #include "compiler.h" #include "crt.h" +#include "std.h" /* Syscalls for ARM in ARM or Thumb modes : * - registers are 32-bit @@ -196,4 +199,11 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote } #endif /* NOLIBC_NO_RUNTIME */ +static __attribute__((unused)) +int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) +{ + return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); +} +#define _sys_ftruncate64 _sys_ftruncate64 + #endif /* _NOLIBC_ARCH_ARM_H */ diff --git a/tools/include/nolibc/arch-mips.h b/tools/include/nolibc/arch-mips.h index 1400653c76c1..26ad413cec62 100644 --- a/tools/include/nolibc/arch-mips.h +++ b/tools/include/nolibc/arch-mips.h @@ -7,8 +7,11 @@ #ifndef _NOLIBC_ARCH_MIPS_H #define _NOLIBC_ARCH_MIPS_H +#include + #include "compiler.h" #include "crt.h" +#include "std.h" #if !defined(_ABIO32) && !defined(_ABIN32) && !defined(_ABI64) #error Unsupported MIPS ABI @@ -282,4 +285,13 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote } #endif /* NOLIBC_NO_RUNTIME */ +#if defined(_ABIO32) +static __attribute__((unused)) +int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) +{ + return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); +} +#define _sys_ftruncate64 _sys_ftruncate64 +#endif + #endif /* _NOLIBC_ARCH_MIPS_H */ diff --git a/tools/include/nolibc/arch-powerpc.h b/tools/include/nolibc/arch-powerpc.h index 111cda70f2cc..a1ab91d55384 100644 --- a/tools/include/nolibc/arch-powerpc.h +++ b/tools/include/nolibc/arch-powerpc.h @@ -7,8 +7,11 @@ #ifndef _NOLIBC_ARCH_POWERPC_H #define _NOLIBC_ARCH_POWERPC_H +#include + #include "compiler.h" #include "crt.h" +#include "std.h" /* Syscalls for PowerPC : * - stack is 16-byte aligned @@ -218,4 +221,13 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote } #endif /* NOLIBC_NO_RUNTIME */ +#if !defined(__powerpc64__) +static __attribute__((unused)) +int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) +{ + return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); +} +#define _sys_ftruncate64 _sys_ftruncate64 +#endif + #endif /* _NOLIBC_ARCH_POWERPC_H */ diff --git a/tools/include/nolibc/unistd.h b/tools/include/nolibc/unistd.h index 5882a6862066..79599ceef45d 100644 --- a/tools/include/nolibc/unistd.h +++ b/tools/include/nolibc/unistd.h @@ -48,6 +48,30 @@ int access(const char *path, int amode) return faccessat(AT_FDCWD, path, amode, 0); } +#if !defined(_sys_ftruncate64) && defined(__NR_ftruncate64) +static __attribute__((unused)) +int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) +{ + return __nolibc_syscall3(__NR_ftruncate64, fd, length0, length1); +} +#define _sys_ftruncate64 _sys_ftruncate64 +#endif + +static __attribute__((unused)) +int _sys_ftruncate(int fd, off_t length) +{ +#if defined(_sys_ftruncate64) + return _sys_ftruncate64(fd, __NOLIBC_LLARGPART(length, 0), __NOLIBC_LLARGPART(length, 1)); +#else + return __nolibc_syscall2(__NR_ftruncate, fd, length); +#endif +} + +static __attribute__((unused)) +int ftruncate(int fd, off_t length) +{ + return __sysret(_sys_ftruncate(fd, length)); +} static __attribute__((unused)) int msleep(unsigned int msecs) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index c3867cc570c6..7c3b711c125c 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1017,6 +1017,57 @@ int test_fork(enum fork_type type) } } +int test_ftruncate(void) +{ + struct stat stat_buf; + int ret, fd; + + ret = ftruncate(-1, 0); + if (ret != -1 || errno != EBADF) { + errno = EINVAL; + return __LINE__; + } + + fd = memfd_create(__func__, 0); + if (fd == -1) + return __LINE__; + + /* + * This also tests that the high 32-bit half is passed through correctly. + * If it gets lost, the kernel will see a positive number and not fail. + */ + ret = ftruncate(fd, -1); + if (!(ret == -1 && errno == EINVAL)) { + if (ret == 0) + errno = EINVAL; + ret = __LINE__; + goto end; + } + + ret = ftruncate(fd, 42); + if (ret != 0) { + ret = __LINE__; + goto end; + } + + ret = fstat(fd, &stat_buf); + if (ret != 0) { + ret = __LINE__; + goto end; + } + + if (stat_buf.st_size != 42) { + errno = EINVAL; + ret = __LINE__; + goto end; + } + +end: + close(fd); + + return ret; +} + int test_stat_timestamps(void) { struct stat st; @@ -1539,6 +1590,7 @@ int run_syscall(int min, int max) CASE_TEST(file_stream); EXPECT_SYSZR(1, test_file_stream()); break; CASE_TEST(file_stream_wsr); EXPECT_SYSZR(1, test_file_stream_wsr()); break; CASE_TEST(fork); EXPECT_SYSZR(1, test_fork(FORK_STANDARD)); break; + CASE_TEST(ftruncate); EXPECT_SYSZR(1, test_ftruncate()); break; CASE_TEST(getdents64_root); EXPECT_SYSNE(1, test_getdents64("/"), -1); break; CASE_TEST(getdents64_null); EXPECT_SYSER(1, test_getdents64("/dev/null"), -1, ENOTDIR); break; CASE_TEST(directories); EXPECT_SYSZR(is_nolibc && proc, test_dirent()); break; From 638b4816135c3b3426fed2e1b8834acec3fc831c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 25 May 2026 10:27:15 +0200 Subject: [PATCH 35/37] tools/nolibc: cast default values of program_invocation_name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With -Wwrite-strings the plain assignment triggers a warning as a 'const char *' is assigned to a 'char *', removing the const qualifier. Casting the const away is fine, as there is no valid modification that can be done to an empty string anyways. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260525-nolibc-write-strings-v2-1-ab5cc16c7b23@weissschuh.net --- tools/include/nolibc/errno.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/include/nolibc/errno.h b/tools/include/nolibc/errno.h index bab83692ea1c..a2325596d550 100644 --- a/tools/include/nolibc/errno.h +++ b/tools/include/nolibc/errno.h @@ -15,8 +15,8 @@ #ifndef NOLIBC_IGNORE_ERRNO #define SET_ERRNO(v) do { errno = (v); } while (0) int errno __attribute__((weak)); -char *program_invocation_name __attribute__((weak)) = ""; -char *program_invocation_short_name __attribute__((weak)) = ""; +char *program_invocation_name __attribute__((weak)) = (char *)""; +char *program_invocation_short_name __attribute__((weak)) = (char *)""; #else #define SET_ERRNO(v) do { } while (0) #define program_invocation_name "" From bfa093f12cce76141ea83283a9c8cec3874d208e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 25 May 2026 10:27:16 +0200 Subject: [PATCH 36/37] selftests/nolibc: use mutable buffer for execve() argv string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing code would trigger a warning under -Wwrite-strings which is about to be enabled. Use a mutable buffer instead. While in this specific case, casting away the 'const' would be fine, let's avoid casts which are not really necessary. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260525-nolibc-write-strings-v2-2-ab5cc16c7b23@weissschuh.net --- tools/testing/selftests/nolibc/nolibc-test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index 7c3b711c125c..c1c1ce43a047 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1584,7 +1584,7 @@ int run_syscall(int min, int max) CASE_TEST(dup2_m1); tmp = dup2(-1, 100); EXPECT_SYSER(1, tmp, -1, EBADF); if (tmp != -1) close(tmp); break; CASE_TEST(dup3_0); tmp = dup3(0, 100, 0); EXPECT_SYSNE(1, tmp, -1); close(tmp); break; CASE_TEST(dup3_m1); tmp = dup3(-1, 100, 0); EXPECT_SYSER(1, tmp, -1, EBADF); if (tmp != -1) close(tmp); break; - CASE_TEST(execve_root); EXPECT_SYSER(1, execve("/", (char*[]){ [0] = "/", [1] = NULL }, NULL), -1, EACCES); break; + CASE_TEST(execve_root); EXPECT_SYSER(1, execve("/", (char*[]){ [0] = (char []){"/"}, [1] = NULL }, NULL), -1, EACCES); break; CASE_TEST(fchdir_stdin); EXPECT_SYSER(1, fchdir(STDIN_FILENO), -1, ENOTDIR); break; CASE_TEST(fchdir_badfd); EXPECT_SYSER(1, fchdir(-1), -1, EBADF); break; CASE_TEST(file_stream); EXPECT_SYSZR(1, test_file_stream()); break; From 3850c2920a10d5f50f5c2f8acccfb3925002f30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 25 May 2026 10:27:17 +0200 Subject: [PATCH 37/37] selftests/nolibc: test against -Wwrite-strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Users may use this warning when building their own applications. Make sure that nolibc does not trigger any such warnings. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260525-nolibc-write-strings-v2-3-ab5cc16c7b23@weissschuh.net --- tools/testing/selftests/nolibc/Makefile.include | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/nolibc/Makefile.include b/tools/testing/selftests/nolibc/Makefile.include index 96fe2bc2191e..c30ca3a9ef14 100644 --- a/tools/testing/selftests/nolibc/Makefile.include +++ b/tools/testing/selftests/nolibc/Makefile.include @@ -6,7 +6,7 @@ _CFLAGS_STACKPROTECTOR ?= $(call try-run, \ $(__CFLAGS_STACKPROTECTOR)) _CFLAGS_SANITIZER ?= $(call cc-option,-fsanitize=undefined -fsanitize-trap=all) CFLAGS_NOLIBC_TEST ?= -Os -fno-ident -fno-asynchronous-unwind-tables -std=c89 \ - -W -Wall -Wextra -Wundef \ + -W -Wall -Wextra -Wundef -Wwrite-strings \ $(call cc-option,-fno-stack-protector) $(call cc-option,-Wmissing-prototypes) \ $(_CFLAGS_STACKPROTECTOR) $(_CFLAGS_SANITIZER)