[RFC PATCH v2 02/10] tools/nolibc: crt: Split _start_c() into stack-only and normal parts

Daniel Palmer posted 10 patches 4 days, 6 hours ago
[RFC PATCH v2 02/10] tools/nolibc: crt: Split _start_c() into stack-only and normal parts
Posted by Daniel Palmer 4 days, 6 hours ago
To prepare for nolibc programs being able to relocate themselves
we need to split _start_c() into two parts:

- One part that only uses the stack so there are no accesses via
  the GOT etc that isn't setup yet. Note that on m68k at least
  this also means forcing the stackprotector off because accessing
  the stack protector canary is done via the GOT.

- Another part that is called after we have done relocation so it
  is safe to access global variables etc that might use the GOT etc.

Signed-off-by: Daniel Palmer <daniel@thingy.jp>
---
 tools/include/nolibc/compiler.h |  6 ++++
 tools/include/nolibc/crt.h      | 57 ++++++++++++++++++---------------
 2 files changed, 38 insertions(+), 25 deletions(-)

diff --git a/tools/include/nolibc/compiler.h b/tools/include/nolibc/compiler.h
index 87090bbc53e0..3f403e54e4f4 100644
--- a/tools/include/nolibc/compiler.h
+++ b/tools/include/nolibc/compiler.h
@@ -47,4 +47,10 @@
 #  define __nolibc_fallthrough do { } while (0)
 #endif /* __nolibc_has_attribute(fallthrough) */
 
+#if __nolibc_has_feature(undefined_behavior_sanitizer)
+#  define __no_sanitize __attribute__((no_sanitize("function")))
+#else
+#  define __no_sanitize
+#endif
+
 #endif /* _NOLIBC_COMPILER_H */
diff --git a/tools/include/nolibc/crt.h b/tools/include/nolibc/crt.h
index d9262998dae9..fab042f1ff62 100644
--- a/tools/include/nolibc/crt.h
+++ b/tools/include/nolibc/crt.h
@@ -27,26 +27,45 @@ extern void (*const __init_array_end[])(int, char **, char**) __attribute__((wea
 extern void (*const __fini_array_start[])(void) __attribute__((weak));
 extern void (*const __fini_array_end[])(void) __attribute__((weak));
 
-void _start_c(long *sp);
-__attribute__((weak,used))
-#if __nolibc_has_feature(undefined_behavior_sanitizer)
-	__attribute__((no_sanitize("function")))
-#endif
-void _start_c(long *sp)
+void _start_c_global_data(long argc, char **argv, char **envp, const unsigned long *auxv);
+__attribute__((weak, used))
+void __no_sanitize __no_stack_protector _start_c_global_data(long argc, char **argv, char **envp, const unsigned long *auxv)
 {
-	long argc;
-	char **argv;
-	char **envp;
-	int exitcode;
 	void (* const *ctor_func)(int, char **, char **);
 	void (* const *dtor_func)(void);
-	const unsigned long *auxv;
 	/* silence potential warning: conflicting types for 'main' */
 	int _nolibc_main(int, char **, char **) __asm__ ("main");
+	int exitcode;
 
 	/* initialize stack protector */
 	__stack_chk_init();
 
+	environ = envp;
+	_auxv = auxv;
+
+	for (ctor_func = __preinit_array_start; ctor_func < __preinit_array_end; ctor_func++)
+		(*ctor_func)(argc, argv, envp);
+	for (ctor_func = __init_array_start; ctor_func < __init_array_end; ctor_func++)
+		(*ctor_func)(argc, argv, envp);
+
+	/* go to application */
+	exitcode = _nolibc_main(argc, argv, envp);
+
+	for (dtor_func = __fini_array_end; dtor_func > __fini_array_start;)
+		(*--dtor_func)();
+
+	exit(exitcode);
+}
+
+void _start_c(long *sp);
+__attribute__((weak, used))
+void __no_sanitize __no_stack_protector _start_c(long *sp)
+{
+	long argc;
+	char **argv;
+	char **envp;
+	const unsigned long *auxv;
+
 	/*
 	 * sp  :    argc          <-- argument count, required by main()
 	 * argv:    argv[0]       <-- argument vector, required by main()
@@ -69,25 +88,13 @@ void _start_c(long *sp)
 	argv = (void *)(sp + 1);
 
 	/* find environ */
-	environ = envp = argv + argc + 1;
+	envp = argv + argc + 1;
 
 	/* find _auxv */
 	for (auxv = (void *)envp; *auxv++;)
 		;
-	_auxv = auxv;
 
-	for (ctor_func = __preinit_array_start; ctor_func < __preinit_array_end; ctor_func++)
-		(*ctor_func)(argc, argv, envp);
-	for (ctor_func = __init_array_start; ctor_func < __init_array_end; ctor_func++)
-		(*ctor_func)(argc, argv, envp);
-
-	/* go to application */
-	exitcode = _nolibc_main(argc, argv, envp);
-
-	for (dtor_func = __fini_array_end; dtor_func > __fini_array_start;)
-		(*--dtor_func)();
-
-	exit(exitcode);
+	_start_c_global_data(argc, argv, envp, auxv);
 }
 
 #endif /* NOLIBC_NO_RUNTIME */
-- 
2.51.0
Re: [RFC PATCH v2 02/10] tools/nolibc: crt: Split _start_c() into stack-only and normal parts
Posted by Willy Tarreau 1 day, 3 hours ago
On Wed, Feb 04, 2026 at 09:45:34PM +0900, Daniel Palmer wrote:
> To prepare for nolibc programs being able to relocate themselves
> we need to split _start_c() into two parts:
> 
> - One part that only uses the stack so there are no accesses via
>   the GOT etc that isn't setup yet. Note that on m68k at least
>   this also means forcing the stackprotector off because accessing
>   the stack protector canary is done via the GOT.
> 
> - Another part that is called after we have done relocation so it
>   is safe to access global variables etc that might use the GOT etc.
> 
> Signed-off-by: Daniel Palmer <daniel@thingy.jp>
> ---
>  tools/include/nolibc/compiler.h |  6 ++++
>  tools/include/nolibc/crt.h      | 57 ++++++++++++++++++---------------
>  2 files changed, 38 insertions(+), 25 deletions(-)
> 
> diff --git a/tools/include/nolibc/compiler.h b/tools/include/nolibc/compiler.h
> index 87090bbc53e0..3f403e54e4f4 100644
> --- a/tools/include/nolibc/compiler.h
> +++ b/tools/include/nolibc/compiler.h
> @@ -47,4 +47,10 @@
>  #  define __nolibc_fallthrough do { } while (0)
>  #endif /* __nolibc_has_attribute(fallthrough) */
>  
> +#if __nolibc_has_feature(undefined_behavior_sanitizer)
> +#  define __no_sanitize __attribute__((no_sanitize("function")))
> +#else
> +#  define __no_sanitize
> +#endif

I'm starting to feel uncomfortable with the addition of new __no_foo
stuff, which doesn't have the "nolibc" prefix, risking to conflict with
userland code. I think we'll have to go through a cleanup patch at some
point for __no_sanitize and __no_stack_protector.

So probably in order to reduce the technical debt it would be nice to
to prepend __nolibc in front of this new internal macro. Maybe this part
of the patch should be a separate cleanup patch by the way, as future
patches might depend on it.

Willy
Re: [RFC PATCH v2 02/10] tools/nolibc: crt: Split _start_c() into stack-only and normal parts
Posted by Daniel Palmer 17 hours ago
Hi Willy,

On Sun, 8 Feb 2026 at 00:45, Willy Tarreau <w@1wt.eu> wrote:
> > +#if __nolibc_has_feature(undefined_behavior_sanitizer)
> > +#  define __no_sanitize __attribute__((no_sanitize("function")))
> > +#else
> > +#  define __no_sanitize
> > +#endif
>
> I'm starting to feel uncomfortable with the addition of new __no_foo
> stuff, which doesn't have the "nolibc" prefix, risking to conflict with
> userland code. I think we'll have to go through a cleanup patch at some
> point for __no_sanitize and __no_stack_protector.
>
> So probably in order to reduce the technical debt it would be nice to
> to prepend __nolibc in front of this new internal macro. Maybe this part
> of the patch should be a separate cleanup patch by the way, as future
> patches might depend on it.

Since this relocation thing will still take more time I will create a
set of patches just to add the prefix to __no_stack_protector, add
__nolibc_no_sanitize and use it in crt.h to send now instead of as
part of this.

Cheers,

Daniel