diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile
index ce21ace8210e..e23c816598dc 100644
--- a/tools/include/nolibc/Makefile
+++ b/tools/include/nolibc/Makefile
@@ -24,9 +24,13 @@ Q=@
 endif
 
 nolibc_arch := $(patsubst arm64,aarch64,$(ARCH))
-arch_file := $(nolibc_arch)/arch.h
+arch_files := \
+		$(nolibc_arch)/crt.h \
+		$(nolibc_arch)/sys.h
+
 all_files := \
 		compiler.h \
+		crt.h \
 		ctype.h \
 		errno.h \
 		nolibc.h \
@@ -68,19 +72,23 @@ headers:
 	$(Q)mkdir -p $(OUTPUT)sysroot
 	$(Q)mkdir -p $(OUTPUT)sysroot/include
 	$(Q)cp $(all_files) $(OUTPUT)sysroot/include/
-	$(Q)if [ "$(ARCH)" = "x86" ]; then      \
-		sed -e                          \
-		  's,^#ifndef _NOLIBC_X86_64_ARCH_H,#if !defined(_NOLIBC_X86_64_ARCH_H) \&\& defined(__x86_64__),' \
-		  x86_64/arch.h;                \
-		sed -e                          \
-		  's,^#ifndef _NOLIBC_I386_ARCH_H,#if !defined(_NOLIBC_I386_ARCH_H) \&\& !defined(__x86_64__),' \
-		  i386/arch.h;                  \
-	elif [ -e "$(arch_file)" ]; then        \
-		cat $(arch_file);               \
-	else                                    \
-		echo "Fatal: architecture $(ARCH) not yet supported by nolibc." >&2; \
-		exit 1;                         \
-	fi > $(OUTPUT)sysroot/include/arch.h
+	$(Q)for arch_file in $(arch_files); do                                 \
+		base_file=$$(basename $$arch_file);                            \
+		arch_header=$$(echo $$base_file | sed -e 's/\.h$$/_arch.h/g'); \
+		if [ "$(ARCH)" = "x86" ]; then                                 \
+			sed -e                                                 \
+			  's,^#ifndef _NOLIBC_X86_64_\([^_]*\)_H,#if !defined(_NOLIBC_X86_64_\1_H) \&\& defined(__x86_64__),' \
+			  x86_64/$$base_file;                                  \
+			sed -e                                                 \
+			  's,^#ifndef _NOLIBC_I386_\([^_]*\)_H,#if !defined(_NOLIBC_I386_\1_H) \&\& !defined(__x86_64__),' \
+			  i386/$$base_file;                                    \
+		elif [ -e "$$arch_file" ]; then                                \
+			cat $$arch_file;                                       \
+		else                                                           \
+			echo "Fatal: architecture $(ARCH) not yet supported by nolibc." >&2; \
+			exit 1;                                                \
+		fi > $(OUTPUT)sysroot/include/$$arch_header;                   \
+	done
 
 headers_standalone: headers
 	$(Q)$(MAKE) -C $(srctree) headers
diff --git a/tools/include/nolibc/aarch64/crt.h b/tools/include/nolibc/aarch64/crt.h
new file mode 100644
index 000000000000..6c81d81cf31f
--- /dev/null
+++ b/tools/include/nolibc/aarch64/crt.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
+/*
+ * AARCH64 specific startup code for NOLIBC
+ * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
+ */
+
+#ifndef _NOLIBC_AARCH64_CRT_H
+#define _NOLIBC_AARCH64_CRT_H
+
+/* startup code */
+void __attribute__((weak, noreturn, optimize("omit-frame-pointer"))) __no_stack_protector _start(void)
+{
+	__asm__ volatile (
+#ifdef _NOLIBC_STACKPROTECTOR
+		"bl __stack_chk_init\n"   /* initialize stack protector                     */
+#endif
+		"ldr x0, [sp]\n"     /* argc (x0) was in the stack                          */
+		"add x1, sp, 8\n"    /* argv (x1) = sp                                      */
+		"lsl x2, x0, 3\n"    /* envp (x2) = 8*argc ...                              */
+		"add x2, x2, 8\n"    /*           + 8 (skip null)                           */
+		"add x2, x2, x1\n"   /*           + argv                                    */
+		"adrp x3, environ\n"          /* x3 = &environ (high bits)                  */
+		"str x2, [x3, #:lo12:environ]\n" /* store envp into environ                 */
+		"mov x4, x2\n"       /* search for auxv (follows NULL after last env)       */
+		"0:\n"
+		"ldr x5, [x4], 8\n"  /* x5 = *x4; x4 += 8                                   */
+		"cbnz x5, 0b\n"      /* and stop at NULL after last env                     */
+		"adrp x3, _auxv\n"   /* x3 = &_auxv (high bits)                             */
+		"str x4, [x3, #:lo12:_auxv]\n" /* store x4 into _auxv                       */
+		"and sp, x1, -16\n"  /* sp must be 16-byte aligned in the callee            */
+		"bl main\n"          /* main() returns the status code, we'll exit with it. */
+		"mov x8, 93\n"       /* NR_exit == 93                                       */
+		"svc #0\n"
+	);
+	__builtin_unreachable();
+}
+#endif /* _NOLIBC_AARCH64_CRT_H */
diff --git a/tools/include/nolibc/aarch64/arch.h b/tools/include/nolibc/aarch64/sys.h
similarity index 82%
rename from tools/include/nolibc/aarch64/arch.h
rename to tools/include/nolibc/aarch64/sys.h
index 7d38da13c72b..b79d6a35ec37 100644
--- a/tools/include/nolibc/aarch64/arch.h
+++ b/tools/include/nolibc/aarch64/sys.h
@@ -1,13 +1,11 @@
 /* SPDX-License-Identifier: LGPL-2.1 OR MIT */
 /*
- * AARCH64 specific definitions for NOLIBC
+ * AARCH64 specific syscall declarations for NOLIBC
  * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
  */
 
-#ifndef _NOLIBC_AARCH64_ARCH_H
-#define _NOLIBC_AARCH64_ARCH_H
-
-#include "compiler.h"
+#ifndef _NOLIBC_AARCH64_SYS_H
+#define _NOLIBC_AARCH64_SYS_H
 
 /* The struct returned by the newfstatat() syscall. Differs slightly from the
  * x86_64's stat one by field ordering, so be careful.
@@ -171,34 +169,4 @@ struct sys_stat_struct {
 	_arg1;                                                                \
 })
 
-char **environ __attribute__((weak));
-const unsigned long *_auxv __attribute__((weak));
-
-/* startup code */
-void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector _start(void)
-{
-	__asm__ volatile (
-#ifdef _NOLIBC_STACKPROTECTOR
-		"bl __stack_chk_init\n"   /* initialize stack protector                     */
-#endif
-		"ldr x0, [sp]\n"     /* argc (x0) was in the stack                          */
-		"add x1, sp, 8\n"    /* argv (x1) = sp                                      */
-		"lsl x2, x0, 3\n"    /* envp (x2) = 8*argc ...                              */
-		"add x2, x2, 8\n"    /*           + 8 (skip null)                           */
-		"add x2, x2, x1\n"   /*           + argv                                    */
-		"adrp x3, environ\n"          /* x3 = &environ (high bits)                  */
-		"str x2, [x3, #:lo12:environ]\n" /* store envp into environ                 */
-		"mov x4, x2\n"       /* search for auxv (follows NULL after last env)       */
-		"0:\n"
-		"ldr x5, [x4], 8\n"  /* x5 = *x4; x4 += 8                                   */
-		"cbnz x5, 0b\n"      /* and stop at NULL after last env                     */
-		"adrp x3, _auxv\n"   /* x3 = &_auxv (high bits)                             */
-		"str x4, [x3, #:lo12:_auxv]\n" /* store x4 into _auxv                       */
-		"and sp, x1, -16\n"  /* sp must be 16-byte aligned in the callee            */
-		"bl main\n"          /* main() returns the status code, we'll exit with it. */
-		"mov x8, 93\n"       /* NR_exit == 93                                       */
-		"svc #0\n"
-	);
-	__builtin_unreachable();
-}
-#endif /* _NOLIBC_AARCH64_ARCH_H */
+#endif /* _NOLIBC_AARCH64_SYS_H */
diff --git a/tools/include/nolibc/arch.h b/tools/include/nolibc/arch.h
deleted file mode 100644
index f98616f5b219..000000000000
--- a/tools/include/nolibc/arch.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
-/*
- * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
- */
-
-/* Below comes the architecture-specific code. For each architecture, we have
- * the syscall declarations and the _start code definition. This is the only
- * global part. On all architectures the kernel puts everything in the stack
- * before jumping to _start just above us, without any return address (_start
- * is not a function but an entry point). So at the stack pointer we find argc.
- * Then argv[] begins, and ends at the first NULL. Then we have envp which
- * starts and ends with a NULL as well. So envp=argv+argc+1.
- */
-
-#ifndef _NOLIBC_ARCH_H
-#define _NOLIBC_ARCH_H
-
-#if defined(__x86_64__)
-#include "x86_64/arch.h"
-#elif defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__)
-#include "i386/arch.h"
-#elif defined(__ARM_EABI__)
-#include "arm/arch.h"
-#elif defined(__aarch64__)
-#include "aarch64/arch.h"
-#elif defined(__mips__) && defined(_ABIO32)
-#include "mips/arch.h"
-#elif defined(__riscv)
-#include "riscv/arch.h"
-#elif defined(__s390x__)
-#include "s390/arch.h"
-#elif defined(__loongarch__)
-#include "loongarch/arch.h"
-#endif
-
-#endif /* _NOLIBC_ARCH_H */
diff --git a/tools/include/nolibc/arm/crt.h b/tools/include/nolibc/arm/crt.h
new file mode 100644
index 000000000000..7b3b9e21e5c4
--- /dev/null
+++ b/tools/include/nolibc/arm/crt.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
+/*
+ * ARM specific startup code for NOLIBC
+ * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
+ */
+
+#ifndef _NOLIBC_ARM_CRT_H
+#define _NOLIBC_ARM_CRT_H
+
+/* startup code */
+void __attribute__((weak, noreturn, optimize("omit-frame-pointer"))) __no_stack_protector _start(void)
+{
+	__asm__ volatile (
+#ifdef _NOLIBC_STACKPROTECTOR
+		"bl __stack_chk_init\n"       /* initialize stack protector                          */
+#endif
+		"pop {%r0}\n"                 /* argc was in the stack                               */
+		"mov %r1, %sp\n"              /* argv = sp                                           */
+
+		"add %r2, %r0, $1\n"          /* envp = (argc + 1) ...                               */
+		"lsl %r2, %r2, $2\n"          /*        * 4        ...                               */
+		"add %r2, %r2, %r1\n"         /*        + argv                                       */
+		"ldr %r3, 1f\n"               /* r3 = &environ (see below)                           */
+		"str %r2, [r3]\n"             /* store envp into environ                             */
+
+		"mov r4, r2\n"                /* search for auxv (follows NULL after last env)       */
+		"0:\n"
+		"mov r5, r4\n"                /* r5 = r4                                             */
+		"add r4, r4, #4\n"            /* r4 += 4                                             */
+		"ldr r5,[r5]\n"               /* r5 = *r5 = *(r4-4)                                  */
+		"cmp r5, #0\n"                /* and stop at NULL after last env                     */
+		"bne 0b\n"
+		"ldr %r3, 2f\n"               /* r3 = &_auxv (low bits)                              */
+		"str r4, [r3]\n"              /* store r4 into _auxv                                 */
+
+		"mov %r3, $8\n"               /* AAPCS : sp must be 8-byte aligned in the            */
+		"neg %r3, %r3\n"              /*         callee, and bl doesn't push (lr=pc)         */
+		"and %r3, %r3, %r1\n"         /* so we do sp = r1(=sp) & r3(=-8);                    */
+		"mov %sp, %r3\n"
+
+		"bl main\n"                   /* main() returns the status code, we'll exit with it. */
+		"movs r7, $1\n"               /* NR_exit == 1                                        */
+		"svc $0x00\n"
+		".align 2\n"                  /* below are the pointers to a few variables           */
+		"1:\n"
+		".word environ\n"
+		"2:\n"
+		".word _auxv\n"
+	);
+	__builtin_unreachable();
+}
+
+#endif /* _NOLIBC_ARM_CRT_H */
diff --git a/tools/include/nolibc/arm/arch.h b/tools/include/nolibc/arm/sys.h
similarity index 81%
rename from tools/include/nolibc/arm/arch.h
rename to tools/include/nolibc/arm/sys.h
index 473d2c000740..79173f860948 100644
--- a/tools/include/nolibc/arm/arch.h
+++ b/tools/include/nolibc/arm/sys.h
@@ -1,13 +1,11 @@
 /* SPDX-License-Identifier: LGPL-2.1 OR MIT */
 /*
- * ARM specific definitions for NOLIBC
+ * ARM specific syscall declarations for NOLIBC
  * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
  */
 
-#ifndef _NOLIBC_ARM_ARCH_H
-#define _NOLIBC_ARM_ARCH_H
-
-#include "compiler.h"
+#ifndef _NOLIBC_ARM_SYS_H
+#define _NOLIBC_ARM_SYS_H
 
 /* The struct returned by the stat() syscall, 32-bit only, the syscall returns
  * exactly 56 bytes (stops before the unused array). In big endian, the format
@@ -69,7 +67,7 @@ struct sys_stat_struct {
  *
  * Also, ARM supports the old_select syscall if newselect is not available
  */
-#define __ARCH_WANT_SYS_OLD_SELECT
+#define __SYS_WANT_SYS_OLD_SELECT
 
 #if (defined(__THUMBEB__) || defined(__THUMBEL__)) && \
     !defined(NOLIBC_OMIT_FRAME_POINTER)
@@ -220,51 +218,4 @@ struct sys_stat_struct {
 	_arg1;                                                                \
 })
 
-
-char **environ __attribute__((weak));
-const unsigned long *_auxv __attribute__((weak));
-
-/* startup code */
-void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector _start(void)
-{
-	__asm__ volatile (
-#ifdef _NOLIBC_STACKPROTECTOR
-		"bl __stack_chk_init\n"       /* initialize stack protector                          */
-#endif
-		"pop {%r0}\n"                 /* argc was in the stack                               */
-		"mov %r1, %sp\n"              /* argv = sp                                           */
-
-		"add %r2, %r0, $1\n"          /* envp = (argc + 1) ...                               */
-		"lsl %r2, %r2, $2\n"          /*        * 4        ...                               */
-		"add %r2, %r2, %r1\n"         /*        + argv                                       */
-		"ldr %r3, 1f\n"               /* r3 = &environ (see below)                           */
-		"str %r2, [r3]\n"             /* store envp into environ                             */
-
-		"mov r4, r2\n"                /* search for auxv (follows NULL after last env)       */
-		"0:\n"
-		"mov r5, r4\n"                /* r5 = r4                                             */
-		"add r4, r4, #4\n"            /* r4 += 4                                             */
-		"ldr r5,[r5]\n"               /* r5 = *r5 = *(r4-4)                                  */
-		"cmp r5, #0\n"                /* and stop at NULL after last env                     */
-		"bne 0b\n"
-		"ldr %r3, 2f\n"               /* r3 = &_auxv (low bits)                              */
-		"str r4, [r3]\n"              /* store r4 into _auxv                                 */
-
-		"mov %r3, $8\n"               /* AAPCS : sp must be 8-byte aligned in the            */
-		"neg %r3, %r3\n"              /*         callee, and bl doesn't push (lr=pc)         */
-		"and %r3, %r3, %r1\n"         /* so we do sp = r1(=sp) & r3(=-8);                    */
-		"mov %sp, %r3\n"
-
-		"bl main\n"                   /* main() returns the status code, we'll exit with it. */
-		"movs r7, $1\n"               /* NR_exit == 1                                        */
-		"svc $0x00\n"
-		".align 2\n"                  /* below are the pointers to a few variables           */
-		"1:\n"
-		".word environ\n"
-		"2:\n"
-		".word _auxv\n"
-	);
-	__builtin_unreachable();
-}
-
-#endif /* _NOLIBC_ARM_ARCH_H */
+#endif /* _NOLIBC_ARM_SYS_H */
diff --git a/tools/include/nolibc/crt.h b/tools/include/nolibc/crt.h
new file mode 100644
index 000000000000..221b7c5346ca
--- /dev/null
+++ b/tools/include/nolibc/crt.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
+/*
+ * C Run Time support for NOLIBC
+ * Copyright (C) 2023 Zhangjin Wu <falcon@tinylab.org>
+ */
+
+#ifndef _NOLIBC_CRT_H
+#define _NOLIBC_CRT_H
+
+#include "compiler.h"
+#include "crt_arch.h"
+
+char **environ __attribute__((weak));
+const unsigned long *_auxv __attribute__((weak));
+
+#endif /* _NOLIBC_CRT_H */
diff --git a/tools/include/nolibc/crt_arch.h b/tools/include/nolibc/crt_arch.h
new file mode 100644
index 000000000000..741402557bd0
--- /dev/null
+++ b/tools/include/nolibc/crt_arch.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
+/*
+ * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
+ */
+
+/* Below comes the architecture-specific code. For each architecture, we have
+ * the syscall declarations and the _start code definition. This is the global
+ * part for _start code definition.
+ */
+
+#ifndef _NOLIBC_CRT_ARCH_H
+#define _NOLIBC_CRT_ARCH_H
+
+#if defined(__x86_64__)
+#include "x86_64/crt.h"
+#elif defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__)
+#include "i386/crt.h"
+#elif defined(__ARM_EABI__)
+#include "arm/crt.h"
+#elif defined(__aarch64__)
+#include "aarch64/crt.h"
+#elif defined(__mips__) && defined(_ABIO32)
+#include "mips/crt.h"
+#elif defined(__riscv)
+#include "riscv/crt.h"
+#elif defined(__s390x__)
+#include "s390/crt.h"
+#elif defined(__loongarch__)
+#include "loongarch/crt.h"
+#endif
+
+#endif /* _NOLIBC_CRT_ARCH_H */
diff --git a/tools/include/nolibc/i386/crt.h b/tools/include/nolibc/i386/crt.h
new file mode 100644
index 000000000000..0b88f7c887ae
--- /dev/null
+++ b/tools/include/nolibc/i386/crt.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
+/*
+ * i386 specific startup code for NOLIBC
+ * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
+ */
+
+#ifndef _NOLIBC_I386_CRT_H
+#define _NOLIBC_I386_CRT_H
+
+/* startup code */
+/*
+ * i386 System V ABI mandates:
+ * 1) last pushed argument must be 16-byte aligned.
+ * 2) The deepest stack frame should be set to zero
+ *
+ */
+void __attribute__((weak, noreturn, optimize("omit-frame-pointer"))) __no_stack_protector _start(void)
+{
+	__asm__ volatile (
+#ifdef _NOLIBC_STACKPROTECTOR
+		"call __stack_chk_init\n"   /* initialize stack protector                    */
+#endif
+		"pop %eax\n"                /* argc   (first arg, %eax)                      */
+		"mov %esp, %ebx\n"          /* argv[] (second arg, %ebx)                     */
+		"lea 4(%ebx,%eax,4),%ecx\n" /* then a NULL then envp (third arg, %ecx)       */
+		"mov %ecx, environ\n"       /* save environ                                  */
+		"xor %ebp, %ebp\n"          /* zero the stack frame                          */
+		"mov %ecx, %edx\n"          /* search for auxv (follows NULL after last env) */
+		"0:\n"
+		"add $4, %edx\n"            /* search for auxv using edx, it follows the     */
+		"cmp -4(%edx), %ebp\n"      /* ... NULL after last env (ebp is zero here)    */
+		"jnz 0b\n"
+		"mov %edx, _auxv\n"         /* save it into _auxv                            */
+		"and $-16, %esp\n"          /* x86 ABI : esp must be 16-byte aligned before  */
+		"sub $4, %esp\n"            /* the call instruction (args are aligned)       */
+		"push %ecx\n"               /* push all registers on the stack so that we    */
+		"push %ebx\n"               /* support both regparm and plain stack modes    */
+		"push %eax\n"
+		"call main\n"               /* main() returns the status code in %eax        */
+		"mov %eax, %ebx\n"          /* retrieve exit code (32-bit int)               */
+		"movl $1, %eax\n"           /* NR_exit == 1                                  */
+		"int $0x80\n"               /* exit now                                      */
+		"hlt\n"                     /* ensure it does not                            */
+	);
+	__builtin_unreachable();
+}
+
+#endif /* _NOLIBC_I386_CRT_H */
diff --git a/tools/include/nolibc/i386/arch.h b/tools/include/nolibc/i386/sys.h
similarity index 79%
rename from tools/include/nolibc/i386/arch.h
rename to tools/include/nolibc/i386/sys.h
index 66052742763e..c626cf2b145a 100644
--- a/tools/include/nolibc/i386/arch.h
+++ b/tools/include/nolibc/i386/sys.h
@@ -1,13 +1,11 @@
 /* SPDX-License-Identifier: LGPL-2.1 OR MIT */
 /*
- * i386 specific definitions for NOLIBC
+ * i386 specific syscall declarations for NOLIBC
  * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
  */
 
-#ifndef _NOLIBC_I386_ARCH_H
-#define _NOLIBC_I386_ARCH_H
-
-#include "compiler.h"
+#ifndef _NOLIBC_I386_SYS_H
+#define _NOLIBC_I386_SYS_H
 
 /* The struct returned by the stat() syscall, 32-bit only, the syscall returns
  * exactly 56 bytes (stops before the unused array).
@@ -180,45 +178,4 @@ struct sys_stat_struct {
 	_eax;							\
 })
 
-char **environ __attribute__((weak));
-const unsigned long *_auxv __attribute__((weak));
-
-/* startup code */
-/*
- * i386 System V ABI mandates:
- * 1) last pushed argument must be 16-byte aligned.
- * 2) The deepest stack frame should be set to zero
- *
- */
-void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector _start(void)
-{
-	__asm__ volatile (
-#ifdef _NOLIBC_STACKPROTECTOR
-		"call __stack_chk_init\n"   /* initialize stack protector                    */
-#endif
-		"pop %eax\n"                /* argc   (first arg, %eax)                      */
-		"mov %esp, %ebx\n"          /* argv[] (second arg, %ebx)                     */
-		"lea 4(%ebx,%eax,4),%ecx\n" /* then a NULL then envp (third arg, %ecx)       */
-		"mov %ecx, environ\n"       /* save environ                                  */
-		"xor %ebp, %ebp\n"          /* zero the stack frame                          */
-		"mov %ecx, %edx\n"          /* search for auxv (follows NULL after last env) */
-		"0:\n"
-		"add $4, %edx\n"            /* search for auxv using edx, it follows the     */
-		"cmp -4(%edx), %ebp\n"      /* ... NULL after last env (ebp is zero here)    */
-		"jnz 0b\n"
-		"mov %edx, _auxv\n"         /* save it into _auxv                            */
-		"and $-16, %esp\n"          /* x86 ABI : esp must be 16-byte aligned before  */
-		"sub $4, %esp\n"            /* the call instruction (args are aligned)       */
-		"push %ecx\n"               /* push all registers on the stack so that we    */
-		"push %ebx\n"               /* support both regparm and plain stack modes    */
-		"push %eax\n"
-		"call main\n"               /* main() returns the status code in %eax        */
-		"mov %eax, %ebx\n"          /* retrieve exit code (32-bit int)               */
-		"movl $1, %eax\n"           /* NR_exit == 1                                  */
-		"int $0x80\n"               /* exit now                                      */
-		"hlt\n"                     /* ensure it does not                            */
-	);
-	__builtin_unreachable();
-}
-
-#endif /* _NOLIBC_I386_ARCH_H */
+#endif /* _NOLIBC_I386_SYS_H */
diff --git a/tools/include/nolibc/loongarch/crt.h b/tools/include/nolibc/loongarch/crt.h
new file mode 100644
index 000000000000..253d2fccea1e
--- /dev/null
+++ b/tools/include/nolibc/loongarch/crt.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
+/*
+ * LoongArch specific startup code for NOLIBC
+ * Copyright (C) 2023 Loongson Technology Corporation Limited
+ */
+
+#ifndef _NOLIBC_LOONGARCH_CRT_H
+#define _NOLIBC_LOONGARCH_CRT_H
+
+#if __loongarch_grlen == 32
+#define LONGLOG      "2"
+#define SZREG        "4"
+#define REG_L        "ld.w"
+#define LONG_S       "st.w"
+#define LONG_ADD     "add.w"
+#define LONG_ADDI    "addi.w"
+#define LONG_SLL     "slli.w"
+#define LONG_BSTRINS "bstrins.w"
+#else /* __loongarch_grlen == 64 */
+#define LONGLOG      "3"
+#define SZREG        "8"
+#define REG_L        "ld.d"
+#define LONG_S       "st.d"
+#define LONG_ADD     "add.d"
+#define LONG_ADDI    "addi.d"
+#define LONG_SLL     "slli.d"
+#define LONG_BSTRINS "bstrins.d"
+#endif
+
+/* startup code */
+void __attribute__((weak, noreturn, optimize("omit-frame-pointer"))) __no_stack_protector _start(void)
+{
+	__asm__ volatile (
+#ifdef _NOLIBC_STACKPROTECTOR
+		"bl __stack_chk_init\n"               /* initialize stack protector                          */
+#endif
+		REG_L        " $a0, $sp, 0\n"         /* argc (a0) was in the stack                          */
+		LONG_ADDI    " $a1, $sp, "SZREG"\n"   /* argv (a1) = sp + SZREG                              */
+		LONG_SLL     " $a2, $a0, "LONGLOG"\n" /* envp (a2) = SZREG*argc ...                          */
+		LONG_ADDI    " $a2, $a2, "SZREG"\n"   /*             + SZREG (skip null)                     */
+		LONG_ADD     " $a2, $a2, $a1\n"       /*             + argv                                  */
+
+		"move          $a3, $a2\n"            /* iterate a3 over envp to find auxv (after NULL)      */
+		"0:\n"                                /* do {                                                */
+		REG_L        " $a4, $a3, 0\n"         /*   a4 = *a3;                                         */
+		LONG_ADDI    " $a3, $a3, "SZREG"\n"   /*   a3 += sizeof(void*);                              */
+		"bne           $a4, $zero, 0b\n"      /* } while (a4);                                       */
+		"la.pcrel      $a4, _auxv\n"          /* a4 = &_auxv                                         */
+		LONG_S       " $a3, $a4, 0\n"         /* store a3 into _auxv                                 */
+
+		"la.pcrel      $a3, environ\n"        /* a3 = &environ                                       */
+		LONG_S       " $a2, $a3, 0\n"         /* store envp(a2) into environ                         */
+		LONG_BSTRINS " $sp, $zero, 3, 0\n"    /* sp must be 16-byte aligned                          */
+		"bl            main\n"                /* main() returns the status code, we'll exit with it. */
+		"li.w          $a7, 93\n"             /* NR_exit == 93                                       */
+		"syscall       0\n"
+	);
+	__builtin_unreachable();
+}
+
+#endif /* _NOLIBC_LOONGARCH_CRT_H */
diff --git a/tools/include/nolibc/loongarch/arch.h b/tools/include/nolibc/loongarch/sys.h
similarity index 73%
rename from tools/include/nolibc/loongarch/arch.h
rename to tools/include/nolibc/loongarch/sys.h
index 63fee1e8f4d9..98d47b5823dc 100644
--- a/tools/include/nolibc/loongarch/arch.h
+++ b/tools/include/nolibc/loongarch/sys.h
@@ -1,13 +1,11 @@
 /* SPDX-License-Identifier: LGPL-2.1 OR MIT */
 /*
- * LoongArch specific definitions for NOLIBC
+ * LoongArch specific syscall declarations for NOLIBC
  * Copyright (C) 2023 Loongson Technology Corporation Limited
  */
 
-#ifndef _NOLIBC_LOONGARCH_ARCH_H
-#define _NOLIBC_LOONGARCH_ARCH_H
-
-#include "compiler.h"
+#ifndef _NOLIBC_LOONGARCH_SYS_H
+#define _NOLIBC_LOONGARCH_SYS_H
 
 /* Syscalls for LoongArch :
  *   - stack is 16-byte aligned
@@ -143,58 +141,4 @@
 	_arg1;                                                                \
 })
 
-char **environ __attribute__((weak));
-const unsigned long *_auxv __attribute__((weak));
-
-#if __loongarch_grlen == 32
-#define LONGLOG      "2"
-#define SZREG        "4"
-#define REG_L        "ld.w"
-#define LONG_S       "st.w"
-#define LONG_ADD     "add.w"
-#define LONG_ADDI    "addi.w"
-#define LONG_SLL     "slli.w"
-#define LONG_BSTRINS "bstrins.w"
-#else /* __loongarch_grlen == 64 */
-#define LONGLOG      "3"
-#define SZREG        "8"
-#define REG_L        "ld.d"
-#define LONG_S       "st.d"
-#define LONG_ADD     "add.d"
-#define LONG_ADDI    "addi.d"
-#define LONG_SLL     "slli.d"
-#define LONG_BSTRINS "bstrins.d"
-#endif
-
-/* startup code */
-void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector _start(void)
-{
-	__asm__ volatile (
-#ifdef _NOLIBC_STACKPROTECTOR
-		"bl __stack_chk_init\n"               /* initialize stack protector                          */
-#endif
-		REG_L        " $a0, $sp, 0\n"         /* argc (a0) was in the stack                          */
-		LONG_ADDI    " $a1, $sp, "SZREG"\n"   /* argv (a1) = sp + SZREG                              */
-		LONG_SLL     " $a2, $a0, "LONGLOG"\n" /* envp (a2) = SZREG*argc ...                          */
-		LONG_ADDI    " $a2, $a2, "SZREG"\n"   /*             + SZREG (skip null)                     */
-		LONG_ADD     " $a2, $a2, $a1\n"       /*             + argv                                  */
-
-		"move          $a3, $a2\n"            /* iterate a3 over envp to find auxv (after NULL)      */
-		"0:\n"                                /* do {                                                */
-		REG_L        " $a4, $a3, 0\n"         /*   a4 = *a3;                                         */
-		LONG_ADDI    " $a3, $a3, "SZREG"\n"   /*   a3 += sizeof(void*);                              */
-		"bne           $a4, $zero, 0b\n"      /* } while (a4);                                       */
-		"la.pcrel      $a4, _auxv\n"          /* a4 = &_auxv                                         */
-		LONG_S       " $a3, $a4, 0\n"         /* store a3 into _auxv                                 */
-
-		"la.pcrel      $a3, environ\n"        /* a3 = &environ                                       */
-		LONG_S       " $a2, $a3, 0\n"         /* store envp(a2) into environ                         */
-		LONG_BSTRINS " $sp, $zero, 3, 0\n"    /* sp must be 16-byte aligned                          */
-		"bl            main\n"                /* main() returns the status code, we'll exit with it. */
-		"li.w          $a7, 93\n"             /* NR_exit == 93                                       */
-		"syscall       0\n"
-	);
-	__builtin_unreachable();
-}
-
-#endif /* _NOLIBC_LOONGARCH_ARCH_H */
+#endif /* _NOLIBC_LOONGARCH_SYS_H */
diff --git a/tools/include/nolibc/mips/crt.h b/tools/include/nolibc/mips/crt.h
new file mode 100644
index 000000000000..38df52d8ec16
--- /dev/null
+++ b/tools/include/nolibc/mips/crt.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
+/*
+ * MIPS specific startup code for NOLIBC
+ * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
+ */
+
+#ifndef _NOLIBC_MIPS_CRT_H
+#define _NOLIBC_MIPS_CRT_H
+
+/* startup code, note that it's called __start on MIPS */
+void __attribute__((weak, noreturn, optimize("omit-frame-pointer"))) __no_stack_protector __start(void)
+{
+	__asm__ volatile (
+		/*".set nomips16\n"*/
+		".set push\n"
+		".set    noreorder\n"
+		".option pic0\n"
+#ifdef _NOLIBC_STACKPROTECTOR
+		"jal __stack_chk_init\n" /* initialize stack protector                         */
+		"nop\n"                  /* delayed slot                                       */
+#endif
+		/*".ent __start\n"*/
+		/*"__start:\n"*/
+		"lw $a0,($sp)\n"        /* argc was in the stack                               */
+		"addiu  $a1, $sp, 4\n"  /* argv = sp + 4                                       */
+		"sll $a2, $a0, 2\n"     /* a2 = argc * 4                                       */
+		"add   $a2, $a2, $a1\n" /* envp = argv + 4*argc ...                            */
+		"addiu $a2, $a2, 4\n"   /*        ... + 4                                      */
+		"lui $a3, %hi(environ)\n"     /* load environ into a3 (hi)                     */
+		"addiu $a3, %lo(environ)\n"   /* load environ into a3 (lo)                     */
+		"sw $a2,($a3)\n"              /* store envp(a2) into environ                   */
+
+		"move $t0, $a2\n"             /* iterate t0 over envp, look for NULL           */
+		"0:"                          /* do {                                          */
+		"lw $a3, ($t0)\n"             /*   a3=*(t0);                                   */
+		"bne $a3, $0, 0b\n"           /* } while (a3);                                 */
+		"addiu $t0, $t0, 4\n"         /* delayed slot: t0+=4;                          */
+		"lui $a3, %hi(_auxv)\n"       /* load _auxv into a3 (hi)                       */
+		"addiu $a3, %lo(_auxv)\n"     /* load _auxv into a3 (lo)                       */
+		"sw $t0, ($a3)\n"             /* store t0 into _auxv                           */
+
+		"li $t0, -8\n"
+		"and $sp, $sp, $t0\n"   /* sp must be 8-byte aligned                           */
+		"addiu $sp,$sp,-16\n"   /* the callee expects to save a0..a3 there!            */
+		"jal main\n"            /* main() returns the status code, we'll exit with it. */
+		"nop\n"                 /* delayed slot                                        */
+		"move $a0, $v0\n"       /* retrieve 32-bit exit code from v0                   */
+		"li $v0, 4001\n"        /* NR_exit == 4001                                     */
+		"syscall\n"
+		/*".end __start\n"*/
+		".set pop\n"
+	);
+	__builtin_unreachable();
+}
+
+#endif /* _NOLIBC_MIPS_CRT_H */
diff --git a/tools/include/nolibc/mips/arch.h b/tools/include/nolibc/mips/sys.h
similarity index 79%
rename from tools/include/nolibc/mips/arch.h
rename to tools/include/nolibc/mips/sys.h
index 1581b721b714..13dfbf3e6a3d 100644
--- a/tools/include/nolibc/mips/arch.h
+++ b/tools/include/nolibc/mips/sys.h
@@ -1,13 +1,11 @@
 /* SPDX-License-Identifier: LGPL-2.1 OR MIT */
 /*
- * MIPS specific definitions for NOLIBC
+ * MIPS specific syscall declarations for NOLIBC
  * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
  */
 
-#ifndef _NOLIBC_MIPS_ARCH_H
-#define _NOLIBC_MIPS_ARCH_H
-
-#include "compiler.h"
+#ifndef _NOLIBC_MIPS_SYS_H
+#define _NOLIBC_MIPS_SYS_H
 
 /* The struct returned by the stat() syscall. 88 bytes are returned by the
  * syscall.
@@ -201,53 +199,4 @@ struct sys_stat_struct {
 	_arg4 ? -_num : _num;                                                 \
 })
 
-char **environ __attribute__((weak));
-const unsigned long *_auxv __attribute__((weak));
-
-/* startup code, note that it's called __start on MIPS */
-void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector __start(void)
-{
-	__asm__ volatile (
-		/*".set nomips16\n"*/
-		".set push\n"
-		".set    noreorder\n"
-		".option pic0\n"
-#ifdef _NOLIBC_STACKPROTECTOR
-		"jal __stack_chk_init\n" /* initialize stack protector                         */
-		"nop\n"                  /* delayed slot                                       */
-#endif
-		/*".ent __start\n"*/
-		/*"__start:\n"*/
-		"lw $a0,($sp)\n"        /* argc was in the stack                               */
-		"addiu  $a1, $sp, 4\n"  /* argv = sp + 4                                       */
-		"sll $a2, $a0, 2\n"     /* a2 = argc * 4                                       */
-		"add   $a2, $a2, $a1\n" /* envp = argv + 4*argc ...                            */
-		"addiu $a2, $a2, 4\n"   /*        ... + 4                                      */
-		"lui $a3, %hi(environ)\n"     /* load environ into a3 (hi)                     */
-		"addiu $a3, %lo(environ)\n"   /* load environ into a3 (lo)                     */
-		"sw $a2,($a3)\n"              /* store envp(a2) into environ                   */
-
-		"move $t0, $a2\n"             /* iterate t0 over envp, look for NULL           */
-		"0:"                          /* do {                                          */
-		"lw $a3, ($t0)\n"             /*   a3=*(t0);                                   */
-		"bne $a3, $0, 0b\n"           /* } while (a3);                                 */
-		"addiu $t0, $t0, 4\n"         /* delayed slot: t0+=4;                          */
-		"lui $a3, %hi(_auxv)\n"       /* load _auxv into a3 (hi)                       */
-		"addiu $a3, %lo(_auxv)\n"     /* load _auxv into a3 (lo)                       */
-		"sw $t0, ($a3)\n"             /* store t0 into _auxv                           */
-
-		"li $t0, -8\n"
-		"and $sp, $sp, $t0\n"   /* sp must be 8-byte aligned                           */
-		"addiu $sp,$sp,-16\n"   /* the callee expects to save a0..a3 there!            */
-		"jal main\n"            /* main() returns the status code, we'll exit with it. */
-		"nop\n"                 /* delayed slot                                        */
-		"move $a0, $v0\n"       /* retrieve 32-bit exit code from v0                   */
-		"li $v0, 4001\n"        /* NR_exit == 4001                                     */
-		"syscall\n"
-		/*".end __start\n"*/
-		".set pop\n"
-	);
-	__builtin_unreachable();
-}
-
-#endif /* _NOLIBC_MIPS_ARCH_H */
+#endif /* _NOLIBC_MIPS_SYS_H */
diff --git a/tools/include/nolibc/nolibc.h b/tools/include/nolibc/nolibc.h
index 1f8d821000ac..096a18b71923 100644
--- a/tools/include/nolibc/nolibc.h
+++ b/tools/include/nolibc/nolibc.h
@@ -93,8 +93,8 @@
 #define _NOLIBC_H
 
 #include "std.h"
-#include "arch.h"
 #include "types.h"
+#include "crt.h"
 #include "sys.h"
 #include "ctype.h"
 #include "signal.h"
diff --git a/tools/include/nolibc/riscv/crt.h b/tools/include/nolibc/riscv/crt.h
new file mode 100644
index 000000000000..1f81a2e47a48
--- /dev/null
+++ b/tools/include/nolibc/riscv/crt.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
+/*
+ * RISCV (32 and 64) specific startup code for NOLIBC
+ * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
+ */
+
+#ifndef _NOLIBC_RISCV_CRT_H
+#define _NOLIBC_RISCV_CRT_H
+
+#if   __riscv_xlen == 64
+#define PTRLOG "3"
+#define SZREG  "8"
+#define REG_L  "ld"
+#define REG_S  "sd"
+#elif __riscv_xlen == 32
+#define PTRLOG "2"
+#define SZREG  "4"
+#define REG_L  "lw"
+#define REG_S  "sw"
+#endif
+
+/* startup code */
+void __attribute__((weak, noreturn, optimize("omit-frame-pointer"))) __no_stack_protector _start(void)
+{
+	__asm__ volatile (
+		".option push\n"
+		".option norelax\n"
+		"lla   gp, __global_pointer$\n"
+		".option pop\n"
+#ifdef _NOLIBC_STACKPROTECTOR
+		"call __stack_chk_init\n"    /* initialize stack protector                          */
+#endif
+		REG_L" a0, 0(sp)\n"          /* argc (a0) was in the stack                          */
+		"add   a1, sp, "SZREG"\n"    /* argv (a1) = sp                                      */
+		"slli  a2, a0, "PTRLOG"\n"   /* envp (a2) = SZREG*argc ...                          */
+		"add   a2, a2, "SZREG"\n"    /*             + SZREG (skip null)                     */
+		"add   a2,a2,a1\n"           /*             + argv                                  */
+
+		"add   a3, a2, zero\n"       /* iterate a3 over envp to find auxv (after NULL)      */
+		"0:\n"                       /* do {                                                */
+		REG_L" a4, 0(a3)\n"          /*   a4 = *a3;                                         */
+		"add   a3, a3, "SZREG"\n"    /*   a3 += sizeof(void*);                              */
+		"bne   a4, zero, 0b\n"       /* } while (a4);                                       */
+		"lui   a4, %hi(_auxv)\n"     /* a4 = &_auxv (high bits)                             */
+		REG_S" a3, %lo(_auxv)(a4)\n" /* store a3 into _auxv                                 */
+
+		"lui   a3, %hi(environ)\n"   /* a3 = &environ (high bits)                           */
+		REG_S" a2,%lo(environ)(a3)\n"/* store envp(a2) into environ                         */
+		"andi  sp,a1,-16\n"          /* sp must be 16-byte aligned                          */
+		"call  main\n"               /* main() returns the status code, we'll exit with it. */
+		"li a7, 93\n"                /* NR_exit == 93                                       */
+		"ecall\n"
+	);
+	__builtin_unreachable();
+}
+
+#endif /* _NOLIBC_RISCV_CRT_H */
diff --git a/tools/include/nolibc/riscv/arch.h b/tools/include/nolibc/riscv/sys.h
similarity index 78%
rename from tools/include/nolibc/riscv/arch.h
rename to tools/include/nolibc/riscv/sys.h
index de68759f5959..aa15b86c919f 100644
--- a/tools/include/nolibc/riscv/arch.h
+++ b/tools/include/nolibc/riscv/sys.h
@@ -1,13 +1,11 @@
 /* SPDX-License-Identifier: LGPL-2.1 OR MIT */
 /*
- * RISCV (32 and 64) specific definitions for NOLIBC
+ * RISCV (32 and 64) specific syscall declarations for NOLIBC
  * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
  */
 
-#ifndef _NOLIBC_RISCV_ARCH_H
-#define _NOLIBC_RISCV_ARCH_H
-
-#include "compiler.h"
+#ifndef _NOLIBC_RISCV_SYS_H
+#define _NOLIBC_RISCV_SYS_H
 
 struct sys_stat_struct {
 	unsigned long	st_dev;		/* Device.  */
@@ -32,18 +30,6 @@ struct sys_stat_struct {
 	unsigned int	__unused5;
 };
 
-#if   __riscv_xlen == 64
-#define PTRLOG "3"
-#define SZREG  "8"
-#define REG_L  "ld"
-#define REG_S  "sd"
-#elif __riscv_xlen == 32
-#define PTRLOG "2"
-#define SZREG  "4"
-#define REG_L  "lw"
-#define REG_S  "sw"
-#endif
-
 /* Syscalls for RISCV :
  *   - stack is 16-byte aligned
  *   - syscall number is passed in a7
@@ -176,42 +162,4 @@ struct sys_stat_struct {
 	_arg1;                                                                \
 })
 
-char **environ __attribute__((weak));
-const unsigned long *_auxv __attribute__((weak));
-
-/* startup code */
-void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector _start(void)
-{
-	__asm__ volatile (
-		".option push\n"
-		".option norelax\n"
-		"lla   gp, __global_pointer$\n"
-		".option pop\n"
-#ifdef _NOLIBC_STACKPROTECTOR
-		"call __stack_chk_init\n"    /* initialize stack protector                          */
-#endif
-		REG_L" a0, 0(sp)\n"          /* argc (a0) was in the stack                          */
-		"add   a1, sp, "SZREG"\n"    /* argv (a1) = sp                                      */
-		"slli  a2, a0, "PTRLOG"\n"   /* envp (a2) = SZREG*argc ...                          */
-		"add   a2, a2, "SZREG"\n"    /*             + SZREG (skip null)                     */
-		"add   a2,a2,a1\n"           /*             + argv                                  */
-
-		"add   a3, a2, zero\n"       /* iterate a3 over envp to find auxv (after NULL)      */
-		"0:\n"                       /* do {                                                */
-		REG_L" a4, 0(a3)\n"          /*   a4 = *a3;                                         */
-		"add   a3, a3, "SZREG"\n"    /*   a3 += sizeof(void*);                              */
-		"bne   a4, zero, 0b\n"       /* } while (a4);                                       */
-		"lui   a4, %hi(_auxv)\n"     /* a4 = &_auxv (high bits)                             */
-		REG_S" a3, %lo(_auxv)(a4)\n" /* store a3 into _auxv                                 */
-
-		"lui   a3, %hi(environ)\n"   /* a3 = &environ (high bits)                           */
-		REG_S" a2,%lo(environ)(a3)\n"/* store envp(a2) into environ                         */
-		"andi  sp,a1,-16\n"          /* sp must be 16-byte aligned                          */
-		"call  main\n"               /* main() returns the status code, we'll exit with it. */
-		"li a7, 93\n"                /* NR_exit == 93                                       */
-		"ecall\n"
-	);
-	__builtin_unreachable();
-}
-
-#endif /* _NOLIBC_RISCV_ARCH_H */
+#endif /* _NOLIBC_RISCV_SYS_H */
diff --git a/tools/include/nolibc/s390/crt.h b/tools/include/nolibc/s390/crt.h
new file mode 100644
index 000000000000..8580f615d525
--- /dev/null
+++ b/tools/include/nolibc/s390/crt.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
+/*
+ * s390 specific startup code for NOLIBC
+ */
+
+#ifndef _NOLIBC_S390_CRT_H
+#define _NOLIBC_S390_CRT_H
+
+/* startup code */
+void __attribute__((weak, noreturn, optimize("omit-frame-pointer"))) __no_stack_protector _start(void)
+{
+	__asm__ volatile (
+		"lg	%r2,0(%r15)\n"		/* argument count */
+		"la	%r3,8(%r15)\n"		/* argument pointers */
+
+		"xgr	%r0,%r0\n"		/* r0 will be our NULL value */
+		/* search for envp */
+		"lgr	%r4,%r3\n"		/* start at argv */
+		"0:\n"
+		"clg	%r0,0(%r4)\n"		/* entry zero? */
+		"la	%r4,8(%r4)\n"		/* advance pointer */
+		"jnz	0b\n"			/* no -> test next pointer */
+						/* yes -> r4 now contains start of envp */
+		"larl	%r1,environ\n"
+		"stg	%r4,0(%r1)\n"
+
+		/* search for auxv */
+		"lgr	%r5,%r4\n"		/* start at envp */
+		"1:\n"
+		"clg	%r0,0(%r5)\n"		/* entry zero? */
+		"la	%r5,8(%r5)\n"		/* advance pointer */
+		"jnz	1b\n"			/* no -> test next pointer */
+		"larl	%r1,_auxv\n"		/* yes -> store value in _auxv */
+		"stg	%r5,0(%r1)\n"
+
+		"aghi	%r15,-160\n"		/* allocate new stackframe */
+		"xc	0(8,%r15),0(%r15)\n"	/* clear backchain */
+		"brasl	%r14,main\n"		/* ret value of main is arg to exit */
+		"lghi	%r1,1\n"		/* __NR_exit */
+		"svc	0\n"
+	);
+	__builtin_unreachable();
+}
+
+#endif /* _NOLIBC_S390_CRT_H */
diff --git a/tools/include/nolibc/s390/arch.h b/tools/include/nolibc/s390/sys.h
similarity index 78%
rename from tools/include/nolibc/s390/arch.h
rename to tools/include/nolibc/s390/sys.h
index a7b512e81234..041f4ecb7922 100644
--- a/tools/include/nolibc/s390/arch.h
+++ b/tools/include/nolibc/s390/sys.h
@@ -1,15 +1,13 @@
 /* SPDX-License-Identifier: LGPL-2.1 OR MIT */
 /*
- * s390 specific definitions for NOLIBC
+ * s390 specific syscall declarations for NOLIBC
  */
 
-#ifndef _NOLIBC_S390_ARCH_H
-#define _NOLIBC_S390_ARCH_H
+#ifndef _NOLIBC_S390_SYS_H
+#define _NOLIBC_S390_SYS_H
 #include <asm/signal.h>
 #include <asm/unistd.h>
 
-#include "compiler.h"
-
 /* The struct returned by the stat() syscall, equivalent to stat64(). The
  * syscall returns 116 bytes and stops in the middle of __unused.
  */
@@ -162,45 +160,6 @@ struct sys_stat_struct {
 	_arg1;								\
 })
 
-char **environ __attribute__((weak));
-const unsigned long *_auxv __attribute__((weak));
-
-/* startup code */
-void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector _start(void)
-{
-	__asm__ volatile (
-		"lg	%r2,0(%r15)\n"		/* argument count */
-		"la	%r3,8(%r15)\n"		/* argument pointers */
-
-		"xgr	%r0,%r0\n"		/* r0 will be our NULL value */
-		/* search for envp */
-		"lgr	%r4,%r3\n"		/* start at argv */
-		"0:\n"
-		"clg	%r0,0(%r4)\n"		/* entry zero? */
-		"la	%r4,8(%r4)\n"		/* advance pointer */
-		"jnz	0b\n"			/* no -> test next pointer */
-						/* yes -> r4 now contains start of envp */
-		"larl	%r1,environ\n"
-		"stg	%r4,0(%r1)\n"
-
-		/* search for auxv */
-		"lgr	%r5,%r4\n"		/* start at envp */
-		"1:\n"
-		"clg	%r0,0(%r5)\n"		/* entry zero? */
-		"la	%r5,8(%r5)\n"		/* advance pointer */
-		"jnz	1b\n"			/* no -> test next pointer */
-		"larl	%r1,_auxv\n"		/* yes -> store value in _auxv */
-		"stg	%r5,0(%r1)\n"
-
-		"aghi	%r15,-160\n"		/* allocate new stackframe */
-		"xc	0(8,%r15),0(%r15)\n"	/* clear backchain */
-		"brasl	%r14,main\n"		/* ret value of main is arg to exit */
-		"lghi	%r1,1\n"		/* __NR_exit */
-		"svc	0\n"
-	);
-	__builtin_unreachable();
-}
-
 struct s390_mmap_arg_struct {
 	unsigned long addr;
 	unsigned long len;
@@ -234,4 +193,4 @@ pid_t sys_fork(void)
 }
 #define sys_fork sys_fork
 
-#endif /* _NOLIBC_S390_ARCH_H */
+#endif /* _NOLIBC_S390_SYS_H */
diff --git a/tools/include/nolibc/signal.h b/tools/include/nolibc/signal.h
index 137552216e46..687c4c13a1d9 100644
--- a/tools/include/nolibc/signal.h
+++ b/tools/include/nolibc/signal.h
@@ -8,7 +8,6 @@
 #define _NOLIBC_SIGNAL_H
 
 #include "std.h"
-#include "arch.h"
 #include "types.h"
 #include "sys.h"
 
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 0eef91daf289..ee5b3f3b79e6 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -10,7 +10,6 @@
 #include <stdarg.h>
 
 #include "std.h"
-#include "arch.h"
 #include "errno.h"
 #include "types.h"
 #include "sys.h"
diff --git a/tools/include/nolibc/stdlib.h b/tools/include/nolibc/stdlib.h
index 902162f80337..2f9b4b3c6d26 100644
--- a/tools/include/nolibc/stdlib.h
+++ b/tools/include/nolibc/stdlib.h
@@ -8,8 +8,8 @@
 #define _NOLIBC_STDLIB_H
 
 #include "std.h"
-#include "arch.h"
 #include "types.h"
+#include "crt.h"
 #include "sys.h"
 #include "string.h"
 #include <linux/auxvec.h>
diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h
index dee56894a811..e84ed57f3a63 100644
--- a/tools/include/nolibc/sys.h
+++ b/tools/include/nolibc/sys.h
@@ -23,7 +23,7 @@
 #include <linux/stat.h>  /* for statx() */
 #include <linux/prctl.h>
 
-#include "arch.h"
+#include "sys_arch.h"
 #include "errno.h"
 #include "types.h"
 
diff --git a/tools/include/nolibc/sys_arch.h b/tools/include/nolibc/sys_arch.h
new file mode 100644
index 000000000000..b073051c7713
--- /dev/null
+++ b/tools/include/nolibc/sys_arch.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
+/*
+ * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
+ */
+
+/* Below comes the architecture-specific code. For each architecture, we have
+ * the syscall declarations and the _start code definition. This is the global
+ * part for syscall declarations.
+ */
+
+#ifndef _NOLIBC_SYS_ARCH_H
+#define _NOLIBC_SYS_ARCH_H
+
+#if defined(__x86_64__)
+#include "x86_64/sys.h"
+#elif defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__)
+#include "i386/sys.h"
+#elif defined(__ARM_EABI__)
+#include "arm/sys.h"
+#elif defined(__aarch64__)
+#include "aarch64/sys.h"
+#elif defined(__mips__) && defined(_ABIO32)
+#include "mips/sys.h"
+#elif defined(__riscv)
+#include "riscv/sys.h"
+#elif defined(__s390x__)
+#include "s390/sys.h"
+#elif defined(__loongarch__)
+#include "loongarch/sys.h"
+#endif
+
+#endif /* _NOLIBC_SYS_ARCH_H */
diff --git a/tools/include/nolibc/time.h b/tools/include/nolibc/time.h
index 84655361b9ad..127ac60706b2 100644
--- a/tools/include/nolibc/time.h
+++ b/tools/include/nolibc/time.h
@@ -8,7 +8,6 @@
 #define _NOLIBC_TIME_H
 
 #include "std.h"
-#include "arch.h"
 #include "types.h"
 #include "sys.h"
 
diff --git a/tools/include/nolibc/unistd.h b/tools/include/nolibc/unistd.h
index e38f3660c051..219cc5ea319a 100644
--- a/tools/include/nolibc/unistd.h
+++ b/tools/include/nolibc/unistd.h
@@ -8,7 +8,6 @@
 #define _NOLIBC_UNISTD_H
 
 #include "std.h"
-#include "arch.h"
 #include "types.h"
 #include "sys.h"
 
diff --git a/tools/include/nolibc/x86_64/crt.h b/tools/include/nolibc/x86_64/crt.h
new file mode 100644
index 000000000000..6fede1235efc
--- /dev/null
+++ b/tools/include/nolibc/x86_64/crt.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
+/*
+ * x86_64 specific startup code for NOLIBC
+ * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
+ */
+
+#ifndef _NOLIBC_X86_64_CRT_H
+#define _NOLIBC_X86_64_CRT_H
+
+/* startup code */
+/*
+ * x86-64 System V ABI mandates:
+ * 1) %rsp must be 16-byte aligned right before the function call.
+ * 2) The deepest stack frame should be zero (the %rbp).
+ *
+ */
+void __attribute__((weak, noreturn, optimize("omit-frame-pointer"))) __no_stack_protector _start(void)
+{
+	__asm__ volatile (
+#ifdef _NOLIBC_STACKPROTECTOR
+		"call __stack_chk_init\n"   /* initialize stack protector                          */
+#endif
+		"pop %rdi\n"                /* argc   (first arg, %rdi)                            */
+		"mov %rsp, %rsi\n"          /* argv[] (second arg, %rsi)                           */
+		"lea 8(%rsi,%rdi,8),%rdx\n" /* then a NULL then envp (third arg, %rdx)             */
+		"mov %rdx, environ\n"       /* save environ                                        */
+		"xor %ebp, %ebp\n"          /* zero the stack frame                                */
+		"mov %rdx, %rax\n"          /* search for auxv (follows NULL after last env)       */
+		"0:\n"
+		"add $8, %rax\n"            /* search for auxv using rax, it follows the           */
+		"cmp -8(%rax), %rbp\n"      /* ... NULL after last env (rbp is zero here)          */
+		"jnz 0b\n"
+		"mov %rax, _auxv\n"         /* save it into _auxv                                  */
+		"and $-16, %rsp\n"          /* x86 ABI : esp must be 16-byte aligned before call   */
+		"call main\n"               /* main() returns the status code, we'll exit with it. */
+		"mov %eax, %edi\n"          /* retrieve exit code (32 bit)                         */
+		"mov $60, %eax\n"           /* NR_exit == 60                                       */
+		"syscall\n"                 /* really exit                                         */
+		"hlt\n"                     /* ensure it does not return                           */
+	);
+	__builtin_unreachable();
+}
+
+#endif /* _NOLIBC_X86_64_CRT_H */
diff --git a/tools/include/nolibc/x86_64/arch.h b/tools/include/nolibc/x86_64/sys.h
similarity index 81%
rename from tools/include/nolibc/x86_64/arch.h
rename to tools/include/nolibc/x86_64/sys.h
index 602791c3461a..c761c97dccf8 100644
--- a/tools/include/nolibc/x86_64/arch.h
+++ b/tools/include/nolibc/x86_64/sys.h
@@ -1,13 +1,11 @@
 /* SPDX-License-Identifier: LGPL-2.1 OR MIT */
 /*
- * x86_64 specific definitions for NOLIBC
+ * x86_64 specific syscall declarations for NOLIBC
  * Copyright (C) 2017-2022 Willy Tarreau <w@1wt.eu>
  */
 
-#ifndef _NOLIBC_X86_64_ARCH_H
-#define _NOLIBC_X86_64_ARCH_H
-
-#include "compiler.h"
+#ifndef _NOLIBC_X86_64_SYS_H
+#define _NOLIBC_X86_64_SYS_H
 
 /* The struct returned by the stat() syscall, equivalent to stat64(). The
  * syscall returns 116 bytes and stops in the middle of __unused.
@@ -180,41 +178,4 @@ struct sys_stat_struct {
 	_ret;                                                                 \
 })
 
-char **environ __attribute__((weak));
-const unsigned long *_auxv __attribute__((weak));
-
-/* startup code */
-/*
- * x86-64 System V ABI mandates:
- * 1) %rsp must be 16-byte aligned right before the function call.
- * 2) The deepest stack frame should be zero (the %rbp).
- *
- */
-void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __no_stack_protector _start(void)
-{
-	__asm__ volatile (
-#ifdef _NOLIBC_STACKPROTECTOR
-		"call __stack_chk_init\n"   /* initialize stack protector                          */
-#endif
-		"pop %rdi\n"                /* argc   (first arg, %rdi)                            */
-		"mov %rsp, %rsi\n"          /* argv[] (second arg, %rsi)                           */
-		"lea 8(%rsi,%rdi,8),%rdx\n" /* then a NULL then envp (third arg, %rdx)             */
-		"mov %rdx, environ\n"       /* save environ                                        */
-		"xor %ebp, %ebp\n"          /* zero the stack frame                                */
-		"mov %rdx, %rax\n"          /* search for auxv (follows NULL after last env)       */
-		"0:\n"
-		"add $8, %rax\n"            /* search for auxv using rax, it follows the           */
-		"cmp -8(%rax), %rbp\n"      /* ... NULL after last env (rbp is zero here)          */
-		"jnz 0b\n"
-		"mov %rax, _auxv\n"         /* save it into _auxv                                  */
-		"and $-16, %rsp\n"          /* x86 ABI : esp must be 16-byte aligned before call   */
-		"call main\n"               /* main() returns the status code, we'll exit with it. */
-		"mov %eax, %edi\n"          /* retrieve exit code (32 bit)                         */
-		"mov $60, %eax\n"           /* NR_exit == 60                                       */
-		"syscall\n"                 /* really exit                                         */
-		"hlt\n"                     /* ensure it does not return                           */
-	);
-	__builtin_unreachable();
-}
-
-#endif /* _NOLIBC_X86_64_ARCH_H */
+#endif /* _NOLIBC_X86_64_SYS_H */
