[v9,3/5] Fix assert during static startup (BZ 33326)

Message ID 20260529162818.3749464-4-adhemerval.zanella@linaro.org (mailing list archive)
State Under Review
Delegated to: Florian Weimer
Headers
Series elf: Allow RPATH/RUNPATH for static-pie |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed

Commit Message

Adhemerval Zanella May 29, 2026, 4:26 p.m. UTC
  The BZ#33326 testcase triggers an assertion during process startup,
which results in a segmentation fault instead of an error message
and process termination with a SIGABRT.  The assert issues
__libc_message_impl, which in turn might call string functions
depending on the ABI (strchrnul, strlen, memcpy/mempcpy), system
calls (writev and mmap), and finally the abort call.

Since each function may be called during process startup, before
self-relocation and/or the thread pointer being set up, the
functions should be built without stack protection.

The dl-symbol-redir-ifunc.h is also expanded to cover
strlen/memcpy/mempcpy/strchrnul on multiple architectures that implement
ifunc.

On i386, syscalls should not use the vDSO ("call *%gs:SYSINFO_OFFSET")
during program startup because thread pointer is not yet initialized.  This
requires __raise_direct, _dl_writev, and _dl_mmap to use I386_USE_SYSENTER.
To avoid possible sysdep.h I386_USE_SYSENTER redefinition, all
implementations are done on their own translation unit.

The s390x requires not calling libgcc for the generic strchrnul-c (via
ctz/clz macros) due to a libgcc issue: the __clzdi2 builtin is not built
against a hidden reference to __clz_tab, which creates a GOT reference
for static-pie (and it cannot be called before self-relocation).

Creating a test case is challenging. For static-pie, the assert is only
called for ill-formed ELF files on elf_get_dynamic_info and by some targets
on ELF_DYNAMIC_RELOCATE (although not all targets use assert in their
dl-machine.h).  Some targets also issue __libc_fatal on ARCH_SETUP_IREL,
but also only for ill-formatted ELF files.

The test employs a different strategy and overrides the __tunables_init
symbol, which is invoked immediately before self-relocation and TLS setup.
The test is built with -Wl,-z,muldefs to avoid linker issues.

I checked on aarch64, x86_64, i686, s390x (qemu), sparc (qemu),
mips64el (qemu), armhf, riscv, and powerpc.
---
 assert/Makefile                               |  5 +++
 elf/Makefile                                  | 18 +++++++++
 elf/tst-assert-startup-static.c               | 40 +++++++++++++++++++
 libio/Makefile                                |  5 +++
 stdlib/Makefile                               |  5 +++
 string/Makefile                               |  1 +
 .../aarch64/multiarch/dl-symbol-redir-ifunc.h |  1 +
 sysdeps/aarch64/multiarch/memcpy_generic.S    |  4 ++
 .../armv7/multiarch/dl-symbol-redir-ifunc.h   | 27 +++++++++++++
 sysdeps/generic/dl-mmap.h                     | 34 ++++++++++++++++
 .../lp64/multiarch/dl-symbol-redir-ifunc.h    |  2 +
 sysdeps/posix/libc_fatal.c                    | 12 ++++--
 .../powerpc32/power4/multiarch/Makefile       |  5 +++
 .../be/multiarch/dl-symbol-redir-ifunc.h      | 27 +++++++++++++
 .../le/multiarch/dl-symbol-redir-ifunc.h      |  1 +
 sysdeps/powerpc/powerpc64/multiarch/Makefile  |  1 +
 sysdeps/s390/Makefile                         |  5 +++
 .../s390/multiarch/dl-symbol-redir-ifunc.h    |  4 ++
 sysdeps/s390/string-bitops.h                  | 27 +++++++++++++
 .../sparcv9/multiarch/dl-symbol-redir-ifunc.h |  3 ++
 .../sparc64/multiarch/dl-symbol-redir-ifunc.h |  3 ++
 sysdeps/unix/sysv/linux/Makefile              | 12 ++++++
 sysdeps/unix/sysv/linux/i386/Makefile         | 14 +++++++
 sysdeps/unix/sysv/linux/i386/dl-mmap.c        | 36 +++++++++++++++++
 sysdeps/unix/sysv/linux/i386/dl-mmap.h        | 27 +++++++++++++
 sysdeps/unix/sysv/linux/i386/dl-writev.c      | 32 +++++++++++++++
 sysdeps/unix/sysv/linux/i386/dl-writev.h      | 15 ++++---
 sysdeps/unix/sysv/linux/i386/raise_direct.c   | 26 ++++++++++++
 .../unix/sysv/linux/riscv/multiarch/Makefile  |  8 ++++
 .../x86_64/multiarch/dl-symbol-redir-ifunc.h  | 22 ++++++++++
 30 files changed, 414 insertions(+), 8 deletions(-)
 create mode 100644 elf/tst-assert-startup-static.c
 create mode 100644 sysdeps/arm/armv7/multiarch/dl-symbol-redir-ifunc.h
 create mode 100644 sysdeps/generic/dl-mmap.h
 create mode 100644 sysdeps/powerpc/powerpc64/be/multiarch/dl-symbol-redir-ifunc.h
 create mode 100644 sysdeps/s390/string-bitops.h
 create mode 100644 sysdeps/unix/sysv/linux/i386/dl-mmap.c
 create mode 100644 sysdeps/unix/sysv/linux/i386/dl-mmap.h
 create mode 100644 sysdeps/unix/sysv/linux/i386/dl-writev.c
 create mode 100644 sysdeps/unix/sysv/linux/i386/raise_direct.c
  

Comments

Florian Weimer June 8, 2026, 10:46 a.m. UTC | #1
* Adhemerval Zanella:

I think the general issue is not right: During startup, we should not
use mmap to allocate the backing store.  We can preserve the assert
message and file name by copying them to on-stack buffer.  As no user
code should be running at this point, we do not need to be prepared for
arbitrarily-long assert messages (but we still should have a length
check, of course).

Can we redirect to a different assert implementation during early
startup?

I'm not sure this actually works for HIDDEN_VAR_NEEDS_DYNAMIC_RELOC
because of remaining global variable accesses in __assert_fail, but
maybe we do not support static PIE for them anyway?

> diff --git a/elf/tst-assert-startup-static.c b/elf/tst-assert-startup-static.c
> new file mode 100644
> index 00000000000..9fd79a0e243
> --- /dev/null
> +++ b/elf/tst-assert-startup-static.c
> @@ -0,0 +1,40 @@
> +/* Check if assert work during program startup.

Typo: work[s]

> diff --git a/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h
> index 1cd204c2f69..4379770c314 100644
> --- a/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h
> +++ b/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h
> @@ -20,8 +20,10 @@
>  #define _DL_IFUNC_GENERIC_H
>  
>  #ifndef SHARED
> +asm ("memcpy = __memcpy_aligned");
>  asm ("memset = __memset_aligned");
>  asm ("memcmp = __memcmp_aligned");
> +asm ("__strchrnul = __strchrnul_aligned");
>  asm ("strlen = __strlen_aligned");
>  asm ("memcpy = __memcpy_unaligned");
>  asm ("memmove = __memmove_unaligned");

Now memcpy is redirected twice.  The existing unconsistency between the
*_aligned and *_unaligned variants is a bit jarring, but I don't know
enough about the port to resolve it.

> diff --git a/sysdeps/powerpc/powerpc64/be/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/powerpc/powerpc64/be/multiarch/dl-symbol-redir-ifunc.h
> new file mode 100644
> index 00000000000..560f57366b2
> --- /dev/null
> +++ b/sysdeps/powerpc/powerpc64/be/multiarch/dl-symbol-redir-ifunc.h
> @@ -0,0 +1,27 @@
> +/* Symbol rediretion for loader/static initialization code.

Typo: Symbol redire[c]tion

This occurs multiple times.

> diff --git a/sysdeps/s390/string-bitops.h b/sysdeps/s390/string-bitops.h
> new file mode 100644
> index 00000000000..f660d019c77
> --- /dev/null
> +++ b/sysdeps/s390/string-bitops.h
> @@ -0,0 +1,27 @@
> +/* Zero byte detection, define whether to use stdbit.h

Should say “s390 version”.

> +/* s390x support static-pie and the libgcc implementation for
> +   __builtin_clzl/__builtin_ctzl might access extern data that is not marked
> +   as hidden, which creates additiona GOT access that is used before
> +   self-relocation.  */

Typo: additiona[l]

> +#if __ARCH__ > 6
> +# define HAVE_BITOPTS_WORKING 1
> +#else
> +# define HAVE_BITOPTS_WORKING 0
> +#endif

Do we really support __ARCH__ == 6 and earlier?

(There's an existing issue: The file name and the macro name are
inconsistent.)

> diff --git a/sysdeps/unix/sysv/linux/i386/dl-mmap.c b/sysdeps/unix/sysv/linux/i386/dl-mmap.c
> new file mode 100644
> index 00000000000..ab6f96300fb
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/i386/dl-mmap.c
> @@ -0,0 +1,36 @@
> +/* mmap wrapper for dynamic loader.  Linux/i386 version.

> +/* This mmap call is used to allocate some memory to backup assert() messages
> +   before TLS setup is done, so it can not use "call *%gs:SYSINFO_OFFSET"
> +   during startup in static PIE.  */

Typo: can[]not (multiple times in the patch)

> diff --git a/sysdeps/unix/sysv/linux/i386/dl-mmap.h b/sysdeps/unix/sysv/linux/i386/dl-mmap.h
> new file mode 100644
> index 00000000000..291f92ee14f
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/i386/dl-mmap.h
> @@ -0,0 +1,27 @@
> +/* mmap wrapper for dynamic loader.

Should say: i386 version

> diff --git a/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile b/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
> index 2fdd4af3eb7..a10dd30d12b 100644
> --- a/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
> +++ b/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
> @@ -24,8 +24,16 @@ sysdep_routines += \
>    strncmp-vector \
>    # sysdep_routines
>  
> +# Called during static library initialization, so turn stack-protection
> +# off for non-shared builds.
> +CFLAGS-memset-generic.o = $(no-stack-protector)
> +CFLAGS-memset-generic.op = $(no-stack-protector)
> +CFLAGS-memcpy-generic.o = $(no-stack-protector)
> +CFLAGS-memcpy-generic.op = $(no-stack-protector)
> +
>  CFLAGS-memcpy_noalignment.c += -mno-strict-align
>  # Called during static initialization
> +CFLAGS-strlen-generic.c += $(no-stack-protector)
>  CFLAGS-memset-generic.c += $(no-stack-protector)
>  CFLAGS-memcpy-generic.c += $(no-stack-protector)
>  endif

Isn't the first block of flags changes redundant given the existing
CFLAGS-memset-generic.c setting?

> diff --git a/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
> index 1f3ca20307c..4d9cc5dd914 100644
> --- a/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
> +++ b/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
> @@ -73,6 +73,28 @@ asm ("memmove = " HAVE_MEMMOVE_IFUNC_GENERIC);
>  asm ("mempcpy = " HAVE_MEMPCPY_IFUNC_GENERIC);
>  asm ("__mempcpy = " HAVE_MEMPCPY_IFUNC_GENERIC);
>  
> +
> +#if MINIMUM_X86_ISA_LEVEL >= 4

Spurious blank line.

Thanks,
Florian
  
Adhemerval Zanella June 8, 2026, 12:14 p.m. UTC | #2
On 08/06/26 07:46, Florian Weimer wrote:
> * Adhemerval Zanella:
> 
> I think the general issue is not right: During startup, we should not
> use mmap to allocate the backing store.  We can preserve the assert
> message and file name by copying them to on-stack buffer.  As no user
> code should be running at this point, we do not need to be prepared for
> arbitrarily-long assert messages (but we still should have a length
> check, of course).
> 
> Can we redirect to a different assert implementation during early
> startup?

I think I can reinstate the assert wrapper [1]. I kept the assert used 
simplify the the implementation and use the same code path.  We can use
a stack allocated buffer and cap the message string, for glibc own usage we
would know when we need to increase it.

But it also raises the question whether we do copy the buffer in this case,
this assert will be only used during process startup and it would be triggered
in cases that can be considered glibc issues.

[1] https://sourceware.org/pipermail/libc-alpha/2026-March/176039.html

> 
> I'm not sure this actually works for HIDDEN_VAR_NEEDS_DYNAMIC_RELOC
> because of remaining global variable accesses in __assert_fail, but
> maybe we do not support static PIE for them anyway?
> 
>> diff --git a/elf/tst-assert-startup-static.c b/elf/tst-assert-startup-static.c
>> new file mode 100644
>> index 00000000000..9fd79a0e243
>> --- /dev/null
>> +++ b/elf/tst-assert-startup-static.c
>> @@ -0,0 +1,40 @@
>> +/* Check if assert work during program startup.
> 
> Typo: work[s]

Ack.

> 
>> diff --git a/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h
>> index 1cd204c2f69..4379770c314 100644
>> --- a/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h
>> +++ b/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h
>> @@ -20,8 +20,10 @@
>>  #define _DL_IFUNC_GENERIC_H
>>  
>>  #ifndef SHARED
>> +asm ("memcpy = __memcpy_aligned");
>>  asm ("memset = __memset_aligned");
>>  asm ("memcmp = __memcmp_aligned");
>> +asm ("__strchrnul = __strchrnul_aligned");
>>  asm ("strlen = __strlen_aligned");
>>  asm ("memcpy = __memcpy_unaligned");
>>  asm ("memmove = __memmove_unaligned");
> 
> Now memcpy is redirected twice.  The existing unconsistency between the
> *_aligned and *_unaligned variants is a bit jarring, but I don't know
> enough about the port to resolve it.

Ack, I will remove the memcpy. We can remove the unconsistency later, it
is a performance improvement.

> 
>> diff --git a/sysdeps/powerpc/powerpc64/be/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/powerpc/powerpc64/be/multiarch/dl-symbol-redir-ifunc.h
>> new file mode 100644
>> index 00000000000..560f57366b2
>> --- /dev/null
>> +++ b/sysdeps/powerpc/powerpc64/be/multiarch/dl-symbol-redir-ifunc.h
>> @@ -0,0 +1,27 @@
>> +/* Symbol rediretion for loader/static initialization code.
> 
> Typo: Symbol redire[c]tion
> 
> This occurs multiple times.

Ack.

> 
>> diff --git a/sysdeps/s390/string-bitops.h b/sysdeps/s390/string-bitops.h
>> new file mode 100644
>> index 00000000000..f660d019c77
>> --- /dev/null
>> +++ b/sysdeps/s390/string-bitops.h
>> @@ -0,0 +1,27 @@
>> +/* Zero byte detection, define whether to use stdbit.h
> 
> Should say “s390 version”.
> 

Ack.

>> +/* s390x support static-pie and the libgcc implementation for
>> +   __builtin_clzl/__builtin_ctzl might access extern data that is not marked
>> +   as hidden, which creates additiona GOT access that is used before
>> +   self-relocation.  */
> 
> Typo: additiona[l]

Ack.

> 
>> +#if __ARCH__ > 6
>> +# define HAVE_BITOPTS_WORKING 1
>> +#else
>> +# define HAVE_BITOPTS_WORKING 0
>> +#endif
> 
> Do we really support __ARCH__ == 6 and earlier?
> 
> (There's an existing issue: The file name and the macro name are
> inconsistent.)

It is still used with build-many-glibc.py toolchail.

> 
>> diff --git a/sysdeps/unix/sysv/linux/i386/dl-mmap.c b/sysdeps/unix/sysv/linux/i386/dl-mmap.c
>> new file mode 100644
>> index 00000000000..ab6f96300fb
>> --- /dev/null
>> +++ b/sysdeps/unix/sysv/linux/i386/dl-mmap.c
>> @@ -0,0 +1,36 @@
>> +/* mmap wrapper for dynamic loader.  Linux/i386 version.
> 
>> +/* This mmap call is used to allocate some memory to backup assert() messages
>> +   before TLS setup is done, so it can not use "call *%gs:SYSINFO_OFFSET"
>> +   during startup in static PIE.  */
> 
> Typo: can[]not (multiple times in the patch)

Ack.

> 
>> diff --git a/sysdeps/unix/sysv/linux/i386/dl-mmap.h b/sysdeps/unix/sysv/linux/i386/dl-mmap.h
>> new file mode 100644
>> index 00000000000..291f92ee14f
>> --- /dev/null
>> +++ b/sysdeps/unix/sysv/linux/i386/dl-mmap.h
>> @@ -0,0 +1,27 @@
>> +/* mmap wrapper for dynamic loader.
> 
> Should say: i386 version

Ack.

> 
>> diff --git a/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile b/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
>> index 2fdd4af3eb7..a10dd30d12b 100644
>> --- a/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
>> +++ b/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
>> @@ -24,8 +24,16 @@ sysdep_routines += \
>>    strncmp-vector \
>>    # sysdep_routines
>>  
>> +# Called during static library initialization, so turn stack-protection
>> +# off for non-shared builds.
>> +CFLAGS-memset-generic.o = $(no-stack-protector)
>> +CFLAGS-memset-generic.op = $(no-stack-protector)
>> +CFLAGS-memcpy-generic.o = $(no-stack-protector)
>> +CFLAGS-memcpy-generic.op = $(no-stack-protector)
>> +
>>  CFLAGS-memcpy_noalignment.c += -mno-strict-align
>>  # Called during static initialization
>> +CFLAGS-strlen-generic.c += $(no-stack-protector)
>>  CFLAGS-memset-generic.c += $(no-stack-protector)
>>  CFLAGS-memcpy-generic.c += $(no-stack-protector)
>>  endif
> 
> Isn't the first block of flags changes redundant given the existing
> CFLAGS-memset-generic.c setting?

Yes, I will remove then.

> 
>> diff --git a/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
>> index 1f3ca20307c..4d9cc5dd914 100644
>> --- a/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
>> +++ b/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
>> @@ -73,6 +73,28 @@ asm ("memmove = " HAVE_MEMMOVE_IFUNC_GENERIC);
>>  asm ("mempcpy = " HAVE_MEMPCPY_IFUNC_GENERIC);
>>  asm ("__mempcpy = " HAVE_MEMPCPY_IFUNC_GENERIC);
>>  
>> +
>> +#if MINIMUM_X86_ISA_LEVEL >= 4
> 
> Spurious blank line.
> 

Ack.

> Thanks,
> Florian
>
  
Adhemerval Zanella June 8, 2026, 2:26 p.m. UTC | #3
On 08/06/26 09:14, Adhemerval Zanella Netto wrote:
> 
> 
> On 08/06/26 07:46, Florian Weimer wrote:
>> * Adhemerval Zanella:
>>
>> I think the general issue is not right: During startup, we should not
>> use mmap to allocate the backing store.  We can preserve the assert
>> message and file name by copying them to on-stack buffer.  As no user
>> code should be running at this point, we do not need to be prepared for
>> arbitrarily-long assert messages (but we still should have a length
>> check, of course).
>>
>> Can we redirect to a different assert implementation during early
>> startup?
> 
> I think I can reinstate the assert wrapper [1]. I kept the assert used 
> simplify the the implementation and use the same code path.  We can use
> a stack allocated buffer and cap the message string, for glibc own usage we
> would know when we need to increase it.
> 
> But it also raises the question whether we do copy the buffer in this case,
> this assert will be only used during process startup and it would be triggered
> in cases that can be considered glibc issues.
> 
> [1] https://sourceware.org/pipermail/libc-alpha/2026-March/176039.html
It turned out that for the rtld it is already done by elf/dl-minimal.c
__assert_fail/__assert_perror_fail which only calls _dl_fatal_printf instead
of __libc_assert_fail (which in turn calls __libc_message_impl).

Now changing this to *static-pie* means making static-pie act more like
rtld and less lime a static binary: we will need to add some logic on
the libc __libc_message_impl to handle static-pie and/or adding some
mechanism to override assert (similar how it is done for rtld). I don't
think either worth the trouble.
  
Adhemerval Zanella June 11, 2026, 6:39 p.m. UTC | #4
On 08/06/26 11:26, Adhemerval Zanella Netto wrote:
> 
> 
> On 08/06/26 09:14, Adhemerval Zanella Netto wrote:
>>
>>
>> On 08/06/26 07:46, Florian Weimer wrote:
>>> * Adhemerval Zanella:
>>>
>>> I think the general issue is not right: During startup, we should not
>>> use mmap to allocate the backing store.  We can preserve the assert
>>> message and file name by copying them to on-stack buffer.  As no user
>>> code should be running at this point, we do not need to be prepared for
>>> arbitrarily-long assert messages (but we still should have a length
>>> check, of course).
>>>
>>> Can we redirect to a different assert implementation during early
>>> startup?
>>
>> I think I can reinstate the assert wrapper [1]. I kept the assert used 
>> simplify the the implementation and use the same code path.  We can use
>> a stack allocated buffer and cap the message string, for glibc own usage we
>> would know when we need to increase it.
>>
>> But it also raises the question whether we do copy the buffer in this case,
>> this assert will be only used during process startup and it would be triggered
>> in cases that can be considered glibc issues.
>>
>> [1] https://sourceware.org/pipermail/libc-alpha/2026-March/176039.html
> It turned out that for the rtld it is already done by elf/dl-minimal.c
> __assert_fail/__assert_perror_fail which only calls _dl_fatal_printf instead
> of __libc_assert_fail (which in turn calls __libc_message_impl).
> 
> Now changing this to *static-pie* means making static-pie act more like
> rtld and less lime a static binary: we will need to add some logic on
> the libc __libc_message_impl to handle static-pie and/or adding some
> mechanism to override assert (similar how it is done for rtld). I don't
> think either worth the trouble.

I am planning to send a new version with your remarks fixes, do you still have
reservations for this assert on static-pie Florian? This patch does not really
change any semantic, loader still uses the minimal implementation.
  

Patch

diff --git a/assert/Makefile b/assert/Makefile
index 4c253a344ae..541929c3b67 100644
--- a/assert/Makefile
+++ b/assert/Makefile
@@ -33,6 +33,11 @@  routines := \
   assert-perr \
   # routines
 
+# Called during static library initialization, so turn stack-protection
+# off for non-shared builds.
+CFLAGS-__libc_assert_fail.o = $(no-stack-protector)
+CFLAGS-__libc_assert_fail.op = $(no-stack-protector)
+
 tests := \
   test-assert \
   test-assert-2 \
diff --git a/elf/Makefile b/elf/Makefile
index a58a902451c..0fed0f270fc 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -278,6 +278,7 @@  tests-static-normal := \
   # tests-static-normal
 
 tests-static-internal := \
+  tst-assert-startup-static \
   tst-dl-printf-static \
   tst-dl_find_object-static \
   tst-env-setuid-tunables \
@@ -296,6 +297,12 @@  tests-static-internal += \
   # tests-static-internal
 endif
 
+ifeq (yes,$(run-built-tests))
+tests-special += \
+  $(objpfx)tst-assert-startup-static.out \
+  # tests-special
+endif
+
 CRT-tst-tls1-static-non-pie := $(csu-objpfx)crt1.o
 tst-tls1-static-non-pie-no-pie = yes
 CRT-tst-ifunc-resolver-protector-static-non-pie := $(csu-objpfx)crt1.o
@@ -3670,3 +3677,14 @@  $(objpfx)tst-dl-debug-exclude.out: tst-dl-debug-exclude.sh \
 		 $(objpfx)tst-recursive-tls > $@; \
 	$(evaluate-test)
 endif
+
+CFLAGS-tst-assert-startup-static.c += $(no-stack-protector)
+LDFLAGS-tst-assert-startup-static = -Wl,-z,muldefs
+
+$(objpfx)tst-assert-startup-static.out: $(objpfx)tst-assert-startup-static
+	$(test-program-cmd-before-env) \
+		$(run-program-env) \
+		$< > $@ 2>&1; echo "status: $$?" >> $@; \
+	grep -q 'Fatal glibc error: tst-assert-startup-static' $@ \
+	  && grep -q '^status: 134$$' $@; \
+	  $(evaluate-test)
diff --git a/elf/tst-assert-startup-static.c b/elf/tst-assert-startup-static.c
new file mode 100644
index 00000000000..9fd79a0e243
--- /dev/null
+++ b/elf/tst-assert-startup-static.c
@@ -0,0 +1,40 @@ 
+/* Check if assert work during program startup.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdlib.h>
+#include <assert.h>
+
+/* The __tunables_init is called just before self-relocation and TLS setup,
+   and the __libc_assert_fail is used internally for assert() calls.  */
+extern _Noreturn __typeof (__assert_fail) __libc_assert_fail;
+
+void __tunables_init (char **env)
+{
+/* The assert called by the loader/startup issues __libc_assert_fail instead
+   of __[]assert[_fail], and __libc_assert_fail does not perform the
+   translation (which would require additional handling to be called at this
+   point, like disable stack protection).  So issue the internal routine
+   directly, instead of using assert here.  */
+  __libc_assert_fail ("error", __FILE__, __LINE__, __func__);
+}
+
+int main (int argc, char *argv[])
+{
+  /* Fail with a different error code than abort.  */
+  exit (EXIT_FAILURE);
+}
diff --git a/libio/Makefile b/libio/Makefile
index 616107ee105..09b1b907dc2 100644
--- a/libio/Makefile
+++ b/libio/Makefile
@@ -195,6 +195,11 @@  endif
 
 CPPFLAGS += $(libio-mtsafe)
 
+# Called during static library initialization, so turn stack-protection
+# off for non-shared builds.
+CFLAGS-libc_fatal.o = $(no-stack-protector)
+CFLAGS-libc_fatal.op = $(no-stack-protector)
+
 # Support for exception handling.
 CFLAGS-fileops.c += -fexceptions
 CFLAGS-fputc.c += -fexceptions
diff --git a/stdlib/Makefile b/stdlib/Makefile
index addf7dc99ff..18c8b21910d 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -530,6 +530,11 @@  generated += \
   tst-putenvmod.so \
   # generated
 
+# Called during static library initialization, so turn stack-protection
+# off for non-shared builds.
+CFLAGS-abort.o = $(no-stack-protector)
+CFLAGS-abort.op = $(no-stack-protector)
+
 CFLAGS-bsearch.c += $(uses-callbacks)
 CFLAGS-qsort.c += $(uses-callbacks)
 CFLAGS-system.c += -fexceptions
diff --git a/string/Makefile b/string/Makefile
index aa0b0c2f57a..969726cccb0 100644
--- a/string/Makefile
+++ b/string/Makefile
@@ -287,6 +287,7 @@  CFLAGS-wordcopy.c += $(no-stack-protector)
 CFLAGS-strncmp.c += $(no-stack-protector)
 CFLAGS-memset.c += $(no-stack-protector)
 CFLAGS-strlen.c += $(no-stack-protector)
+CFLAGS-strchrnul.c += $(no-stack-protector)
 
 ifeq ($(run-built-tests),yes)
 $(objpfx)tst-svc-cmp.out: tst-svc.expect $(objpfx)tst-svc.out
diff --git a/sysdeps/aarch64/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/aarch64/multiarch/dl-symbol-redir-ifunc.h
index 26fcd1977ea..ddfd6bd45d8 100644
--- a/sysdeps/aarch64/multiarch/dl-symbol-redir-ifunc.h
+++ b/sysdeps/aarch64/multiarch/dl-symbol-redir-ifunc.h
@@ -19,6 +19,7 @@ 
 #ifndef _DL_IFUNC_GENERIC_H
 #define _DL_IFUNC_GENERIC_H
 
+asm ("memcpy = __memcpy_generic");
 asm ("memset = __memset_generic");
 asm ("strlen = __strlen_generic");
 #ifndef SHARED
diff --git a/sysdeps/aarch64/multiarch/memcpy_generic.S b/sysdeps/aarch64/multiarch/memcpy_generic.S
index c6d09081f47..d6222683056 100644
--- a/sysdeps/aarch64/multiarch/memcpy_generic.S
+++ b/sysdeps/aarch64/multiarch/memcpy_generic.S
@@ -42,3 +42,7 @@ 
 #endif
 
 #include "../memcpy.S"
+
+#if IS_IN (rtld)
+strong_alias (memcpy, __memcpy_generic)
+#endif
diff --git a/sysdeps/arm/armv7/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/arm/armv7/multiarch/dl-symbol-redir-ifunc.h
new file mode 100644
index 00000000000..4a6c81aa8dc
--- /dev/null
+++ b/sysdeps/arm/armv7/multiarch/dl-symbol-redir-ifunc.h
@@ -0,0 +1,27 @@ 
+/* Symbol redirection for loader/static initialization code.  Linux/ARM
+   version.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _DL_IFUNC_GENERIC_H
+#define _DL_IFUNC_GENERIC_H
+
+#ifndef __ARM_NEON__
+asm ("memcpy = __memcpy_vfp");
+#endif
+
+#endif
diff --git a/sysdeps/generic/dl-mmap.h b/sysdeps/generic/dl-mmap.h
new file mode 100644
index 00000000000..f786be0930c
--- /dev/null
+++ b/sysdeps/generic/dl-mmap.h
@@ -0,0 +1,34 @@ 
+/* mmap wrapper for dynamic loader.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _DL_MMAP_H
+#define _DL_MMAP_H
+
+#include <sys/mman.h>
+
+/* This mmap call is used to allocate some memory to backup assert() messages
+   before TLS setup is done (which setup the thread pointer used by some ABIs
+   to issues syscalls).  */
+
+static inline void *
+_dl_mmap (void *addr, size_t len, int prot, int flags)
+{
+  return __mmap (addr, len, prot, flags, -1, 0);
+}
+
+#endif
diff --git a/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h
index 1cd204c2f69..4379770c314 100644
--- a/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h
+++ b/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h
@@ -20,8 +20,10 @@ 
 #define _DL_IFUNC_GENERIC_H
 
 #ifndef SHARED
+asm ("memcpy = __memcpy_aligned");
 asm ("memset = __memset_aligned");
 asm ("memcmp = __memcmp_aligned");
+asm ("__strchrnul = __strchrnul_aligned");
 asm ("strlen = __strlen_aligned");
 asm ("memcpy = __memcpy_unaligned");
 asm ("memmove = __memmove_unaligned");
diff --git a/sysdeps/posix/libc_fatal.c b/sysdeps/posix/libc_fatal.c
index 3f0e302b5ea..1ff20d4feb7 100644
--- a/sysdeps/posix/libc_fatal.c
+++ b/sysdeps/posix/libc_fatal.c
@@ -16,7 +16,12 @@ 
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+/* Mark symbols hidden in static PIE for early self relocation to work.  */
+#if BUILD_PIE_DEFAULT
+# pragma GCC visibility push(hidden)
+#endif
 #include <dl-writev.h>
+#include <dl-mmap.h>
 #include <assert.h>
 #include <ldsodefs.h>
 #include <setvmaname.h>
@@ -24,6 +29,7 @@ 
 #include <stdio.h>
 #include <sys/uio.h>
 #include <unistd.h>
+#include <dl-symbol-redir-ifunc.h>
 
 #ifdef FATAL_PREPARE_INCLUDE
 #include FATAL_PREPARE_INCLUDE
@@ -113,9 +119,9 @@  __libc_message_impl (const char *vma_name, const char *fmt, ...)
 
       total = ALIGN_UP (total + sizeof (struct abort_msg_s) + 1,
 			GLRO(dl_pagesize));
-      struct abort_msg_s *buf = __mmap (NULL, total,
-					PROT_READ | PROT_WRITE,
-					MAP_ANON | MAP_PRIVATE, -1, 0);
+      struct abort_msg_s *buf = _dl_mmap (NULL, total,
+					  PROT_READ | PROT_WRITE,
+					  MAP_ANON | MAP_PRIVATE);
       if (__glibc_likely (buf != MAP_FAILED))
 	{
 	  buf->size = total;
diff --git a/sysdeps/powerpc/powerpc32/power4/multiarch/Makefile b/sysdeps/powerpc/powerpc32/power4/multiarch/Makefile
index 3a49b855ca5..60ba2e50d28 100644
--- a/sysdeps/powerpc/powerpc32/power4/multiarch/Makefile
+++ b/sysdeps/powerpc/powerpc32/power4/multiarch/Makefile
@@ -11,4 +11,9 @@  sysdep_routines += memcpy-power7 memcpy-a2 memcpy-power6 memcpy-cell \
 		   strchr-power7 strchr-ppc32 \
 		   wordcopy-power7 wordcopy-ppc32 \
 		   memmove-power7 memmove-ppc
+
+# Called during static library initialization, so turn stack-protection
+# off for non-shared builds.
+CFLAGS-strchrnul-ppc32.o = $(no-stack-protector)
+CFLAGS-strchrnul-ppc32.op = $(no-stack-protector)
 endif
diff --git a/sysdeps/powerpc/powerpc64/be/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/powerpc/powerpc64/be/multiarch/dl-symbol-redir-ifunc.h
new file mode 100644
index 00000000000..560f57366b2
--- /dev/null
+++ b/sysdeps/powerpc/powerpc64/be/multiarch/dl-symbol-redir-ifunc.h
@@ -0,0 +1,27 @@ 
+/* Symbol rediretion for loader/static initialization code.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _DL_IFUNC_GENERIC_H
+#define _DL_IFUNC_GENERIC_H
+
+#ifndef SHARED
+asm ("__mempcpy = __mempcpy_ppc");
+asm ("__strchrnul = __strchrnul_ppc");
+#endif
+
+#endif
diff --git a/sysdeps/powerpc/powerpc64/le/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/powerpc/powerpc64/le/multiarch/dl-symbol-redir-ifunc.h
index 4680092cd8e..03f6f12032a 100644
--- a/sysdeps/powerpc/powerpc64/le/multiarch/dl-symbol-redir-ifunc.h
+++ b/sysdeps/powerpc/powerpc64/le/multiarch/dl-symbol-redir-ifunc.h
@@ -21,5 +21,6 @@ 
 
 asm ("memset = __memset_power8");
 asm ("__mempcpy = __mempcpy_power7");
+asm ("__strchrnul = __strchrnul_power8");
 
 #endif
diff --git a/sysdeps/powerpc/powerpc64/multiarch/Makefile b/sysdeps/powerpc/powerpc64/multiarch/Makefile
index c9178223a87..0437a05a895 100644
--- a/sysdeps/powerpc/powerpc64/multiarch/Makefile
+++ b/sysdeps/powerpc/powerpc64/multiarch/Makefile
@@ -40,3 +40,4 @@  endif
 
 # Called during static initialization
 CFLAGS-strncmp-ppc64.c += $(no-stack-protector)
+CFLAGS-strchrnul-ppc64.c += $(no-stack-protector)
diff --git a/sysdeps/s390/Makefile b/sysdeps/s390/Makefile
index 481e8347925..01e4a4344fd 100644
--- a/sysdeps/s390/Makefile
+++ b/sysdeps/s390/Makefile
@@ -258,6 +258,11 @@  routines_no_fortify += \
   # routines_no_fortify
 endif
 
+# Called during static library initialization, so turn stack-protection
+# off for non-shared builds.
+CFLAGS-strchrnul-c.o = $(no-stack-protector)
+CFLAGS-strchrnul-c.op = $(no-stack-protector)
+
 ifeq ($(subdir),wcsmbs)
 sysdep_routines += \
   wcpcpy \
diff --git a/sysdeps/s390/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/s390/multiarch/dl-symbol-redir-ifunc.h
index a128cd05bd2..3721f3d95bb 100644
--- a/sysdeps/s390/multiarch/dl-symbol-redir-ifunc.h
+++ b/sysdeps/s390/multiarch/dl-symbol-redir-ifunc.h
@@ -21,11 +21,15 @@ 
 
 #include <ifunc-memset.h>
 #include <ifunc-memcmp.h>
+#include <ifunc-strchrnul.h>
 
 #define IFUNC_SYMBOL_STR1(s)	#s
 #define IFUNC_SYMBOL_STR(s)	IFUNC_SYMBOL_STR1(s)
 
+#ifndef SHARED
 asm ("memset = " IFUNC_SYMBOL_STR(MEMSET_DEFAULT));
 asm ("memcmp = " IFUNC_SYMBOL_STR(MEMCMP_DEFAULT));
+asm ("__strchrnul = " IFUNC_SYMBOL_STR(STRCHRNUL_DEFAULT));
+#endif
 
 #endif
diff --git a/sysdeps/s390/string-bitops.h b/sysdeps/s390/string-bitops.h
new file mode 100644
index 00000000000..f660d019c77
--- /dev/null
+++ b/sysdeps/s390/string-bitops.h
@@ -0,0 +1,27 @@ 
+/* Zero byte detection, define whether to use stdbit.h
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* s390x support static-pie and the libgcc implementation for
+   __builtin_clzl/__builtin_ctzl might access extern data that is not marked
+   as hidden, which creates additiona GOT access that is used before
+   self-relocation.  */
+#if __ARCH__ > 6
+# define HAVE_BITOPTS_WORKING 1
+#else
+# define HAVE_BITOPTS_WORKING 0
+#endif
diff --git a/sysdeps/sparc/sparc32/sparcv9/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/sparc/sparc32/sparcv9/multiarch/dl-symbol-redir-ifunc.h
index ffe1eee87d5..1a76efd5425 100644
--- a/sysdeps/sparc/sparc32/sparcv9/multiarch/dl-symbol-redir-ifunc.h
+++ b/sysdeps/sparc/sparc32/sparcv9/multiarch/dl-symbol-redir-ifunc.h
@@ -19,6 +19,9 @@ 
 #ifndef _DL_IFUNC_GENERIC_H
 #define _DL_IFUNC_GENERIC_H
 
+#ifndef SHARED
 asm ("memset = __memset_ultra1");
+asm ("memcpy = __memcpy_ultra1");
+#endif
 
 #endif
diff --git a/sysdeps/sparc/sparc64/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/sparc/sparc64/multiarch/dl-symbol-redir-ifunc.h
index ffe1eee87d5..1a76efd5425 100644
--- a/sysdeps/sparc/sparc64/multiarch/dl-symbol-redir-ifunc.h
+++ b/sysdeps/sparc/sparc64/multiarch/dl-symbol-redir-ifunc.h
@@ -19,6 +19,9 @@ 
 #ifndef _DL_IFUNC_GENERIC_H
 #define _DL_IFUNC_GENERIC_H
 
+#ifndef SHARED
 asm ("memset = __memset_ultra1");
+asm ("memcpy = __memcpy_ultra1");
+#endif
 
 #endif
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index 17f0c309a62..abf0972dfb2 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -114,6 +114,11 @@  sysdep_routines += \
   xstat \
   xstat64 \
   # sysdep_routines
+#
+# Called during static library initialization, so turn stack-protection
+# off for non-shared builds.
+CFLAGS-setvmaname.o = $(no-stack-protector)
+CFLAGS-setvmaname.op = $(no-stack-protector)
 
 CFLAGS-gethostid.c = -fexceptions
 CFLAGS-tee.c = -fexceptions -fasynchronous-unwind-tables
@@ -459,6 +464,13 @@  sysdep_routines += \
   raise_direct \
   # sysdep_routines
 
+# Called during static library initialization, so turn stack-protection
+# off for non-shared builds.
+CFLAGS-raise.o = $(no-stack-protector)
+CFLAGS-raise.op = $(no-stack-protector)
+CFLAGS-raise_direct.o = $(no-stack-protector)
+CFLAGS-raise_direct.op = $(no-stack-protector)
+
 tests-special += \
   $(objpfx)tst-signal-numbers.out \
   # tests-special
diff --git a/sysdeps/unix/sysv/linux/i386/Makefile b/sysdeps/unix/sysv/linux/i386/Makefile
index f1f8c3f44cf..9a70905df84 100644
--- a/sysdeps/unix/sysv/linux/i386/Makefile
+++ b/sysdeps/unix/sysv/linux/i386/Makefile
@@ -28,3 +28,17 @@  ifeq ($(subdir),rt)
 librt-routines += sysdep
 librt-shared-only-routines += sysdep
 endif
+
+ifeq ($(subdir),elf)
+sysdep_routines += \
+  dl-mmap \
+  dl-writev \
+  # sysdep-routines
+
+# Called during static library initialization, so turn stack-protection
+# off for non-shared builds.
+CFLAGS-dl-mmap.o = $(no-stack-protector)
+CFLAGS-dl-mmap.op = $(no-stack-protector)
+CFLAGS-dl-writev.o = $(no-stack-protector)
+CFLAGS-dl-writev.op = $(no-stack-protector)
+endif
diff --git a/sysdeps/unix/sysv/linux/i386/dl-mmap.c b/sysdeps/unix/sysv/linux/i386/dl-mmap.c
new file mode 100644
index 00000000000..ab6f96300fb
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/i386/dl-mmap.c
@@ -0,0 +1,36 @@ 
+/* mmap wrapper for dynamic loader.  Linux/i386 version.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* This mmap call is used to allocate some memory to backup assert() messages
+   before TLS setup is done, so it can not use "call *%gs:SYSINFO_OFFSET"
+   during startup in static PIE.  */
+#if BUILD_PIE_DEFAULT
+# define I386_USE_SYSENTER 0
+#endif
+
+#include <sys/mman.h>
+#include <dl-mmap.h>
+#include <mmap_internal.h>
+#include <sysdep.h>
+
+void *
+_dl_mmap (void *addr, size_t len, int prot, int flags)
+{
+  return (void *) MMAP_CALL (mmap2, addr, len, prot, flags, -1, 0);
+}
+
diff --git a/sysdeps/unix/sysv/linux/i386/dl-mmap.h b/sysdeps/unix/sysv/linux/i386/dl-mmap.h
new file mode 100644
index 00000000000..291f92ee14f
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/i386/dl-mmap.h
@@ -0,0 +1,27 @@ 
+/* mmap wrapper for dynamic loader.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _DL_MMAP_H
+#define _DL_MMAP_H
+
+/* i386 requires out-of-line implementation because it sets
+   I386_USE_SYSENTER to 0 to avoid use the vDSO.  */
+void * _dl_mmap (void *addr, size_t len, int prot, int flags)
+  attribute_hidden;
+
+#endif
diff --git a/sysdeps/unix/sysv/linux/i386/dl-writev.c b/sysdeps/unix/sysv/linux/i386/dl-writev.c
new file mode 100644
index 00000000000..ef6a6a20b82
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/i386/dl-writev.c
@@ -0,0 +1,32 @@ 
+/* writev wrapper for the dynamic linker.  Linux/i386 version.
+   Copyright (C) 2013-2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* This writev call is used to assert() before TLS setup is done, so it can
+   not use "call *%gs:SYSINFO_OFFSET" during startup in static PIE.  */
+#if BUILD_PIE_DEFAULT
+# define I386_USE_SYSENTER 0
+#endif
+
+#include <dl-writev.h>
+#include <sysdep.h>
+
+ssize_t
+_dl_writev (int fd, const struct iovec *iov, size_t niov)
+{
+  return INTERNAL_SYSCALL_CALL (writev, fd, iov, niov);
+}
diff --git a/sysdeps/unix/sysv/linux/i386/dl-writev.h b/sysdeps/unix/sysv/linux/i386/dl-writev.h
index 8327d32374e..d92c74d7126 100644
--- a/sysdeps/unix/sysv/linux/i386/dl-writev.h
+++ b/sysdeps/unix/sysv/linux/i386/dl-writev.h
@@ -16,9 +16,14 @@ 
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-#if BUILD_PIE_DEFAULT
-/* Can't use "call *%gs:SYSINFO_OFFSET" during startup in static PIE.  */
-# define I386_USE_SYSENTER 0
-#endif
+#ifndef _DL_WRITEV_H
+#define _DL_WRITEV_H
 
-#include <sysdeps/unix/sysv/linux/dl-writev.h>
+#include <sys/uio.h>
+
+/* i386 requires out-of-line implementation because it sets
+   I386_USE_SYSENTER to 0 to avoid use the vDSO.  */
+ssize_t _dl_writev (int fd, const struct iovec *iov, size_t niov)
+  attribute_hidden;
+
+#endif
diff --git a/sysdeps/unix/sysv/linux/i386/raise_direct.c b/sysdeps/unix/sysv/linux/i386/raise_direct.c
new file mode 100644
index 00000000000..b23b8e5386f
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/i386/raise_direct.c
@@ -0,0 +1,26 @@ 
+/* Internal function to send a signal to itself.  Linux/i386 version.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* This is called from abort() (issued by assert()) before TLS setup is done,
+   so it can not use "call *%gs:SYSINFO_OFFSET" during startup in static
+   PIE.  */
+#if BUILD_PIE_DEFAULT
+# define I386_USE_SYSENTER 0
+#endif
+
+#include <sysdeps/unix/sysv/linux/raise_direct.c>
diff --git a/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile b/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
index 2fdd4af3eb7..a10dd30d12b 100644
--- a/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
+++ b/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
@@ -24,8 +24,16 @@  sysdep_routines += \
   strncmp-vector \
   # sysdep_routines
 
+# Called during static library initialization, so turn stack-protection
+# off for non-shared builds.
+CFLAGS-memset-generic.o = $(no-stack-protector)
+CFLAGS-memset-generic.op = $(no-stack-protector)
+CFLAGS-memcpy-generic.o = $(no-stack-protector)
+CFLAGS-memcpy-generic.op = $(no-stack-protector)
+
 CFLAGS-memcpy_noalignment.c += -mno-strict-align
 # Called during static initialization
+CFLAGS-strlen-generic.c += $(no-stack-protector)
 CFLAGS-memset-generic.c += $(no-stack-protector)
 CFLAGS-memcpy-generic.c += $(no-stack-protector)
 endif
diff --git a/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
index 1f3ca20307c..4d9cc5dd914 100644
--- a/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
+++ b/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
@@ -73,6 +73,28 @@  asm ("memmove = " HAVE_MEMMOVE_IFUNC_GENERIC);
 asm ("mempcpy = " HAVE_MEMPCPY_IFUNC_GENERIC);
 asm ("__mempcpy = " HAVE_MEMPCPY_IFUNC_GENERIC);
 
+
+#if MINIMUM_X86_ISA_LEVEL >= 4
+# define HAVE_STRCHRNUL_IFUNC_GENERIC "__strchrnul_evex"
+#elif MINIMUM_X86_ISA_LEVEL == 3
+# define HAVE_STRCHRNUL_IFUNC_GENERIC "__strchrnul_avx2"
+#else
+# define HAVE_STRCHRNUL_IFUNC_GENERIC "__strchrnul_sse2"
+#endif
+
+asm ("__strchrnul = " HAVE_STRCHRNUL_IFUNC_GENERIC);
+
+
+#if MINIMUM_X86_ISA_LEVEL >= 4
+# define HAVE_STRLEN_IFUNC_GENERIC "__strlen_evex"
+#elif MINIMUM_X86_ISA_LEVEL == 3
+# define HAVE_STRLEN_IFUNC_GENERIC "__strlen_avx2"
+#else
+# define HAVE_STRLEN_IFUNC_GENERIC "__strlen_sse2"
+#endif
+
+asm ("strlen = " HAVE_STRLEN_IFUNC_GENERIC);
+
 #endif /* SHARED */
 
 #endif