[v11] elf: Support THP segment load with madvise enabled THP

Message ID CAMe9rOo7hu7HR1iLWNJFCWda+zT8UifUkwhBoKqR9DhFrc3FeQ@mail.gmail.com (mailing list archive)
State Superseded
Headers
Series [v11] elf: Support THP segment load with madvise enabled THP |

Checks

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

Commit Message

H.J. Lu May 28, 2026, 4:26 a.m. UTC
  Changes in v11:

1. Build THP PDE tests with $(load-address-ldflag)=$(THP-PAGE-SIZE) to work
around:

https://sourceware.org/bugzilla/show_bug.cgi?id=34184

2. Update strace-tst-thp.sh to run static THP tests directly.
  

Comments

Adhemerval Zanella Netto May 29, 2026, 2:27 p.m. UTC | #1
On 28/05/26 01:26, H.J. Lu wrote:
> Changes in v11:
> 
> 1. Build THP PDE tests with $(load-address-ldflag)=$(THP-PAGE-SIZE) to work
> around:
> 
> https://sourceware.org/bugzilla/show_bug.cgi?id=34184
> 
> 2. Update strace-tst-thp.sh to run static THP tests directly.
> 
> From 75a068e17c8104beb1f5a982fa3330cc2b3ef06b Mon Sep 17 00:00:00 2001
> From: "H.J. Lu" <hjl.tools@gmail.com>
> Date: Mon, 13 Apr 2026 08:23:05 +0800
> Subject: [PATCH v11] elf: Support THP segment load with madvise enabled THP
> 
> The current THP segment load approach works only when THP is enabled
> with always in the kernel.  If THP is enabled with madvise in the
> kernel, to enable THP segment load in an application, madvise should
> be called with MADV_HUGEPAGE on all THP eligible PT_LOAD segments:
> 
> 1. Define DL_MAP_DEFAULT_THP_PAGESIZE in hugepages.h and default it to 0.
> If DL_MAP_DEFAULT_THP_PAGESIZE is defined, assume kernel THP madvise mode.
> If kernel THP mode is always or never, there is an extra madvise call
> which has no impact.  DL_MAP_DEFAULT_THP_PAGESIZE is defined for x86 and
> 64-bit loongarch.
> 2. Update _dl_map_segment_align to support madvise THP mode.  This fixes
> BZ #34079.
> 3. Call _dl_executable_postprocess in rtld_setup_main_map for dynamic
> executables and in LIBC_START_MAIN for static executables, which calls
> madvise with MADV_HUGEPAGE on all THP eligible PT_LOAD segments in
> executable.  This fixes BZ #34080 for both dynamic and static executables.
> 4. Call _dl_postprocess_loadcmd_extra in _dl_postprocess_loadcmd, which
> calls madvise with MADV_HUGEPAGE on all THP eligible PT_LOAD segments
> when loading an object after they have been mapped in.  This fixes
> BZ #34080 for shared objects.
> 5. Set the maximum page alignment on THP tests to THP page size as the
> default maximum page alignment may be smaller than THP page size.
> 6. Add tests to verify that large executable PT_LOAD segments in
> executables are mapped at addresses aligned to THP page size when the
> kernel is configured to use THP in "always" mode or "madvise" mode by
> inspecting /proc/self/maps to check that the mapping address is aligned
> to THP page size reported by the kernel.  Also verify that madvise is
> called with MADV_HUGEPAGE when the glibc tunable glibc.elf.thp=1 is used
> and madvise isn't called with MADV_HUGEPAGE when the glibc tunable
> glibc.elf.thp=0 is used.
> 
> Skip these tests if THP page size cannot be determined or if THP is not
> enabled in "always" mode nor "madvise" mode.
> 
> Quote WANG Rui <wangrui@loongson.cn>:

Ok, I have done a full review this time, some comments below.

> 
> From benchmarking a clang build of the Linux kernel on x86_64 with
> your patch in THP madvise mode, I observed that iTLB misses were
> reduced, similar to what we see in THP always mode.
> 
> NB: Some THP tests fail on arm due to limitations of arm32 kABI:
> 
> https://sourceware.org/bugzilla/show_bug.cgi?id=34096
> 
> Signed-off-by: H.J. Lu <hjl.tools@gmail.com>

There are still failures on RISC-V, but theses are straightforward to fix:

FAIL: elf/tst-thp-1-no-s-code-static
FAIL: elf/tst-thp-1-static

The _dl_executable_postprocess is issued before IFUNC relocation step 
(_dl_relocate_static_pie_ifunc() / ARCH_SETUP_IREL()); and the function
calls strcmp through __get_thp_mode.  RISC-V now has strcmp through ifunc
so it is just a matter to update its dl-symbol-redir-func.h:

---
diff --git a/sysdeps/riscv/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/riscv/multiarch/dl-symbol-redir-ifunc.h
index c6f2aacd17b..f0520496ce5 100644
--- a/sysdeps/riscv/multiarch/dl-symbol-redir-ifunc.h
+++ b/sysdeps/riscv/multiarch/dl-symbol-redir-ifunc.h
@@ -23,6 +23,7 @@
 asm ("memcpy = __memcpy_generic");
 asm ("memset = __memset_generic");
 asm ("strlen = __strlen_generic");
+asm ("strcmp = __strcmp_generic");
 #endif

 #endif
diff --git a/sysdeps/unix/sysv/linux/hugepages.c b/sysdeps/unix/sysv/linux/hugepages.c
index 46e05151a3c..8c886406914 100644
--- a/sysdeps/unix/sysv/linux/hugepages.c
+++ b/sysdeps/unix/sysv/linux/hugepages.c
@@ -21,6 +21,7 @@
 #include <hugepages.h>
 #include <not-cancel.h>
 #include <sys/mman.h>
+#include <dl-symbol-redir-ifunc.h>

 unsigned long int
 __get_thp_size (void)
---

Another possibility, which would require slight less maintability (to
avoid each ABI to update its dl-symbol-redir-ifunc.h) is to move
_dl_executable_postprocess after ARCH_SETUP_IREL:

---
diff --git a/csu/libc-start.c b/csu/libc-start.c
index bb106b524aa..508f41be71a 100644
--- a/csu/libc-start.c
+++ b/csu/libc-start.c
@@ -301,9 +301,6 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
   __pointer_chk_guard_local = pointer_chk_guard;
 # endif

-  struct link_map *main_map = _dl_get_dl_main_map ();
-  _dl_executable_postprocess (main_map, GL(dl_phdr), GL(dl_phnum));
-
   /* Now that the TCB, canary, and pointer guard are in place, run the
      deferred IFUNC relocations.  For non-PIE static binaries this is
      ARCH_SETUP_IREL (apply_irel); for static-pie it is the IRELATIVE
@@ -311,6 +308,11 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
   _dl_relocate_static_pie_ifunc ();
   ARCH_SETUP_IREL ();

+  /* This must run after the IFUNC relocations: _dl_executable_postprocess may
+     reach IFUNC-resolved routines.  */
+  struct link_map *main_map = _dl_get_dl_main_map ();
+  _dl_executable_postprocess (main_map, GL(dl_phdr), GL(dl_phnum));
+
   /* Initialize libpthread if linked in.  */
   if (__pthread_initialize_minimal != NULL)
     __pthread_initialize_minimal ();
---

I am slight inclined to suggest the second option.


And I also check with a clang based with clang 22.1.0 on x86_64, and it triggers
some regressions:

FAIL: elf/tst-thp-1-no-s-code
FAIL: elf/tst-thp-1-no-s-code-pde
FAIL: elf/tst-thp-1-no-s-code-static

It seems that lld and GNU ld interpret -z noseparate-code differently:

 * GNU ld collapses the read-only and executable regions into a single R E PT_LOAD 
   segment, which begins at the 2 MB-aligned text base.  So the executable mapping 
   is 2 MB-aligned.

 * LLD keeps the executable code as its own PT_LOAD and only drops the inter-segment 
   alignment gap that separate-code would insert. It places that segment at a virtual 
   address merely congruent to its file offset modulo max-page-size - not rounded up 
   to a 2 MB boundary.

For the failing binary elf/tst-thp-1-no-s-code, the R E segment is at VirtAddr
*0x202d40* and a runtime the kernel maps it page-rounded to base + 0x202000, so:

  0x202000 % 0x200000 (2MB) = 0x2000   != 0

This is a linker layout difference, not a glibc runtime bug. I think it would be
better for now to:

ifeq (yes,$(with-lld))
test-xfail-tst-thp-1-no-s-code = yes
test-xfail-tst-thp-1-no-s-code-pde = yes
test-xfail-tst-thp-1-no-s-code-static = yes
endif

> ---
>  csu/libc-start.c                              |   4 +
>  elf/dl-load.h                                 |   3 +
>  elf/dl-support.c                              |   6 +
>  elf/rtld.c                                    |  17 +-
>  sysdeps/generic/dl-exec-post.h                |  34 ++++
>  sysdeps/generic/dl-load-post.h                |  23 +++
>  sysdeps/generic/hugepages.h                   |   8 +-
>  sysdeps/generic/ldsodefs.h                    |  12 ++
>  sysdeps/unix/sysv/linux/Makefile              | 165 +++++++++++++++++-
>  sysdeps/unix/sysv/linux/arm/Makefile          |   7 +
>  sysdeps/unix/sysv/linux/dl-exec-post.h        | 126 +++++++++++++
>  sysdeps/unix/sysv/linux/dl-load-post.h        |  32 ++++
>  .../unix/sysv/linux/dl-map-segment-align.c    |  33 +---
>  .../unix/sysv/linux/dl-map-segment-align.h    |  17 +-
>  sysdeps/unix/sysv/linux/ldsodefs.h            |   3 +
>  sysdeps/unix/sysv/linux/loongarch/Makefile    |   3 +
>  .../{dl-map-segment-align.h => hugepages.h}   |   4 +-
>  sysdeps/unix/sysv/linux/strace-tst-thp.sh     |  80 +++++++++
>  .../unix/sysv/linux/tst-thp-1-no-s-code-pde.c |  19 ++
>  .../sysv/linux/tst-thp-1-no-s-code-static.c   |  19 ++
>  sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c |  19 ++
>  sysdeps/unix/sysv/linux/tst-thp-1-pde.c       |  19 ++
>  sysdeps/unix/sysv/linux/tst-thp-1-static.c    |  19 ++
>  sysdeps/unix/sysv/linux/tst-thp-1.c           |  28 +++
>  sysdeps/unix/sysv/linux/tst-thp-align-check.h | 124 +++++++++++++
>  sysdeps/unix/sysv/linux/tst-thp-align.c       | 123 +------------
>  sysdeps/unix/sysv/linux/x86/hugepages.h       |  22 +++
>  27 files changed, 805 insertions(+), 164 deletions(-)
>  create mode 100644 sysdeps/generic/dl-exec-post.h
>  create mode 100644 sysdeps/generic/dl-load-post.h
>  create mode 100644 sysdeps/unix/sysv/linux/dl-exec-post.h
>  create mode 100644 sysdeps/unix/sysv/linux/dl-load-post.h
>  rename sysdeps/unix/sysv/linux/loongarch/lp64/{dl-map-segment-align.h => hugepages.h} (90%)
>  create mode 100644 sysdeps/unix/sysv/linux/strace-tst-thp.sh
>  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c
>  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c
>  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c
>  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-pde.c
>  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-static.c
>  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1.c
>  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-align-check.h
>  create mode 100644 sysdeps/unix/sysv/linux/x86/hugepages.h
> 
> diff --git a/csu/libc-start.c b/csu/libc-start.c
> index 03d770ef15..bb106b524a 100644
> --- a/csu/libc-start.c
> +++ b/csu/libc-start.c
> @@ -205,6 +205,7 @@ call_fini (void *unused)
>  #endif /* !SHARED */
>  
>  #include <libc-start.h>
> +#include <dl-exec-post.h>
>  
>  STATIC int LIBC_START_MAIN (int (*main) (int, char **, char **
>  					 MAIN_AUXVEC_DECL),
> @@ -300,6 +301,9 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
>    __pointer_chk_guard_local = pointer_chk_guard;
>  # endif
>  
> +  struct link_map *main_map = _dl_get_dl_main_map ();
> +  _dl_executable_postprocess (main_map, GL(dl_phdr), GL(dl_phnum));
> +
>    /* Now that the TCB, canary, and pointer guard are in place, run the
>       deferred IFUNC relocations.  For non-PIE static binaries this is
>       ARCH_SETUP_IREL (apply_irel); for static-pie it is the IRELATIVE

Maybe we should move the _dl_executable_postprocess after IFUNC processing, as above.

> diff --git a/elf/dl-load.h b/elf/dl-load.h
> index 80ae5db4b3..84b82e183d 100644
> --- a/elf/dl-load.h
> +++ b/elf/dl-load.h
> @@ -112,6 +112,7 @@ struct loadcmd
>    int prot;                             /* PROT_* bits.  */
>  };
>  
> +#include <dl-load-post.h>
>  
>  /* Iterator for program header segments.  Initialize with
>     _dl_pt_load_iterator_init, then either walk PT_LOAD segments via
> @@ -215,6 +216,8 @@ _dl_postprocess_loadcmd (struct link_map *l, const ElfW(Ehdr) *header,
>      /* Found the program header in this segment.  */
>      l->l_phdr = (void *) (uintptr_t) (c->mapstart + header->e_phoff
>                                        - c->mapoff);
> +
> +  _dl_postprocess_loadcmd_extra (l, c);
>  }
>  
>  

Ok.

> diff --git a/elf/dl-support.c b/elf/dl-support.c
> index 0508d6113b..a8114de003 100644
> --- a/elf/dl-support.c
> +++ b/elf/dl-support.c
> @@ -179,6 +179,12 @@ int _dl_stack_cache_lock;
>  #endif
>  struct dl_scope_free_list *_dl_scope_free_list;
>  
> +#ifdef HAVE_THP
> +int _dl_elf_thp_control = -1;
> +enum thp_mode_t _dl_thp_mode;
> +size_t _dl_elf_thp_pagesize;
> +#endif
> +
>  #ifdef NEED_DL_SYSINFO
>  /* Needed for improved syscall handling on at least x86/Linux.  NB: Don't
>     initialize it here to avoid RELATIVE relocation in static PIE.  */

Although we already have the Linux ldsodefs.h, I think a better strategy would
to move the HAVE_THP to a different header (ldsodefs-arch.h or something),
so we can always have a definitions and use if "#if" instead of "#ifdef"
(so missing inclusion will trigger an error instead of silent use a
!HAVE_THP).

> diff --git a/elf/rtld.c b/elf/rtld.c
> index 12e1b4dd71..e1ec899756 100644
> --- a/elf/rtld.c
> +++ b/elf/rtld.c
> @@ -52,6 +52,7 @@
>  #include <dl-find_object.h>
>  #include <dl-audit-check.h>
>  #include <dl-call_tls_init_tp.h>
> +#include <dl-exec-post.h>
>  
>  #include <assert.h>
>  
> @@ -323,6 +324,9 @@ struct rtld_global _rtld_global =
>      /* Generally the default presumption without further information is an
>       * executable stack but this is not true for all platforms.  */
>      ._dl_stack_prot_flags = DEFAULT_STACK_PROT_PERMS,
> +#ifdef HAVE_THP
> +    ._dl_elf_thp_control = -1,
> +#endif
>  #ifdef _LIBC_REENTRANT
>      ._dl_load_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
>      ._dl_load_write_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
> @@ -1209,14 +1213,8 @@ rtld_setup_main_map (struct link_map *main_map)
>  	main_map->l_relro_size = ph->p_memsz;
>  	break;
>        }
> -  /* Process program headers again, but scan them backwards since
> -     PT_GNU_PROPERTY is close to the end of program headers.   */
> -  for (const ElfW(Phdr) *ph = &phdr[phnum]; ph != phdr; --ph)
> -    if (ph[-1].p_type == PT_GNU_PROPERTY)
> -      {
> -	_dl_process_pt_gnu_property (main_map, -1, &ph[-1]);
> -	break;
> -      }
> +
> +  _dl_executable_postprocess (main_map, phdr, phnum);
>  
>    /* Adjust the address of the TLS initialization image in case
>       the executable is actually an ET_DYN object.  */
> @@ -1589,6 +1587,9 @@ dl_main (const ElfW(Phdr) *phdr,
>  	{
>  	  RTLD_TIMING_VAR (start);
>  	  rtld_timer_start (&start);
> +#ifdef HAVE_THP
> +	  _dl_get_thp_config ();
> +#endif
>  	  _dl_map_object (NULL, rtld_progname, lt_executable, 0,
>  			  __RTLD_OPENEXEC, LM_ID_BASE);
>  	  rtld_timer_stop (&load_time, start);

Ok.

> diff --git a/sysdeps/generic/dl-exec-post.h b/sysdeps/generic/dl-exec-post.h
> new file mode 100644
> index 0000000000..f5dcdc093a
> --- /dev/null
> +++ b/sysdeps/generic/dl-exec-post.h
> @@ -0,0 +1,34 @@
> +/* _dl_executable_postprocess.  Generic version.
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   Copyright The GNU Toolchain Authors.
> +   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/>.  */
> +
> +static inline void
> +_dl_executable_postprocess (struct link_map *main_map,
> +			    const ElfW(Phdr) *phdr, ElfW(Word) phnum)
> +{
> +#ifdef SHARED
> +  /* Process program headers again, but scan them backwards since
> +     PT_GNU_PROPERTY is close to the end of program headers.   */
> +  for (const ElfW(Phdr) *ph = &phdr[phnum]; ph != phdr; --ph)
> +    if (ph[-1].p_type == PT_GNU_PROPERTY)
> +      {
> +	_dl_process_pt_gnu_property (main_map, -1, &ph[-1]);
> +	break;
> +      }
> +#endif
> +}

Ok.

> diff --git a/sysdeps/generic/dl-load-post.h b/sysdeps/generic/dl-load-post.h
> new file mode 100644
> index 0000000000..1a6d205e33
> --- /dev/null
> +++ b/sysdeps/generic/dl-load-post.h
> @@ -0,0 +1,23 @@
> +/* _dl_postprocess_loadcmd_extra.  Generic version.
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   Copyright The GNU Toolchain Authors.
> +   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/>.  */
> +
> +static inline void
> +_dl_postprocess_loadcmd_extra (struct link_map *l, const struct loadcmd *c)
> +{
> +}

Ok.

> diff --git a/sysdeps/generic/hugepages.h b/sysdeps/generic/hugepages.h
> index 5fc9b5c8de..f7f4957e79 100644
> --- a/sysdeps/generic/hugepages.h
> +++ b/sysdeps/generic/hugepages.h
> @@ -26,10 +26,10 @@ unsigned long int __get_thp_size (void) attribute_hidden;
>  
>  enum thp_mode_t
>  {
> +  thp_mode_not_supported = 0,
>    thp_mode_always,
>    thp_mode_madvise,
> -  thp_mode_never,
> -  thp_mode_not_supported
> +  thp_mode_never
>  };
>  
>  enum thp_mode_t __get_thp_mode (void) attribute_hidden;
> @@ -45,6 +45,10 @@ void __get_hugepage_config (size_t requested, size_t *pagesize, int *flags)
>  # define MALLOC_DEFAULT_THP_PAGESIZE	0
>  #endif
>  
> +#ifndef DL_MAP_DEFAULT_THP_PAGESIZE
> +# define DL_MAP_DEFAULT_THP_PAGESIZE	0
> +#endif
> +
>  #ifndef MAX_THP_PAGESIZE
>  # define MAX_THP_PAGESIZE	(32 * 1024 * 1024)
>  #endif

Ok.

> diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
> index 24529db8a1..ee58e2778a 100644
> --- a/sysdeps/generic/ldsodefs.h
> +++ b/sysdeps/generic/ldsodefs.h
> @@ -39,6 +39,7 @@
>  #include <libc-lock.h>
>  #include <hp-timing.h>
>  #include <list_t.h>
> +#include <hugepages.h>
>  
>  __BEGIN_DECLS
>  
> @@ -477,6 +478,17 @@ struct rtld_global
>    EXTERN struct __pthread **_dl_pthread_threads;
>    __mach_rwlock_define (EXTERN, _dl_pthread_threads_lock)
>  #endif
> +#ifdef HAVE_THP
> +  /* The THP segment load control:
> +      > 0: Enabled by GLIBC_TUNABLES=glibc.elf.thp=1.
> +        0: Disabled by GLIBC_TUNABLES=glibc.elf.thp=0.
> +      < 0: To be enabled or disabled by GLIBC_TUNABLES.  */
> +  EXTERN int _dl_elf_thp_control;

Maybe use an enum for the tri-phase state here.

> +  /* The kernel THP mode.  */
> +  EXTERN enum thp_mode_t _dl_thp_mode;
> +  /* Page size used for THP segment load.  */
> +  EXTERN size_t _dl_elf_thp_pagesize;
> +#endif
>  #ifdef SHARED
>  };
>  # define __rtld_global_attribute__
> diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
> index 63e7046cb3..d825fe853a 100644
> --- a/sysdeps/unix/sysv/linux/Makefile
> +++ b/sysdeps/unix/sysv/linux/Makefile
> @@ -698,11 +698,15 @@ $(objpfx)pldd: $(objpfx)xmalloc.o
>  tests += \
>    tst-rseq-tls-range \
>    tst-rseq-tls-range-4096 \
> +  tst-thp-1 \
> +  tst-thp-1-pde \
> +  tst-thp-1-static \
>    tst-thp-align \
>  # tests
>  tests-static += \
>    tst-rseq-tls-range-4096-static \
>    tst-rseq-tls-range-static \
> +  tst-thp-1-static \
>  # tests-static
>  modules-names += \
>    tst-rseq-tls-range-mod \
> @@ -712,15 +716,12 @@ CFLAGS-tst-rseq-tls-range.c += -DMAIN_TLS_ALIGN=4
>  CFLAGS-tst-rseq-tls-range-4096.c += -DMAIN_TLS_ALIGN=4096
>  CFLAGS-tst-rseq-tls-range-static.c += -DMAIN_TLS_ALIGN=4
>  CFLAGS-tst-rseq-tls-range-4096-static.c += -DMAIN_TLS_ALIGN=4096
> -LDFLAGS-tst-thp-size-mod.so += -Wl,-z,noseparate-code
>  $(objpfx)tst-rseq-tls-range.out: $(objpfx)tst-rseq-tls-range-mod.so
>  $(objpfx)tst-rseq-tls-range-4096.out: $(objpfx)tst-rseq-tls-range-mod.so
>  $(objpfx)tst-rseq-tls-range-static.out: $(objpfx)tst-rseq-tls-range-mod.so
>  $(objpfx)tst-rseq-tls-range-4096-static.out: $(objpfx)tst-rseq-tls-range-mod.so
> -$(objpfx)tst-thp-align.out: $(objpfx)tst-thp-size-mod.so
>  tst-rseq-tls-range-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx)
>  tst-rseq-tls-range-4096-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx)
> -tst-thp-align-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
>  
>  test-internal-extras += tst-nolink-libc
>  ifeq ($(run-built-tests),yes)
> @@ -729,6 +730,164 @@ tests-special += \
>    $(objpfx)tst-nolink-libc-2.out \
>    # tests-special
>  endif
> +
> +ifndef THP-PAGE-SIZE
> +# Align PT_LOAD segments in THP tests to THP page size so that kernel will
> +# map PIE to the address aligned to THP page size.  Default THP page size
> +# to 2MB which can be overridden in Makefile in subdirectories.
> +THP-PAGE-SIZE = 0x200000
> +endif
> +
> +THP-PAGE-SIZE-LDFLAGS = -Wl,-z,max-page-size=$(THP-PAGE-SIZE)
> +
> +# -Wl,-z,max-page-size=$(THP-PAGE-SIZE) alone doesn't work for PDE when
> +# text-segment address is lower than the maximum page size:
> +# https://sourceware.org/bugzilla/show_bug.cgi?id=34184
> +ifneq (,$(load-address-ldflag))
> +LOAD-THP-ADDRESS-LDFLAGS = $(load-address-ldflag)=$(THP-PAGE-SIZE)
> +endif
> +

This made the arm failures to work, now that the first PT_LOAD VirtAddr
is set of 0x200000.  I think the arm32 xfail can now be removed.

> +LDFLAGS-tst-thp-size-mod.so = -Wl,-z,noseparate-code \
> +			      $(THP-PAGE-SIZE-LDFLAGS)
> +tst-thp-align-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> +$(objpfx)tst-thp-align.out: $(objpfx)tst-thp-size-mod.so
> +
> +tests += \
> +  tst-thp-1-no-s-code \
> +  tst-thp-1-no-s-code-pde \
> +  tst-thp-1-no-s-code-static \
> +# tests
> +tests-static += \
> +  tst-thp-1-no-s-code-static \
> +# tests-static
> +
> +LDFLAGS-tst-thp-1 = -Wl,-z,separate-code $(THP-PAGE-SIZE-LDFLAGS)
> +LDFLAGS-tst-thp-1-pde = -Wl,-z,separate-code $(THP-PAGE-SIZE-LDFLAGS) \
> +			$(LOAD-THP-ADDRESS-LDFLAGS)
> +LDFLAGS-tst-thp-1-static = -Wl,-z,separate-code $(THP-PAGE-SIZE-LDFLAGS)
> +ifneq (yes,$(enable-static-pie))
> +LDFLAGS-tst-thp-1-static += $(LOAD-THP-ADDRESS-LDFLAGS)
> +endif
> +LDFLAGS-tst-thp-1-no-s-code = -Wl,-z,noseparate-code \
> +			      $(THP-PAGE-SIZE-LDFLAGS)
> +LDFLAGS-tst-thp-1-no-s-code-pde = -Wl,-z,noseparate-code \
> +				  $(THP-PAGE-SIZE-LDFLAGS) \
> +				  $(LOAD-THP-ADDRESS-LDFLAGS)
> +LDFLAGS-tst-thp-1-no-s-code-static = -Wl,-z,noseparate-code \
> +				     $(THP-PAGE-SIZE-LDFLAGS)
> +ifneq (yes,$(enable-static-pie))
> +LDFLAGS-tst-thp-1-no-s-code-static += $(LOAD-THP-ADDRESS-LDFLAGS)
> +endif
> +
> +$(objpfx)tst-thp-1-no-s-code: $(objpfx)tst-thp-size-mod.o
> +$(objpfx)tst-thp-1-no-s-code-pde: $(objpfx)tst-thp-size-mod.o
> +$(objpfx)tst-thp-1-no-s-code-static: $(objpfx)tst-thp-size-mod.o
> +
> +tst-thp-1-no-s-code-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> +tst-thp-1-no-s-code-pde-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> +tst-thp-1-no-s-code-static-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> +
> +tst-thp-1-no-s-code-pde-no-pie = yes
> +
> +tst-thp-1-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> +tst-thp-1-pde-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> +tst-thp-1-static-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> +
> +$(objpfx)tst-thp-1: $(objpfx)tst-thp-size-mod.o
> +$(objpfx)tst-thp-1-pde: $(objpfx)tst-thp-size-mod.o
> +$(objpfx)tst-thp-1-static: $(objpfx)tst-thp-size-mod.o
> +
> +tst-thp-1-pde-no-pie = yes
> +
> +# Don't run strace tests for cross-compiling.
> +ifeq (no,$(cross-compiling))
> +thp-kernel-status = $(shell grep madvise /sys/kernel/mm/transparent_hugepage/enabled)
> +# Verify that madvise is called with MADV_HUGEPAGE when THP is enabled
> +# under madvise THP kernel.
> +ifneq ($(findstring [madvise],$(thp-kernel-status)),)
> +tests-special += \
> +  $(objpfx)strace-tst-thp-1-disabled.out \
> +  $(objpfx)strace-tst-thp-1-enabled.out \
> +  $(objpfx)strace-tst-thp-1-pde-disabled.out \
> +  $(objpfx)strace-tst-thp-1-pde-enabled.out \
> +  $(objpfx)strace-tst-thp-1-static-disabled.out \
> +  $(objpfx)strace-tst-thp-1-static-enabled.out \
> +  $(objpfx)strace-tst-thp-align-default.out \
> +  $(objpfx)strace-tst-thp-align-disabled.out \
> +  $(objpfx)strace-tst-thp-align-enabled.out \
> +# tests-special
> +
> +$(objpfx)strace-tst-thp-1-enabled.out: \
> +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> +  $(objpfx)tst-thp-1
> +	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' \
> +		'$(rpath-link)' $(objpfx)tst-thp-1 > $@; \
> +	  $(evaluate-test)
> +
> +$(objpfx)strace-tst-thp-1-disabled.out: \
> +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> +  $(objpfx)tst-thp-1
> +	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' \
> +		'$(rpath-link)' $(objpfx)tst-thp-1 > $@; \
> +	  $(evaluate-test)
> +
> +$(objpfx)strace-tst-thp-1-pde-enabled.out: \
> +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> +  $(objpfx)tst-thp-1-pde
> +	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' \
> +		'$(rpath-link)' $(objpfx)tst-thp-1-pde > $@; \
> +	  $(evaluate-test)
> +
> +$(objpfx)strace-tst-thp-1-pde-disabled.out: \
> +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> +  $(objpfx)tst-thp-1-pde
> +	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' \
> +		'$(rpath-link)' $(objpfx)tst-thp-1-pde > $@; \
> +	  $(evaluate-test)
> +
> +$(objpfx)strace-tst-thp-1-static-enabled.out: \
> +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh \
> +  $(objpfx)tst-thp-1-static
> +	$(SHELL) $< $(objpfx)tst-thp-1-static '$(test-wrapper-env)' \
> +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' > $@; \
> +	  $(evaluate-test)
> +
> +$(objpfx)strace-tst-thp-1-static-disabled.out: \
> +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh \
> +  $(objpfx)tst-thp-1-static
> +	$(SHELL) $< $(objpfx)tst-thp-1-static '$(test-wrapper-env)' \
> +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' > $@; \
> +	  $(evaluate-test)
> +
> +$(objpfx)strace-tst-thp-align-default.out: \
> +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> +  $(objpfx)tst-thp-align
> +	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> +		'$(run-program-env)' \
> +		'$(rpath-link)' $(objpfx)tst-thp-align > $@; \
> +	  $(evaluate-test)
> +
> +$(objpfx)strace-tst-thp-align-enabled.out: \
> +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> +  $(objpfx)tst-thp-align
> +	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' \
> +		'$(rpath-link)' $(objpfx)tst-thp-align > $@; \
> +	  $(evaluate-test)
> +
> +$(objpfx)strace-tst-thp-align-disabled.out: \
> +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> +  $(objpfx)tst-thp-align
> +	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' \
> +		'$(rpath-link)' $(objpfx)tst-thp-align > $@; \
> +	  $(evaluate-test)
> +endif # [madvise]
> +endif # $(cross-compiling)
>  endif # $(subdir) == elf
>  
>  ifeq ($(subdir),rt)

Ok.

> diff --git a/sysdeps/unix/sysv/linux/arm/Makefile b/sysdeps/unix/sysv/linux/arm/Makefile
> index e73ce4f811..1ee8bec9b9 100644
> --- a/sysdeps/unix/sysv/linux/arm/Makefile
> +++ b/sysdeps/unix/sysv/linux/arm/Makefile
> @@ -3,6 +3,13 @@ sysdep-rtld-routines += aeabi_read_tp libc-do-syscall
>  # The test uses INTERNAL_SYSCALL_CALL.  In thumb mode, this uses
>  # an undefined reference to __libc_do_syscall.
>  CFLAGS-tst-nolink-libc.c += -marm
> +
> +# These tests fail on arm due to limitations of arm32 kABI:
> +# https://sourceware.org/bugzilla/show_bug.cgi?id=34096
> +test-xfail-tst-thp-1-no-s-code-pde = yes
> +test-xfail-tst-thp-1-no-s-code-static = yes
> +test-xfail-tst-thp-1-pde = yes
> +test-xfail-tst-thp-1-static = yes
>  endif
>  
>  ifeq ($(subdir),misc)

I think these can be removed, from previous remarks.

> diff --git a/sysdeps/unix/sysv/linux/dl-exec-post.h b/sysdeps/unix/sysv/linux/dl-exec-post.h
> new file mode 100644
> index 0000000000..69505df402
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/dl-exec-post.h
> @@ -0,0 +1,126 @@
> +/* _dl_executable_postprocess.  Linux version.
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   Copyright The GNU Toolchain Authors.
> +   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/>.  */
> +
> +static inline void
> +_dl_get_thp_config (void)
> +{
> +  /* Check if there is GLIBC_TUNABLES=glibc.elf.thp=[0|1].  */
> +  GL(dl_elf_thp_control) = TUNABLE_GET_FULL (glibc, elf, thp, int32_t,
> +					     NULL);
> +
> +  /* Return if the tunable is not set or THP is disabled by the
> +     tunable.  */
> +  if (GL(dl_elf_thp_control) == 0)
> +    return;
> +
> +  _Static_assert (DL_MAP_DEFAULT_THP_PAGESIZE <= MAX_THP_PAGESIZE,
> +		  "DL_MAP_DEFAULT_THP_PAGESIZE <= MAX_THP_PAGESIZE");
> +
> +  /* NB: Accessing /sys/kernel/mm files is quite expensive and the file
> +     may not be accessible in containers.  If DL_MAP_DEFAULT_THP_PAGESIZE
> +     is non-zero, assume THP mode is madvise and always call madvise.
> +     Since madvise is a fast system call, it adds only a small overhead
> +     compared to the cost of accessing /sys/kernel/mm files.  */
> +  if (DL_MAP_DEFAULT_THP_PAGESIZE != 0)
> +    {
> +      GL(dl_elf_thp_pagesize) = DL_MAP_DEFAULT_THP_PAGESIZE;
> +      GL(dl_thp_mode) = thp_mode_madvise;
> +    }
> +  else
> +    {
> +      GL(dl_thp_mode) = __get_thp_mode ();
> +      if (GL(dl_thp_mode) == thp_mode_always
> +	  || GL(dl_thp_mode) == thp_mode_madvise)
> +	{
> +	  GL(dl_elf_thp_pagesize) = __get_thp_size ();
> +	  /* We cap the huge page size at MAX_THP_PAGESIZE to avoid
> +	     over-aligning on systems with very large normal pages
> +	     (like 64K pages with 512M huge pages).  */
> +	  if (GL(dl_elf_thp_pagesize) > MAX_THP_PAGESIZE)
> +	    GL(dl_elf_thp_pagesize) = 0;
> +	}
> +      else
> +	GL(dl_elf_thp_pagesize) = 0;
> +
> +      if (GL(dl_elf_thp_pagesize) == 0)
> +	{
> +	  GL(dl_elf_thp_control) = 0;
> +	  GL(dl_thp_mode) = thp_mode_not_supported;
> +	}
> +    }
> +}
> +
> +static inline void
> +_dl_executable_postprocess (struct link_map *main_map,
> +			    const ElfW(Phdr) *phdr, ElfW(Word) phnum)
> +{
> +  /* NB: In static executable, PT_GNU_PROPERTY is processed in target
> +     libc-start.h if it is needed by target.  When ld.so is used, if
> +     a target doesn't need PT_GNU_PROPERTY, _dl_process_pt_gnu_property
> +     is an empty function.  */
> +#ifdef SHARED
> +  /* Process program headers again, but scan them backwards since
> +     PT_GNU_PROPERTY is close to the end of program headers.   */
> +  for (const ElfW(Phdr) *ph = &phdr[phnum]; ph != phdr; --ph)
> +    if (ph[-1].p_type == PT_GNU_PROPERTY)
> +      {
> +	_dl_process_pt_gnu_property (main_map, -1, &ph[-1]);
> +	break;
> +      }
> +#endif
> +
> +  /* If THP state was not yet initialized, the main executable was mapped
> +     by the kernel; in that case this function is the only place that can
> +     apply MADV_HUGEPAGE to the main executable's segments.  Otherwise,
> +     _dl_get_thp_config has already run earlier in dl_main and
> +     _dl_map_segments has just mapped the main executable, so
> +     _dl_postprocess_loadcmd_extra has already done the madvise pass; do
> +     not repeat it here.  */
> +  if (GL(dl_elf_thp_control) != -1)
> +    return;
> +
> +   _dl_get_thp_config ();
> +
> +  /* Return if THP segment load isn't enabled.  */
> +  if (GL(dl_elf_thp_control) <= 0)
> +    return;
> +
> +  /* NB: If DL_MAP_DEFAULT_THP_PAGESIZE is non-zero, dl_thp_mode is set
> +     to thp_mode_madvise.  */
> +  if (DL_MAP_DEFAULT_THP_PAGESIZE == 0
> +      && GL(dl_thp_mode) != thp_mode_madvise)
> +    return;
> +
> +  /* When we get here, the main executable have been mapped in.  Call
> +     madvise with MADV_HUGEPAGE for all THP eligible PT_LOAD segments.  */
> +
> +  const ElfW(Phdr) *ph;
> +
> +  size_t thp_pagesize = GL(dl_elf_thp_pagesize);
> +
> +  /* Call __madvise if offset and address of the PT_LOAD segment are
> +     aligned to THP page size and it is read-only.  */
> +  for (ph = phdr; ph < &phdr[phnum]; ++ph)
> +    if (ph->p_type == PT_LOAD
> +	&& ph->p_memsz >= thp_pagesize
> +	&& ((ph->p_vaddr | ph->p_offset) & (thp_pagesize - 1)) == 0
> +	&& (ph->p_flags & (PF_W | PF_R)) == PF_R)
> +      __madvise ((void *) (main_map->l_addr + ph->p_vaddr),
> +		 ph->p_memsz, MADV_HUGEPAGE);
> +}

Ok.

> diff --git a/sysdeps/unix/sysv/linux/dl-load-post.h b/sysdeps/unix/sysv/linux/dl-load-post.h
> new file mode 100644
> index 0000000000..88467764d4
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/dl-load-post.h
> @@ -0,0 +1,32 @@
> +/* _dl_postprocess_loadcmd_extra.  Linux version.
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   Copyright The GNU Toolchain Authors.
> +   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/>.  */
> +
> +static bool _dl_segment_thp_eligible (const struct loadcmd *, size_t);
> +
> +/* After L has been mapped in, call madvise with MADV_HUGEPAGE for THP
> +   madvise mode if L is THP eligible.  */
> +
> +static inline void
> +_dl_postprocess_loadcmd_extra (struct link_map *l, const struct loadcmd *c)
> +{
> +  if (GL(dl_thp_mode) == thp_mode_madvise
> +      && _dl_segment_thp_eligible (c, GL(dl_elf_thp_pagesize)))
> +    __madvise ((void *) (l->l_addr + c->mapstart),
> +	       c->mapend - c->mapstart, MADV_HUGEPAGE);
> +}

Ok.  Should we warn for LD_DEBUG if the __madvise fails?

> diff --git a/sysdeps/unix/sysv/linux/dl-map-segment-align.c b/sysdeps/unix/sysv/linux/dl-map-segment-align.c
> index a39e74d91b..1260b26c22 100644
> --- a/sysdeps/unix/sysv/linux/dl-map-segment-align.c
> +++ b/sysdeps/unix/sysv/linux/dl-map-segment-align.c
> @@ -17,38 +17,23 @@
>     License along with the GNU C Library; if not, see
>     <https://www.gnu.org/licenses/>.  */
>  
> +#include <ldsodefs.h>
>  #include <dl-map-segment-align.h>
> -#include <dl-tunables.h>
> -#include <hugepages.h>
> +
> +/* Return the alignment of the PT_LOAD segment for THP.  P_ALIGN_MAX is
> +   the maximum p_align value in the PT_LOAD segment.  */
>  
>  ElfW (Addr)
>  _dl_map_segment_align (const struct loadcmd *c, ElfW (Addr) p_align_max)
>  {
> -  static enum thp_mode_t thp_mode = thp_mode_not_supported;
> -  static unsigned long int thp_pagesize;
> +  size_t thp_pagesize = GL(dl_elf_thp_pagesize);
>  
> -  if (TUNABLE_GET (glibc, elf, thp, int32_t, NULL) == 0)
> +  if (GL(dl_elf_thp_control) <= 0 || p_align_max >= thp_pagesize)
>      return p_align_max;
>  
> -  if (__glibc_unlikely (thp_mode == thp_mode_not_supported
> -                        || thp_pagesize == 0))
> -    {
> -      unsigned long int default_thp_pagesize = DL_MAP_DEFAULT_THP_PAGESIZE;
> -      thp_mode = default_thp_pagesize ? thp_mode_always : __get_thp_mode ();
> -      thp_pagesize = default_thp_pagesize ? : __get_thp_size ();
> -    }
> -
> -  /* Aligning load segments that are large enough to the PMD size helps
> -     improve THP eligibility and reduces TLB pressure.
> -     We cap the huge page size at MAX_THP_PAGESIZE to avoid over-aligning
> -     on systems with very large normal pages (like 64K pages with 512M
> -     huge pages). */
> -  if (thp_mode == thp_mode_always
> -      && thp_pagesize <= MAX_THP_PAGESIZE
> -      && ((c->mapstart | c->mapoff) & (thp_pagesize - 1)) == 0
> -      && (c->mapend - c->mapstart) >= thp_pagesize
> -      && p_align_max < thp_pagesize
> -      && (c->prot & PROT_WRITE) == 0)
> +  /* Return true if the segment is THP eligible.  It helps improve THP
> +     eligibility and reduces TLB pressure.  */
> +  if (_dl_segment_thp_eligible (c, thp_pagesize))
>      return thp_pagesize;
>  
>    return p_align_max;

Ok.

> diff --git a/sysdeps/unix/sysv/linux/dl-map-segment-align.h b/sysdeps/unix/sysv/linux/dl-map-segment-align.h
> index d9b05181b7..b904e128d8 100644
> --- a/sysdeps/unix/sysv/linux/dl-map-segment-align.h
> +++ b/sysdeps/unix/sysv/linux/dl-map-segment-align.h
> @@ -19,9 +19,18 @@
>  
>  #include <dl-load.h>
>  
> -#ifndef DL_MAP_DEFAULT_THP_PAGESIZE
> -# define DL_MAP_DEFAULT_THP_PAGESIZE	0
> -#endif
> -
>  extern ElfW (Addr) _dl_map_segment_align
>    (const struct loadcmd *, ElfW (Addr)) attribute_hidden;
> +
> +/* Return true only if the loadcmd C is THP eligible with THP page size
> +   THP_PAGESIZE, which means that it is read-only, its size >= THP page
> +   size, its offset and address of the loadcmd C are aligned to THP page
> +   size.  */
> +
> +static inline bool
> +_dl_segment_thp_eligible (const struct loadcmd *c, size_t thp_pagesize)
> +{
> +  return ((c->prot & PROT_WRITE) == 0
> +	  && (c->mapend - c->mapstart) >= thp_pagesize
> +	  && ((c->mapstart | c->mapoff) & (thp_pagesize - 1)) == 0);
> +}

Ok.

> diff --git a/sysdeps/unix/sysv/linux/ldsodefs.h b/sysdeps/unix/sysv/linux/ldsodefs.h
> index c63b649432..e39d9afe34 100644
> --- a/sysdeps/unix/sysv/linux/ldsodefs.h
> +++ b/sysdeps/unix/sysv/linux/ldsodefs.h
> @@ -21,6 +21,9 @@
>  /* We have the auxiliary vector.  */
>  #define HAVE_AUX_VECTOR
>  
> +/* We have transparent huge page.  */
> +#define HAVE_THP
> +
>  /* Get the real definitions.  */
>  #include_next <ldsodefs.h>
>  
> diff --git a/sysdeps/unix/sysv/linux/loongarch/Makefile b/sysdeps/unix/sysv/linux/loongarch/Makefile> index 0d5f087862..d5beb62440 100644
> --- a/sysdeps/unix/sysv/linux/loongarch/Makefile
> +++ b/sysdeps/unix/sysv/linux/loongarch/Makefile
> @@ -12,3 +12,6 @@ abi-ilp32s-condition	:= __WORDSIZE == 32 && defined __loongarch_soft_float
>  abi-ilp32d-condition	:= __WORDSIZE == 32 && defined __loongarch_double_float
>  abi-lp64s-condition	:= __WORDSIZE == 64 && defined __loongarch_soft_float
>  abi-lp64d-condition	:= __WORDSIZE == 64 && defined __loongarch_double_float
> +
> +# Align THP tests to 32MB.
> +THP-PAGE-SIZE = 0x2000000
> diff --git a/sysdeps/unix/sysv/linux/loongarch/lp64/dl-map-segment-align.h b/sysdeps/unix/sysv/linux/loongarch/lp64/hugepages.h
> similarity index 90%
> rename from sysdeps/unix/sysv/linux/loongarch/lp64/dl-map-segment-align.h
> rename to sysdeps/unix/sysv/linux/loongarch/lp64/hugepages.h
> index c51ee4ac47..30252a9b86 100644
> --- a/sysdeps/unix/sysv/linux/loongarch/lp64/dl-map-segment-align.h
> +++ b/sysdeps/unix/sysv/linux/loongarch/lp64/hugepages.h
> @@ -1,4 +1,4 @@
> -/* _dl_map_segment_align.  LoongArch64 Linux version.
> +/* Huge Page support.  LoongArch64 Linux version.
>     Copyright (C) 2026 Free Software Foundation, Inc.
>     Copyright The GNU Toolchain Authors.
>     This file is part of the GNU C Library.
> @@ -19,4 +19,4 @@
>  
>  #define DL_MAP_DEFAULT_THP_PAGESIZE (32 * 1024 * 1024)
>  
> -#include_next <dl-map-segment-align.h>
> +#include_next <hugepages.h>

Ok.

> diff --git a/sysdeps/unix/sysv/linux/strace-tst-thp.sh b/sysdeps/unix/sysv/linux/strace-tst-thp.sh
> new file mode 100644
> index 0000000000..3ffb256c71
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/strace-tst-thp.sh
> @@ -0,0 +1,80 @@
> +#!/bin/bash
> +# Run THP test under strace to verify control of the THP segment load.
> +# 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/>.
> +
> +set -e
> +
> +case x"${1}" in
> +*-static)
> +  rtld=
> +  test_wrapper_env="$2"
> +  run_program_env="$3"
> +  library_path=
> +  test_prog="$1"
> +  ;;
> +*)
> +  rtld="$1"
> +  test_wrapper_env="$2"
> +  run_program_env="$3"
> +  library_path="$4"
> +  test_prog="$5"
> +  ;;
> +esac
> +
> +
> +if test x"${rtld}" = x; then
> +  cmd="${test_wrapper_env} ${run_program_env} strace ${test_prog}"
> +else
> +  cmd="${test_wrapper_env} ${run_program_env} strace ${rtld} \
> +       --library-path ${library_path} ${test_prog}"
> +fi
> +
> +TIMEOUTFACTOR=${TIMEOUTFACTOR:-1}
> +
> +case x"${run_program_env}" in
> +*glibc.elf.thp=1*)
> +  strace_expected=yes
> +  ;;
> +*)
> +  strace_expected=no
> +  ;;
> +esac
> +
> +# Verify strace is not just present, but works in this environment.  If
> +# not, skip the test.
> +/bin/sh -c \
> + "${test_wrapper_env} ${run_program_env} \
> +  strace -e trace=none -- /bin/true" > /dev/null 2>&1 || exit 77
> +
> +# Finally the actual test inside the test environment, using the just
> +# build ld.so and new libraries to run the THP test under strace.
> +if /bin/sh -c \
> +  "timeout -k 4 $((3*$TIMEOUTFACTOR)) ${cmd} --direct 2>&1 \
> +   | grep -E \"madvise.*, MADV_HUGEPAGE\""; then
> +  if test ${strace_expected} = yes; then
> +    exit 0
> +  else
> +    exit 1
> +  fi
> +else
> +  if test ${strace_expected} = no; then
> +    exit 0
> +  else
> +    exit 1
> +  fi
> +fi

Ok.

> diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c
> new file mode 100644
> index 0000000000..3fd01e9bfe
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c
> @@ -0,0 +1,19 @@
> +/* Test PDE with THP segment load linked with -Wl,-z,noseparate-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
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include "tst-thp-1.c"

Ok.

> diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c
> new file mode 100644
> index 0000000000..d0ae0f1ff0
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c
> @@ -0,0 +1,19 @@
> +/* Test static with THP segment load linked with -Wl,-z,noseparate-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
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include "tst-thp-1.c"


Ok.

> diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c
> new file mode 100644
> index 0000000000..5eb1e005ed
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c
> @@ -0,0 +1,19 @@
> +/* Test THP segment load linked with -Wl,-z,noseparate-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
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include "tst-thp-1.c"


Ok.

> diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-pde.c b/sysdeps/unix/sysv/linux/tst-thp-1-pde.c
> new file mode 100644
> index 0000000000..d854dd43da
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-thp-1-pde.c
> @@ -0,0 +1,19 @@
> +/* Test PDE with THP segment load linked with -Wl,-z,separate-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
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include "tst-thp-1.c"
> diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-static.c b/sysdeps/unix/sysv/linux/tst-thp-1-static.c
> new file mode 100644
> index 0000000000..66d7e12954
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-thp-1-static.c
> @@ -0,0 +1,19 @@
> +/* Test static with THP segment load linked with -Wl,-z,separate-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
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include "tst-thp-1.c"
> diff --git a/sysdeps/unix/sysv/linux/tst-thp-1.c b/sysdeps/unix/sysv/linux/tst-thp-1.c
> new file mode 100644
> index 0000000000..49eea7069c
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-thp-1.c
> @@ -0,0 +1,28 @@
> +/* Test THP segment load linked with -Wl,-z,separate-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
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include "tst-thp-align-check.h"
> +
> +static int
> +do_test (void)
> +{
> +  check_align ("tst-thp-1");
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>


Ok.

> diff --git a/sysdeps/unix/sysv/linux/tst-thp-align-check.h b/sysdeps/unix/sysv/linux/tst-thp-align-check.h
> new file mode 100644
> index 0000000000..8f1efc0ef1
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-thp-align-check.h
> @@ -0,0 +1,124 @@
> +/* Test the THP compatible alignment of PT_LOAD segments.
> +
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +
> +   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 <fcntl.h>
> +#include <stdint.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <intprops.h>
> +#include <inttypes.h>
> +#include <support/check.h>
> +#include <support/xstdio.h>
> +#include <support/xunistd.h>
> +#undef attribute_hidden
> +#define attribute_hidden
> +#include <hugepages.h>	/* For enum thp_mode_t and MAX_THP_PAGESIZE.  */
> +#undef attribute_hidden
> +
> +static unsigned long int
> +get_thp_size (void)
> +{
> +  int fd = open ("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size",
> +                 O_RDONLY, 0);
> +  if (fd == -1)
> +    return 0;
> +
> +  char str[INT_BUFSIZE_BOUND (unsigned long int)];
> +  ssize_t s = read (fd, str, sizeof (str));
> +  close (fd);
> +  if (s < 0)	
> +    return 0;
> +
> +  unsigned long int r = 0;
> +  for (ssize_t i = 0; i < s; i++)
> +    {
> +      if (str[i] == '\n')
> +    break;
> +      r *= 10;
> +      r += str[i] - '0';
> +    }
> +  return r;
> +}
> +
> +static enum thp_mode_t
> +get_thp_mode (void)
> +{
> +  int fd = open ("/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY, 0);
> +  if (fd == -1)
> +    return thp_mode_not_supported;
> +
> +  static const char mode_always[]  = "[always] madvise never\n";
> +  static const char mode_madvise[] = "always [madvise] never\n";
> +  static const char mode_never[]   = "always madvise [never]\n";
> +
> +  char str[sizeof(mode_always)];
> +  ssize_t s = read (fd, str, sizeof (str));

Move the the close (fd) here.  

And now that we have support/support_check_hugetlb.c, maybe we can move the 
get_thp_mode to libsupport, along with get_thp_size.

> +  if (s >= sizeof str || s < 0)
> +    return thp_mode_not_supported;
> +  str[s] = '\0';
> +  close (fd);
> +
> +  if (s == sizeof (mode_always) - 1)
> +    {
> +      if (strcmp (str, mode_always) == 0)
> +    return thp_mode_always;
> +      else if (strcmp (str, mode_madvise) == 0)
> +    return thp_mode_madvise;
> +      else if (strcmp (str, mode_never) == 0)
> +    return thp_mode_never;
> +    }
> +  return thp_mode_not_supported;
> +}
> +
> +static void
> +check_align (const char *name)
> +{
> +  unsigned long int thp_size = get_thp_size ();
> +  enum thp_mode_t thp_mode = get_thp_mode ();
> +
> +  if (thp_size == 0)
> +    FAIL_UNSUPPORTED ("unable to get THP size.\n");
> +
> +  if (thp_size > MAX_THP_PAGESIZE)
> +    FAIL_UNSUPPORTED ("THP size exceeds MAX_THP_PAGESIZE.\n");
> +
> +  if (thp_mode != thp_mode_always && thp_mode != thp_mode_madvise)
> +    FAIL_UNSUPPORTED ("THP mode is not always nor madvise.\n");
> +
> +  FILE *f = xfopen ("/proc/self/maps", "r");
> +  char *line = NULL;
> +  size_t len;
> +
> +  while (xgetline (&line, &len, f))
> +    {
> +      uintptr_t from, to;
> +      char *prot = NULL, *path = NULL;
> +      int r = sscanf (line, "%" SCNxPTR "-%" SCNxPTR "%ms%*s%*s%*s%ms",
> +                      &from, &to, &prot, &path);
> +
> +      TEST_VERIFY (r == 3 || r == 4);

When sscanf returns r == 3 (executable mapping with no pathname), path is NULL; 
strstr (path, name) would deref NULL. Not really an issue because most tests
won't have an exacutable mapping (specially on Linux), but I think we should
add a 'path != NULL' just to be safe. 

> +
> +      if (strstr (prot, "x") && strstr (path, name))
> +        TEST_COMPARE (from % thp_size, 0);
> +
> +      free (path);

And add free (prot) as well.

> +    }
> +
> +  free (line);
> +  xfclose (f);
> +}
> diff --git a/sysdeps/unix/sysv/linux/tst-thp-align.c b/sysdeps/unix/sysv/linux/tst-thp-align.c
> index 0b3f18e000..2e44109ba6 100644
> --- a/sysdeps/unix/sysv/linux/tst-thp-align.c
> +++ b/sysdeps/unix/sysv/linux/tst-thp-align.c
> @@ -16,129 +16,10 @@
>     License along with the GNU C Library; if not, see
>     <https://www.gnu.org/licenses/>.  */
>  
> -#include <fcntl.h>
> -#include <stdint.h>
> -#include <stdlib.h>
> -#include <string.h>
> -#include <intprops.h>
> -#include <inttypes.h>
> -#include <support/check.h>
>  #include <support/xdlfcn.h>
> -#include <support/xstdio.h>
> -#include <support/xunistd.h>
> +#include "tst-thp-align-check.h"
>  
>  #define THP_SIZE_MOD_NAME "tst-thp-size-mod.so"
> -#define MAX_THP_PAGESIZE (32 * 1024 * 1024)
> -
> -enum thp_mode_t
> -{
> -  thp_mode_always,
> -  thp_mode_madvise,
> -  thp_mode_never,
> -  thp_mode_not_supported
> -};
> -
> -static unsigned long int
> -get_thp_size (void)
> -{
> -  int fd = open ("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size",
> -                 O_RDONLY, 0);
> -  if (fd == -1)
> -    return 0;
> -
> -  char str[INT_BUFSIZE_BOUND (unsigned long int)];
> -  ssize_t s = read (fd, str, sizeof (str));
> -  close (fd);
> -  if (s < 0)
> -    return 0;
> -
> -  unsigned long int r = 0;
> -  for (ssize_t i = 0; i < s; i++)
> -    {
> -      if (str[i] == '\n')
> -    break;
> -      r *= 10;
> -      r += str[i] - '0';
> -    }
> -  return r;
> -}
> -
> -static enum thp_mode_t
> -get_thp_mode (void)
> -{
> -  int fd = open ("/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY, 0);
> -  if (fd == -1)
> -    return thp_mode_not_supported;
> -
> -  static const char mode_always[]  = "[always] madvise never\n";
> -  static const char mode_madvise[] = "always [madvise] never\n";
> -  static const char mode_never[]   = "always madvise [never]\n";
> -
> -  char str[sizeof(mode_always)];
> -  ssize_t s = read (fd, str, sizeof (str));
> -  if (s >= sizeof str || s < 0)
> -    return thp_mode_not_supported;
> -  str[s] = '\0';
> -  close (fd);
> -
> -  if (s == sizeof (mode_always) - 1)
> -    {
> -      if (strcmp (str, mode_always) == 0)
> -    return thp_mode_always;
> -      else if (strcmp (str, mode_madvise) == 0)
> -    return thp_mode_madvise;
> -      else if (strcmp (str, mode_never) == 0)
> -    return thp_mode_never;
> -    }
> -  return thp_mode_not_supported;
> -}
> -
> -static void
> -check_align (void)
> -{
> -  unsigned long int thp_size = get_thp_size ();
> -  enum thp_mode_t thp_mode = get_thp_mode ();
> -
> -  if (thp_size == 0)
> -    {
> -      FAIL_UNSUPPORTED ("unable to get THP size.\n");
> -      return;
> -    }
> -
> -  if (thp_size > MAX_THP_PAGESIZE)
> -    {
> -      FAIL_UNSUPPORTED ("THP size exceeds MAX_THP_PAGESIZE.\n");
> -      return;
> -    }
> -
> -  if (thp_mode != thp_mode_always)
> -    {
> -      FAIL_UNSUPPORTED ("THP mode is not always.\n");
> -      return;
> -    }
> -
> -  FILE *f = xfopen ("/proc/self/maps", "r");
> -  char *line = NULL;
> -  size_t len;
> -
> -  while (xgetline (&line, &len, f))
> -    {
> -      uintptr_t from, to;
> -      char *prot = NULL, *path = NULL;
> -      int r = sscanf (line, "%" SCNxPTR "-%" SCNxPTR "%ms%*s%*s%*s%ms",
> -                      &from, &to, &prot, &path);
> -
> -      TEST_VERIFY (r == 3 || r == 4);
> -
> -      if (strstr (prot, "x") && strstr (path, THP_SIZE_MOD_NAME))
> -        TEST_COMPARE (from % thp_size, 0);
> -
> -      free (path);
> -    }
> -
> -  free (line);
> -  xfclose (f);
> -}
>  
>  static int
>  do_test (void)
> @@ -146,7 +27,7 @@ do_test (void)
>    void *dl;
>  
>    dl = xdlopen (THP_SIZE_MOD_NAME, RTLD_NOW);
> -  check_align ();
> +  check_align (THP_SIZE_MOD_NAME);
>    xdlclose (dl);
>  
>    return 0;


Ok.

> diff --git a/sysdeps/unix/sysv/linux/x86/hugepages.h b/sysdeps/unix/sysv/linux/x86/hugepages.h
> new file mode 100644
> index 0000000000..1a8c370969
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/x86/hugepages.h
> @@ -0,0 +1,22 @@
> +/* Huge Page support.  Linux/x86 version.
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   Copyright The GNU Toolchain Authors.
> +   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/>.  */
> +
> +#define DL_MAP_DEFAULT_THP_PAGESIZE (2 * 1024 * 1024)
> +
> +#include_next <hugepages.h>
> -- 
> 2.54.0
>
  
H.J. Lu May 29, 2026, 9 p.m. UTC | #2
On Fri, May 29, 2026 at 11:27:58AM -0300, Adhemerval Zanella Netto wrote:
> On 28/05/26 01:26, H.J. Lu wrote:
> > Changes in v11:
> > 
> > 1. Build THP PDE tests with $(load-address-ldflag)=$(THP-PAGE-SIZE) to work
> > around:
> > 
> > https://sourceware.org/bugzilla/show_bug.cgi?id=34184
> > 
> > 2. Update strace-tst-thp.sh to run static THP tests directly.
> > 
> > From 75a068e17c8104beb1f5a982fa3330cc2b3ef06b Mon Sep 17 00:00:00 2001
> > From: "H.J. Lu" <hjl.tools@gmail.com>
> > Date: Mon, 13 Apr 2026 08:23:05 +0800
> > Subject: [PATCH v11] elf: Support THP segment load with madvise enabled THP
> > 
> > The current THP segment load approach works only when THP is enabled
> > with always in the kernel.  If THP is enabled with madvise in the
> > kernel, to enable THP segment load in an application, madvise should
> > be called with MADV_HUGEPAGE on all THP eligible PT_LOAD segments:
> > 
> > 1. Define DL_MAP_DEFAULT_THP_PAGESIZE in hugepages.h and default it to 0.
> > If DL_MAP_DEFAULT_THP_PAGESIZE is defined, assume kernel THP madvise mode.
> > If kernel THP mode is always or never, there is an extra madvise call
> > which has no impact.  DL_MAP_DEFAULT_THP_PAGESIZE is defined for x86 and
> > 64-bit loongarch.
> > 2. Update _dl_map_segment_align to support madvise THP mode.  This fixes
> > BZ #34079.
> > 3. Call _dl_executable_postprocess in rtld_setup_main_map for dynamic
> > executables and in LIBC_START_MAIN for static executables, which calls
> > madvise with MADV_HUGEPAGE on all THP eligible PT_LOAD segments in
> > executable.  This fixes BZ #34080 for both dynamic and static executables.
> > 4. Call _dl_postprocess_loadcmd_extra in _dl_postprocess_loadcmd, which
> > calls madvise with MADV_HUGEPAGE on all THP eligible PT_LOAD segments
> > when loading an object after they have been mapped in.  This fixes
> > BZ #34080 for shared objects.
> > 5. Set the maximum page alignment on THP tests to THP page size as the
> > default maximum page alignment may be smaller than THP page size.
> > 6. Add tests to verify that large executable PT_LOAD segments in
> > executables are mapped at addresses aligned to THP page size when the
> > kernel is configured to use THP in "always" mode or "madvise" mode by
> > inspecting /proc/self/maps to check that the mapping address is aligned
> > to THP page size reported by the kernel.  Also verify that madvise is
> > called with MADV_HUGEPAGE when the glibc tunable glibc.elf.thp=1 is used
> > and madvise isn't called with MADV_HUGEPAGE when the glibc tunable
> > glibc.elf.thp=0 is used.
> > 
> > Skip these tests if THP page size cannot be determined or if THP is not
> > enabled in "always" mode nor "madvise" mode.
> > 
> > Quote WANG Rui <wangrui@loongson.cn>:
> 
> Ok, I have done a full review this time, some comments below.
> 
> > 
> > From benchmarking a clang build of the Linux kernel on x86_64 with
> > your patch in THP madvise mode, I observed that iTLB misses were
> > reduced, similar to what we see in THP always mode.
> > 
> > NB: Some THP tests fail on arm due to limitations of arm32 kABI:
> > 
> > https://sourceware.org/bugzilla/show_bug.cgi?id=34096
> > 
> > Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
> 
> There are still failures on RISC-V, but theses are straightforward to fix:
> 
> FAIL: elf/tst-thp-1-no-s-code-static
> FAIL: elf/tst-thp-1-static
> 
> The _dl_executable_postprocess is issued before IFUNC relocation step 
> (_dl_relocate_static_pie_ifunc() / ARCH_SETUP_IREL()); and the function
> calls strcmp through __get_thp_mode.  RISC-V now has strcmp through ifunc
> so it is just a matter to update its dl-symbol-redir-func.h:
> 
> ---
> diff --git a/sysdeps/riscv/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/riscv/multiarch/dl-symbol-redir-ifunc.h
> index c6f2aacd17b..f0520496ce5 100644
> --- a/sysdeps/riscv/multiarch/dl-symbol-redir-ifunc.h
> +++ b/sysdeps/riscv/multiarch/dl-symbol-redir-ifunc.h
> @@ -23,6 +23,7 @@
>  asm ("memcpy = __memcpy_generic");
>  asm ("memset = __memset_generic");
>  asm ("strlen = __strlen_generic");
> +asm ("strcmp = __strcmp_generic");
>  #endif
> 
>  #endif
> diff --git a/sysdeps/unix/sysv/linux/hugepages.c b/sysdeps/unix/sysv/linux/hugepages.c
> index 46e05151a3c..8c886406914 100644
> --- a/sysdeps/unix/sysv/linux/hugepages.c
> +++ b/sysdeps/unix/sysv/linux/hugepages.c
> @@ -21,6 +21,7 @@
>  #include <hugepages.h>
>  #include <not-cancel.h>
>  #include <sys/mman.h>
> +#include <dl-symbol-redir-ifunc.h>
> 
>  unsigned long int
>  __get_thp_size (void)
> ---
> 
> Another possibility, which would require slight less maintability (to
> avoid each ABI to update its dl-symbol-redir-ifunc.h) is to move
> _dl_executable_postprocess after ARCH_SETUP_IREL:
> 
> ---
> diff --git a/csu/libc-start.c b/csu/libc-start.c
> index bb106b524aa..508f41be71a 100644
> --- a/csu/libc-start.c
> +++ b/csu/libc-start.c
> @@ -301,9 +301,6 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
>    __pointer_chk_guard_local = pointer_chk_guard;
>  # endif
> 
> -  struct link_map *main_map = _dl_get_dl_main_map ();
> -  _dl_executable_postprocess (main_map, GL(dl_phdr), GL(dl_phnum));
> -
>    /* Now that the TCB, canary, and pointer guard are in place, run the
>       deferred IFUNC relocations.  For non-PIE static binaries this is
>       ARCH_SETUP_IREL (apply_irel); for static-pie it is the IRELATIVE
> @@ -311,6 +308,11 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
>    _dl_relocate_static_pie_ifunc ();
>    ARCH_SETUP_IREL ();
> 
> +  /* This must run after the IFUNC relocations: _dl_executable_postprocess may
> +     reach IFUNC-resolved routines.  */
> +  struct link_map *main_map = _dl_get_dl_main_map ();
> +  _dl_executable_postprocess (main_map, GL(dl_phdr), GL(dl_phnum));
> +
>    /* Initialize libpthread if linked in.  */
>    if (__pthread_initialize_minimal != NULL)
>      __pthread_initialize_minimal ();
> ---
> 
> I am slight inclined to suggest the second option.
> 

Fixed in v12.

> 
> And I also check with a clang based with clang 22.1.0 on x86_64, and it triggers
> some regressions:
> 
> FAIL: elf/tst-thp-1-no-s-code
> FAIL: elf/tst-thp-1-no-s-code-pde
> FAIL: elf/tst-thp-1-no-s-code-static
> 
> It seems that lld and GNU ld interpret -z noseparate-code differently:
> 
>  * GNU ld collapses the read-only and executable regions into a single R E PT_LOAD 
>    segment, which begins at the 2 MB-aligned text base.  So the executable mapping 
>    is 2 MB-aligned.
> 
>  * LLD keeps the executable code as its own PT_LOAD and only drops the inter-segment 
>    alignment gap that separate-code would insert. It places that segment at a virtual 
>    address merely congruent to its file offset modulo max-page-size - not rounded up 
>    to a 2 MB boundary.
> 
> For the failing binary elf/tst-thp-1-no-s-code, the R E segment is at VirtAddr
> *0x202d40* and a runtime the kernel maps it page-rounded to base + 0x202000, so:
> 
>   0x202000 % 0x200000 (2MB) = 0x2000   != 0
> 
> This is a linker layout difference, not a glibc runtime bug. I think it would be
> better for now to:
> 
> ifeq (yes,$(with-lld))
> test-xfail-tst-thp-1-no-s-code = yes
> test-xfail-tst-thp-1-no-s-code-pde = yes
> test-xfail-tst-thp-1-no-s-code-static = yes
> endif

Actually, -z noseparate-code is a nop for lld.  I added

ifeq (yes,$(with-lld))
# -z noseparate-code is a nop for lld.  It doesn't create an executable
# segment placed at 0 file offset.
test-xfail-tst-thp-1-no-s-code = yes
test-xfail-tst-thp-1-no-s-code-pde = yes
test-xfail-tst-thp-1-no-s-code-static = yes
endif

> 
> > ---
> >  csu/libc-start.c                              |   4 +
> >  elf/dl-load.h                                 |   3 +
> >  elf/dl-support.c                              |   6 +
> >  elf/rtld.c                                    |  17 +-
> >  sysdeps/generic/dl-exec-post.h                |  34 ++++
> >  sysdeps/generic/dl-load-post.h                |  23 +++
> >  sysdeps/generic/hugepages.h                   |   8 +-
> >  sysdeps/generic/ldsodefs.h                    |  12 ++
> >  sysdeps/unix/sysv/linux/Makefile              | 165 +++++++++++++++++-
> >  sysdeps/unix/sysv/linux/arm/Makefile          |   7 +
> >  sysdeps/unix/sysv/linux/dl-exec-post.h        | 126 +++++++++++++
> >  sysdeps/unix/sysv/linux/dl-load-post.h        |  32 ++++
> >  .../unix/sysv/linux/dl-map-segment-align.c    |  33 +---
> >  .../unix/sysv/linux/dl-map-segment-align.h    |  17 +-
> >  sysdeps/unix/sysv/linux/ldsodefs.h            |   3 +
> >  sysdeps/unix/sysv/linux/loongarch/Makefile    |   3 +
> >  .../{dl-map-segment-align.h => hugepages.h}   |   4 +-
> >  sysdeps/unix/sysv/linux/strace-tst-thp.sh     |  80 +++++++++
> >  .../unix/sysv/linux/tst-thp-1-no-s-code-pde.c |  19 ++
> >  .../sysv/linux/tst-thp-1-no-s-code-static.c   |  19 ++
> >  sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c |  19 ++
> >  sysdeps/unix/sysv/linux/tst-thp-1-pde.c       |  19 ++
> >  sysdeps/unix/sysv/linux/tst-thp-1-static.c    |  19 ++
> >  sysdeps/unix/sysv/linux/tst-thp-1.c           |  28 +++
> >  sysdeps/unix/sysv/linux/tst-thp-align-check.h | 124 +++++++++++++
> >  sysdeps/unix/sysv/linux/tst-thp-align.c       | 123 +------------
> >  sysdeps/unix/sysv/linux/x86/hugepages.h       |  22 +++
> >  27 files changed, 805 insertions(+), 164 deletions(-)
> >  create mode 100644 sysdeps/generic/dl-exec-post.h
> >  create mode 100644 sysdeps/generic/dl-load-post.h
> >  create mode 100644 sysdeps/unix/sysv/linux/dl-exec-post.h
> >  create mode 100644 sysdeps/unix/sysv/linux/dl-load-post.h
> >  rename sysdeps/unix/sysv/linux/loongarch/lp64/{dl-map-segment-align.h => hugepages.h} (90%)
> >  create mode 100644 sysdeps/unix/sysv/linux/strace-tst-thp.sh
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-pde.c
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-static.c
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1.c
> >  create mode 100644 sysdeps/unix/sysv/linux/tst-thp-align-check.h
> >  create mode 100644 sysdeps/unix/sysv/linux/x86/hugepages.h
> > 
> > diff --git a/csu/libc-start.c b/csu/libc-start.c
> > index 03d770ef15..bb106b524a 100644
> > --- a/csu/libc-start.c
> > +++ b/csu/libc-start.c
> > @@ -205,6 +205,7 @@ call_fini (void *unused)
> >  #endif /* !SHARED */
> >  
> >  #include <libc-start.h>
> > +#include <dl-exec-post.h>
> >  
> >  STATIC int LIBC_START_MAIN (int (*main) (int, char **, char **
> >  					 MAIN_AUXVEC_DECL),
> > @@ -300,6 +301,9 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
> >    __pointer_chk_guard_local = pointer_chk_guard;
> >  # endif
> >  
> > +  struct link_map *main_map = _dl_get_dl_main_map ();
> > +  _dl_executable_postprocess (main_map, GL(dl_phdr), GL(dl_phnum));
> > +
> >    /* Now that the TCB, canary, and pointer guard are in place, run the
> >       deferred IFUNC relocations.  For non-PIE static binaries this is
> >       ARCH_SETUP_IREL (apply_irel); for static-pie it is the IRELATIVE
> 
> Maybe we should move the _dl_executable_postprocess after IFUNC processing, as above.

Done.

> 
> > diff --git a/elf/dl-load.h b/elf/dl-load.h
> > index 80ae5db4b3..84b82e183d 100644
> > --- a/elf/dl-load.h
> > +++ b/elf/dl-load.h
> > @@ -112,6 +112,7 @@ struct loadcmd
> >    int prot;                             /* PROT_* bits.  */
> >  };
> >  
> > +#include <dl-load-post.h>
> >  
> >  /* Iterator for program header segments.  Initialize with
> >     _dl_pt_load_iterator_init, then either walk PT_LOAD segments via
> > @@ -215,6 +216,8 @@ _dl_postprocess_loadcmd (struct link_map *l, const ElfW(Ehdr) *header,
> >      /* Found the program header in this segment.  */
> >      l->l_phdr = (void *) (uintptr_t) (c->mapstart + header->e_phoff
> >                                        - c->mapoff);
> > +
> > +  _dl_postprocess_loadcmd_extra (l, c);
> >  }
> >  
> >  
> 
> Ok.
> 
> > diff --git a/elf/dl-support.c b/elf/dl-support.c
> > index 0508d6113b..a8114de003 100644
> > --- a/elf/dl-support.c
> > +++ b/elf/dl-support.c
> > @@ -179,6 +179,12 @@ int _dl_stack_cache_lock;
> >  #endif
> >  struct dl_scope_free_list *_dl_scope_free_list;
> >  
> > +#ifdef HAVE_THP
> > +int _dl_elf_thp_control = -1;
> > +enum thp_mode_t _dl_thp_mode;
> > +size_t _dl_elf_thp_pagesize;
> > +#endif
> > +
> >  #ifdef NEED_DL_SYSINFO
> >  /* Needed for improved syscall handling on at least x86/Linux.  NB: Don't
> >     initialize it here to avoid RELATIVE relocation in static PIE.  */
> 
> Although we already have the Linux ldsodefs.h, I think a better strategy would
> to move the HAVE_THP to a different header (ldsodefs-arch.h or something),
> so we can always have a definitions and use if "#if" instead of "#ifdef"
> (so missing inclusion will trigger an error instead of silent use a
> !HAVE_THP).

There are a few places where HAVE_THP is checked:

elf/dl-support.c:#ifdef HAVE_THP
elf/rtld.c:#ifdef HAVE_THP
elf/rtld.c:#ifdef HAVE_THP
sysdeps/generic/ldsodefs.h:#ifdef HAVE_THP
sysdeps/unix/sysv/linux/ldsodefs.h:#define HAVE_THP

Adding a header doesn't solve the issue, just like for similar macros,
like HAVE_AUX_VECTOR:

csu/libc-start.c:# ifdef HAVE_AUX_VECTOR
elf/dl-support.c:#ifdef HAVE_AUX_VECTOR
elf/rtld.c:#ifdef HAVE_AUX_VECTOR
elf/rtld.c:#ifdef HAVE_AUX_VECTOR
elf/rtld_static_init.c:#ifdef HAVE_AUX_VECTOR
misc/getauxval.c:#ifdef HAVE_AUX_VECTOR
misc/getauxval.c:#ifdef HAVE_AUX_VECTOR
sysdeps/generic/ldsodefs.h:#ifdef HAVE_AUX_VECTOR
sysdeps/unix/sysv/linux/ldsodefs.h:#define HAVE_AUX_VECTOR
sysdeps/unix/sysv/linux/powerpc/libc-start.c:#ifdef HAVE_AUX_VECTOR

One unrelated question, why is HAVE_AUX_VECTOR checked for powerpc?

> 
> > diff --git a/elf/rtld.c b/elf/rtld.c
> > index 12e1b4dd71..e1ec899756 100644
> > --- a/elf/rtld.c
> > +++ b/elf/rtld.c
> > @@ -52,6 +52,7 @@
> >  #include <dl-find_object.h>
> >  #include <dl-audit-check.h>
> >  #include <dl-call_tls_init_tp.h>
> > +#include <dl-exec-post.h>
> >  
> >  #include <assert.h>
> >  
> > @@ -323,6 +324,9 @@ struct rtld_global _rtld_global =
> >      /* Generally the default presumption without further information is an
> >       * executable stack but this is not true for all platforms.  */
> >      ._dl_stack_prot_flags = DEFAULT_STACK_PROT_PERMS,
> > +#ifdef HAVE_THP
> > +    ._dl_elf_thp_control = -1,
> > +#endif
> >  #ifdef _LIBC_REENTRANT
> >      ._dl_load_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
> >      ._dl_load_write_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
> > @@ -1209,14 +1213,8 @@ rtld_setup_main_map (struct link_map *main_map)
> >  	main_map->l_relro_size = ph->p_memsz;
> >  	break;
> >        }
> > -  /* Process program headers again, but scan them backwards since
> > -     PT_GNU_PROPERTY is close to the end of program headers.   */
> > -  for (const ElfW(Phdr) *ph = &phdr[phnum]; ph != phdr; --ph)
> > -    if (ph[-1].p_type == PT_GNU_PROPERTY)
> > -      {
> > -	_dl_process_pt_gnu_property (main_map, -1, &ph[-1]);
> > -	break;
> > -      }
> > +
> > +  _dl_executable_postprocess (main_map, phdr, phnum);
> >  
> >    /* Adjust the address of the TLS initialization image in case
> >       the executable is actually an ET_DYN object.  */
> > @@ -1589,6 +1587,9 @@ dl_main (const ElfW(Phdr) *phdr,
> >  	{
> >  	  RTLD_TIMING_VAR (start);
> >  	  rtld_timer_start (&start);
> > +#ifdef HAVE_THP
> > +	  _dl_get_thp_config ();
> > +#endif
> >  	  _dl_map_object (NULL, rtld_progname, lt_executable, 0,
> >  			  __RTLD_OPENEXEC, LM_ID_BASE);
> >  	  rtld_timer_stop (&load_time, start);
> 
> Ok.
> 
> > diff --git a/sysdeps/generic/dl-exec-post.h b/sysdeps/generic/dl-exec-post.h
> > new file mode 100644
> > index 0000000000..f5dcdc093a
> > --- /dev/null
> > +++ b/sysdeps/generic/dl-exec-post.h
> > @@ -0,0 +1,34 @@
> > +/* _dl_executable_postprocess.  Generic version.
> > +   Copyright (C) 2026 Free Software Foundation, Inc.
> > +   Copyright The GNU Toolchain Authors.
> > +   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/>.  */
> > +
> > +static inline void
> > +_dl_executable_postprocess (struct link_map *main_map,
> > +			    const ElfW(Phdr) *phdr, ElfW(Word) phnum)
> > +{
> > +#ifdef SHARED
> > +  /* Process program headers again, but scan them backwards since
> > +     PT_GNU_PROPERTY is close to the end of program headers.   */
> > +  for (const ElfW(Phdr) *ph = &phdr[phnum]; ph != phdr; --ph)
> > +    if (ph[-1].p_type == PT_GNU_PROPERTY)
> > +      {
> > +	_dl_process_pt_gnu_property (main_map, -1, &ph[-1]);
> > +	break;
> > +      }
> > +#endif
> > +}
> 
> Ok.
> 
> > diff --git a/sysdeps/generic/dl-load-post.h b/sysdeps/generic/dl-load-post.h
> > new file mode 100644
> > index 0000000000..1a6d205e33
> > --- /dev/null
> > +++ b/sysdeps/generic/dl-load-post.h
> > @@ -0,0 +1,23 @@
> > +/* _dl_postprocess_loadcmd_extra.  Generic version.
> > +   Copyright (C) 2026 Free Software Foundation, Inc.
> > +   Copyright The GNU Toolchain Authors.
> > +   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/>.  */
> > +
> > +static inline void
> > +_dl_postprocess_loadcmd_extra (struct link_map *l, const struct loadcmd *c)
> > +{
> > +}
> 
> Ok.
> 
> > diff --git a/sysdeps/generic/hugepages.h b/sysdeps/generic/hugepages.h
> > index 5fc9b5c8de..f7f4957e79 100644
> > --- a/sysdeps/generic/hugepages.h
> > +++ b/sysdeps/generic/hugepages.h
> > @@ -26,10 +26,10 @@ unsigned long int __get_thp_size (void) attribute_hidden;
> >  
> >  enum thp_mode_t
> >  {
> > +  thp_mode_not_supported = 0,
> >    thp_mode_always,
> >    thp_mode_madvise,
> > -  thp_mode_never,
> > -  thp_mode_not_supported
> > +  thp_mode_never
> >  };
> >  
> >  enum thp_mode_t __get_thp_mode (void) attribute_hidden;
> > @@ -45,6 +45,10 @@ void __get_hugepage_config (size_t requested, size_t *pagesize, int *flags)
> >  # define MALLOC_DEFAULT_THP_PAGESIZE	0
> >  #endif
> >  
> > +#ifndef DL_MAP_DEFAULT_THP_PAGESIZE
> > +# define DL_MAP_DEFAULT_THP_PAGESIZE	0
> > +#endif
> > +
> >  #ifndef MAX_THP_PAGESIZE
> >  # define MAX_THP_PAGESIZE	(32 * 1024 * 1024)
> >  #endif
> 
> Ok.
> 
> > diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
> > index 24529db8a1..ee58e2778a 100644
> > --- a/sysdeps/generic/ldsodefs.h
> > +++ b/sysdeps/generic/ldsodefs.h
> > @@ -39,6 +39,7 @@
> >  #include <libc-lock.h>
> >  #include <hp-timing.h>
> >  #include <list_t.h>
> > +#include <hugepages.h>
> >  
> >  __BEGIN_DECLS
> >  
> > @@ -477,6 +478,17 @@ struct rtld_global
> >    EXTERN struct __pthread **_dl_pthread_threads;
> >    __mach_rwlock_define (EXTERN, _dl_pthread_threads_lock)
> >  #endif
> > +#ifdef HAVE_THP
> > +  /* The THP segment load control:
> > +      > 0: Enabled by GLIBC_TUNABLES=glibc.elf.thp=1.
> > +        0: Disabled by GLIBC_TUNABLES=glibc.elf.thp=0.
> > +      < 0: To be enabled or disabled by GLIBC_TUNABLES.  */
> > +  EXTERN int _dl_elf_thp_control;
> 
> Maybe use an enum for the tri-phase state here.

I will investigate.

> 
> > +  /* The kernel THP mode.  */
> > +  EXTERN enum thp_mode_t _dl_thp_mode;
> > +  /* Page size used for THP segment load.  */
> > +  EXTERN size_t _dl_elf_thp_pagesize;
> > +#endif
> >  #ifdef SHARED
> >  };
> >  # define __rtld_global_attribute__
> > diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
> > index 63e7046cb3..d825fe853a 100644
> > --- a/sysdeps/unix/sysv/linux/Makefile
> > +++ b/sysdeps/unix/sysv/linux/Makefile
> > @@ -698,11 +698,15 @@ $(objpfx)pldd: $(objpfx)xmalloc.o
> >  tests += \
> >    tst-rseq-tls-range \
> >    tst-rseq-tls-range-4096 \
> > +  tst-thp-1 \
> > +  tst-thp-1-pde \
> > +  tst-thp-1-static \
> >    tst-thp-align \
> >  # tests
> >  tests-static += \
> >    tst-rseq-tls-range-4096-static \
> >    tst-rseq-tls-range-static \
> > +  tst-thp-1-static \
> >  # tests-static
> >  modules-names += \
> >    tst-rseq-tls-range-mod \
> > @@ -712,15 +716,12 @@ CFLAGS-tst-rseq-tls-range.c += -DMAIN_TLS_ALIGN=4
> >  CFLAGS-tst-rseq-tls-range-4096.c += -DMAIN_TLS_ALIGN=4096
> >  CFLAGS-tst-rseq-tls-range-static.c += -DMAIN_TLS_ALIGN=4
> >  CFLAGS-tst-rseq-tls-range-4096-static.c += -DMAIN_TLS_ALIGN=4096
> > -LDFLAGS-tst-thp-size-mod.so += -Wl,-z,noseparate-code
> >  $(objpfx)tst-rseq-tls-range.out: $(objpfx)tst-rseq-tls-range-mod.so
> >  $(objpfx)tst-rseq-tls-range-4096.out: $(objpfx)tst-rseq-tls-range-mod.so
> >  $(objpfx)tst-rseq-tls-range-static.out: $(objpfx)tst-rseq-tls-range-mod.so
> >  $(objpfx)tst-rseq-tls-range-4096-static.out: $(objpfx)tst-rseq-tls-range-mod.so
> > -$(objpfx)tst-thp-align.out: $(objpfx)tst-thp-size-mod.so
> >  tst-rseq-tls-range-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx)
> >  tst-rseq-tls-range-4096-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx)
> > -tst-thp-align-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> >  
> >  test-internal-extras += tst-nolink-libc
> >  ifeq ($(run-built-tests),yes)
> > @@ -729,6 +730,164 @@ tests-special += \
> >    $(objpfx)tst-nolink-libc-2.out \
> >    # tests-special
> >  endif
> > +
> > +ifndef THP-PAGE-SIZE
> > +# Align PT_LOAD segments in THP tests to THP page size so that kernel will
> > +# map PIE to the address aligned to THP page size.  Default THP page size
> > +# to 2MB which can be overridden in Makefile in subdirectories.
> > +THP-PAGE-SIZE = 0x200000
> > +endif
> > +
> > +THP-PAGE-SIZE-LDFLAGS = -Wl,-z,max-page-size=$(THP-PAGE-SIZE)
> > +
> > +# -Wl,-z,max-page-size=$(THP-PAGE-SIZE) alone doesn't work for PDE when
> > +# text-segment address is lower than the maximum page size:
> > +# https://sourceware.org/bugzilla/show_bug.cgi?id=34184
> > +ifneq (,$(load-address-ldflag))
> > +LOAD-THP-ADDRESS-LDFLAGS = $(load-address-ldflag)=$(THP-PAGE-SIZE)
> > +endif
> > +
> 
> This made the arm failures to work, now that the first PT_LOAD VirtAddr
> is set of 0x200000.  I think the arm32 xfail can now be removed.

I think these static PIE tests still fail on arm.

> 
> > +LDFLAGS-tst-thp-size-mod.so = -Wl,-z,noseparate-code \
> > +			      $(THP-PAGE-SIZE-LDFLAGS)
> > +tst-thp-align-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> > +$(objpfx)tst-thp-align.out: $(objpfx)tst-thp-size-mod.so
> > +
> > +tests += \
> > +  tst-thp-1-no-s-code \
> > +  tst-thp-1-no-s-code-pde \
> > +  tst-thp-1-no-s-code-static \
> > +# tests
> > +tests-static += \
> > +  tst-thp-1-no-s-code-static \
> > +# tests-static
> > +
> > +LDFLAGS-tst-thp-1 = -Wl,-z,separate-code $(THP-PAGE-SIZE-LDFLAGS)
> > +LDFLAGS-tst-thp-1-pde = -Wl,-z,separate-code $(THP-PAGE-SIZE-LDFLAGS) \
> > +			$(LOAD-THP-ADDRESS-LDFLAGS)
> > +LDFLAGS-tst-thp-1-static = -Wl,-z,separate-code $(THP-PAGE-SIZE-LDFLAGS)
> > +ifneq (yes,$(enable-static-pie))
> > +LDFLAGS-tst-thp-1-static += $(LOAD-THP-ADDRESS-LDFLAGS)
> > +endif
> > +LDFLAGS-tst-thp-1-no-s-code = -Wl,-z,noseparate-code \
> > +			      $(THP-PAGE-SIZE-LDFLAGS)
> > +LDFLAGS-tst-thp-1-no-s-code-pde = -Wl,-z,noseparate-code \
> > +				  $(THP-PAGE-SIZE-LDFLAGS) \
> > +				  $(LOAD-THP-ADDRESS-LDFLAGS)
> > +LDFLAGS-tst-thp-1-no-s-code-static = -Wl,-z,noseparate-code \
> > +				     $(THP-PAGE-SIZE-LDFLAGS)
> > +ifneq (yes,$(enable-static-pie))
> > +LDFLAGS-tst-thp-1-no-s-code-static += $(LOAD-THP-ADDRESS-LDFLAGS)
> > +endif
> > +
> > +$(objpfx)tst-thp-1-no-s-code: $(objpfx)tst-thp-size-mod.o
> > +$(objpfx)tst-thp-1-no-s-code-pde: $(objpfx)tst-thp-size-mod.o
> > +$(objpfx)tst-thp-1-no-s-code-static: $(objpfx)tst-thp-size-mod.o
> > +
> > +tst-thp-1-no-s-code-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> > +tst-thp-1-no-s-code-pde-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> > +tst-thp-1-no-s-code-static-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> > +
> > +tst-thp-1-no-s-code-pde-no-pie = yes
> > +
> > +tst-thp-1-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> > +tst-thp-1-pde-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> > +tst-thp-1-static-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
> > +
> > +$(objpfx)tst-thp-1: $(objpfx)tst-thp-size-mod.o
> > +$(objpfx)tst-thp-1-pde: $(objpfx)tst-thp-size-mod.o
> > +$(objpfx)tst-thp-1-static: $(objpfx)tst-thp-size-mod.o
> > +
> > +tst-thp-1-pde-no-pie = yes
> > +
> > +# Don't run strace tests for cross-compiling.
> > +ifeq (no,$(cross-compiling))
> > +thp-kernel-status = $(shell grep madvise /sys/kernel/mm/transparent_hugepage/enabled)
> > +# Verify that madvise is called with MADV_HUGEPAGE when THP is enabled
> > +# under madvise THP kernel.
> > +ifneq ($(findstring [madvise],$(thp-kernel-status)),)
> > +tests-special += \
> > +  $(objpfx)strace-tst-thp-1-disabled.out \
> > +  $(objpfx)strace-tst-thp-1-enabled.out \
> > +  $(objpfx)strace-tst-thp-1-pde-disabled.out \
> > +  $(objpfx)strace-tst-thp-1-pde-enabled.out \
> > +  $(objpfx)strace-tst-thp-1-static-disabled.out \
> > +  $(objpfx)strace-tst-thp-1-static-enabled.out \
> > +  $(objpfx)strace-tst-thp-align-default.out \
> > +  $(objpfx)strace-tst-thp-align-disabled.out \
> > +  $(objpfx)strace-tst-thp-align-enabled.out \
> > +# tests-special
> > +
> > +$(objpfx)strace-tst-thp-1-enabled.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> > +  $(objpfx)tst-thp-1
> > +	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> > +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' \
> > +		'$(rpath-link)' $(objpfx)tst-thp-1 > $@; \
> > +	  $(evaluate-test)
> > +
> > +$(objpfx)strace-tst-thp-1-disabled.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> > +  $(objpfx)tst-thp-1
> > +	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> > +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' \
> > +		'$(rpath-link)' $(objpfx)tst-thp-1 > $@; \
> > +	  $(evaluate-test)
> > +
> > +$(objpfx)strace-tst-thp-1-pde-enabled.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> > +  $(objpfx)tst-thp-1-pde
> > +	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> > +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' \
> > +		'$(rpath-link)' $(objpfx)tst-thp-1-pde > $@; \
> > +	  $(evaluate-test)
> > +
> > +$(objpfx)strace-tst-thp-1-pde-disabled.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> > +  $(objpfx)tst-thp-1-pde
> > +	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> > +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' \
> > +		'$(rpath-link)' $(objpfx)tst-thp-1-pde > $@; \
> > +	  $(evaluate-test)
> > +
> > +$(objpfx)strace-tst-thp-1-static-enabled.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh \
> > +  $(objpfx)tst-thp-1-static
> > +	$(SHELL) $< $(objpfx)tst-thp-1-static '$(test-wrapper-env)' \
> > +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' > $@; \
> > +	  $(evaluate-test)
> > +
> > +$(objpfx)strace-tst-thp-1-static-disabled.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh \
> > +  $(objpfx)tst-thp-1-static
> > +	$(SHELL) $< $(objpfx)tst-thp-1-static '$(test-wrapper-env)' \
> > +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' > $@; \
> > +	  $(evaluate-test)
> > +
> > +$(objpfx)strace-tst-thp-align-default.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> > +  $(objpfx)tst-thp-align
> > +	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> > +		'$(run-program-env)' \
> > +		'$(rpath-link)' $(objpfx)tst-thp-align > $@; \
> > +	  $(evaluate-test)
> > +
> > +$(objpfx)strace-tst-thp-align-enabled.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> > +  $(objpfx)tst-thp-align
> > +	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> > +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' \
> > +		'$(rpath-link)' $(objpfx)tst-thp-align > $@; \
> > +	  $(evaluate-test)
> > +
> > +$(objpfx)strace-tst-thp-align-disabled.out: \
> > +  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
> > +  $(objpfx)tst-thp-align
> > +	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
> > +		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' \
> > +		'$(rpath-link)' $(objpfx)tst-thp-align > $@; \
> > +	  $(evaluate-test)
> > +endif # [madvise]
> > +endif # $(cross-compiling)
> >  endif # $(subdir) == elf
> >  
> >  ifeq ($(subdir),rt)
> 
> Ok.
> 
> > diff --git a/sysdeps/unix/sysv/linux/arm/Makefile b/sysdeps/unix/sysv/linux/arm/Makefile
> > index e73ce4f811..1ee8bec9b9 100644
> > --- a/sysdeps/unix/sysv/linux/arm/Makefile
> > +++ b/sysdeps/unix/sysv/linux/arm/Makefile
> > @@ -3,6 +3,13 @@ sysdep-rtld-routines += aeabi_read_tp libc-do-syscall
> >  # The test uses INTERNAL_SYSCALL_CALL.  In thumb mode, this uses
> >  # an undefined reference to __libc_do_syscall.
> >  CFLAGS-tst-nolink-libc.c += -marm
> > +
> > +# These tests fail on arm due to limitations of arm32 kABI:
> > +# https://sourceware.org/bugzilla/show_bug.cgi?id=34096
> > +test-xfail-tst-thp-1-no-s-code-pde = yes
> > +test-xfail-tst-thp-1-no-s-code-static = yes
> > +test-xfail-tst-thp-1-pde = yes
> > +test-xfail-tst-thp-1-static = yes
> >  endif
> >  
> >  ifeq ($(subdir),misc)
> 
> I think these can be removed, from previous remarks.

I think these static PIE tests still fail on arm.  I changed them to:

ifeq (yes,$(enable-static-pie))
# These static PIE tests fail on arm due to limitations of arm32 kABI:
# https://sourceware.org/bugzilla/show_bug.cgi?id=34096
test-xfail-tst-thp-1-no-s-code-static = yes
test-xfail-tst-thp-1-static = yes
endif
endif

> 
> > diff --git a/sysdeps/unix/sysv/linux/dl-exec-post.h b/sysdeps/unix/sysv/linux/dl-exec-post.h
> > new file mode 100644
> > index 0000000000..69505df402
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/dl-exec-post.h
> > @@ -0,0 +1,126 @@
> > +/* _dl_executable_postprocess.  Linux version.
> > +   Copyright (C) 2026 Free Software Foundation, Inc.
> > +   Copyright The GNU Toolchain Authors.
> > +   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/>.  */
> > +
> > +static inline void
> > +_dl_get_thp_config (void)
> > +{
> > +  /* Check if there is GLIBC_TUNABLES=glibc.elf.thp=[0|1].  */
> > +  GL(dl_elf_thp_control) = TUNABLE_GET_FULL (glibc, elf, thp, int32_t,
> > +					     NULL);
> > +
> > +  /* Return if the tunable is not set or THP is disabled by the
> > +     tunable.  */
> > +  if (GL(dl_elf_thp_control) == 0)
> > +    return;
> > +
> > +  _Static_assert (DL_MAP_DEFAULT_THP_PAGESIZE <= MAX_THP_PAGESIZE,
> > +		  "DL_MAP_DEFAULT_THP_PAGESIZE <= MAX_THP_PAGESIZE");
> > +
> > +  /* NB: Accessing /sys/kernel/mm files is quite expensive and the file
> > +     may not be accessible in containers.  If DL_MAP_DEFAULT_THP_PAGESIZE
> > +     is non-zero, assume THP mode is madvise and always call madvise.
> > +     Since madvise is a fast system call, it adds only a small overhead
> > +     compared to the cost of accessing /sys/kernel/mm files.  */
> > +  if (DL_MAP_DEFAULT_THP_PAGESIZE != 0)
> > +    {
> > +      GL(dl_elf_thp_pagesize) = DL_MAP_DEFAULT_THP_PAGESIZE;
> > +      GL(dl_thp_mode) = thp_mode_madvise;
> > +    }
> > +  else
> > +    {
> > +      GL(dl_thp_mode) = __get_thp_mode ();
> > +      if (GL(dl_thp_mode) == thp_mode_always
> > +	  || GL(dl_thp_mode) == thp_mode_madvise)
> > +	{
> > +	  GL(dl_elf_thp_pagesize) = __get_thp_size ();
> > +	  /* We cap the huge page size at MAX_THP_PAGESIZE to avoid
> > +	     over-aligning on systems with very large normal pages
> > +	     (like 64K pages with 512M huge pages).  */
> > +	  if (GL(dl_elf_thp_pagesize) > MAX_THP_PAGESIZE)
> > +	    GL(dl_elf_thp_pagesize) = 0;
> > +	}
> > +      else
> > +	GL(dl_elf_thp_pagesize) = 0;
> > +
> > +      if (GL(dl_elf_thp_pagesize) == 0)
> > +	{
> > +	  GL(dl_elf_thp_control) = 0;
> > +	  GL(dl_thp_mode) = thp_mode_not_supported;
> > +	}
> > +    }
> > +}
> > +
> > +static inline void
> > +_dl_executable_postprocess (struct link_map *main_map,
> > +			    const ElfW(Phdr) *phdr, ElfW(Word) phnum)
> > +{
> > +  /* NB: In static executable, PT_GNU_PROPERTY is processed in target
> > +     libc-start.h if it is needed by target.  When ld.so is used, if
> > +     a target doesn't need PT_GNU_PROPERTY, _dl_process_pt_gnu_property
> > +     is an empty function.  */
> > +#ifdef SHARED
> > +  /* Process program headers again, but scan them backwards since
> > +     PT_GNU_PROPERTY is close to the end of program headers.   */
> > +  for (const ElfW(Phdr) *ph = &phdr[phnum]; ph != phdr; --ph)
> > +    if (ph[-1].p_type == PT_GNU_PROPERTY)
> > +      {
> > +	_dl_process_pt_gnu_property (main_map, -1, &ph[-1]);
> > +	break;
> > +      }
> > +#endif
> > +
> > +  /* If THP state was not yet initialized, the main executable was mapped
> > +     by the kernel; in that case this function is the only place that can
> > +     apply MADV_HUGEPAGE to the main executable's segments.  Otherwise,
> > +     _dl_get_thp_config has already run earlier in dl_main and
> > +     _dl_map_segments has just mapped the main executable, so
> > +     _dl_postprocess_loadcmd_extra has already done the madvise pass; do
> > +     not repeat it here.  */
> > +  if (GL(dl_elf_thp_control) != -1)
> > +    return;
> > +
> > +   _dl_get_thp_config ();
> > +
> > +  /* Return if THP segment load isn't enabled.  */
> > +  if (GL(dl_elf_thp_control) <= 0)
> > +    return;
> > +
> > +  /* NB: If DL_MAP_DEFAULT_THP_PAGESIZE is non-zero, dl_thp_mode is set
> > +     to thp_mode_madvise.  */
> > +  if (DL_MAP_DEFAULT_THP_PAGESIZE == 0
> > +      && GL(dl_thp_mode) != thp_mode_madvise)
> > +    return;
> > +
> > +  /* When we get here, the main executable have been mapped in.  Call
> > +     madvise with MADV_HUGEPAGE for all THP eligible PT_LOAD segments.  */
> > +
> > +  const ElfW(Phdr) *ph;
> > +
> > +  size_t thp_pagesize = GL(dl_elf_thp_pagesize);
> > +
> > +  /* Call __madvise if offset and address of the PT_LOAD segment are
> > +     aligned to THP page size and it is read-only.  */
> > +  for (ph = phdr; ph < &phdr[phnum]; ++ph)
> > +    if (ph->p_type == PT_LOAD
> > +	&& ph->p_memsz >= thp_pagesize
> > +	&& ((ph->p_vaddr | ph->p_offset) & (thp_pagesize - 1)) == 0
> > +	&& (ph->p_flags & (PF_W | PF_R)) == PF_R)
> > +      __madvise ((void *) (main_map->l_addr + ph->p_vaddr),
> > +		 ph->p_memsz, MADV_HUGEPAGE);
> > +}
> 
> Ok.
> 
> > diff --git a/sysdeps/unix/sysv/linux/dl-load-post.h b/sysdeps/unix/sysv/linux/dl-load-post.h
> > new file mode 100644
> > index 0000000000..88467764d4
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/dl-load-post.h
> > @@ -0,0 +1,32 @@
> > +/* _dl_postprocess_loadcmd_extra.  Linux version.
> > +   Copyright (C) 2026 Free Software Foundation, Inc.
> > +   Copyright The GNU Toolchain Authors.
> > +   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/>.  */
> > +
> > +static bool _dl_segment_thp_eligible (const struct loadcmd *, size_t);
> > +
> > +/* After L has been mapped in, call madvise with MADV_HUGEPAGE for THP
> > +   madvise mode if L is THP eligible.  */
> > +
> > +static inline void
> > +_dl_postprocess_loadcmd_extra (struct link_map *l, const struct loadcmd *c)
> > +{
> > +  if (GL(dl_thp_mode) == thp_mode_madvise
> > +      && _dl_segment_thp_eligible (c, GL(dl_elf_thp_pagesize)))
> > +    __madvise ((void *) (l->l_addr + c->mapstart),
> > +	       c->mapend - c->mapstart, MADV_HUGEPAGE);
> > +}
> 
> Ok.  Should we warn for LD_DEBUG if the __madvise fails?

Will do.

> 
> > diff --git a/sysdeps/unix/sysv/linux/dl-map-segment-align.c b/sysdeps/unix/sysv/linux/dl-map-segment-align.c
> > index a39e74d91b..1260b26c22 100644
> > --- a/sysdeps/unix/sysv/linux/dl-map-segment-align.c
> > +++ b/sysdeps/unix/sysv/linux/dl-map-segment-align.c
> > @@ -17,38 +17,23 @@
> >     License along with the GNU C Library; if not, see
> >     <https://www.gnu.org/licenses/>.  */
> >  
> > +#include <ldsodefs.h>
> >  #include <dl-map-segment-align.h>
> > -#include <dl-tunables.h>
> > -#include <hugepages.h>
> > +
> > +/* Return the alignment of the PT_LOAD segment for THP.  P_ALIGN_MAX is
> > +   the maximum p_align value in the PT_LOAD segment.  */
> >  
> >  ElfW (Addr)
> >  _dl_map_segment_align (const struct loadcmd *c, ElfW (Addr) p_align_max)
> >  {
> > -  static enum thp_mode_t thp_mode = thp_mode_not_supported;
> > -  static unsigned long int thp_pagesize;
> > +  size_t thp_pagesize = GL(dl_elf_thp_pagesize);
> >  
> > -  if (TUNABLE_GET (glibc, elf, thp, int32_t, NULL) == 0)
> > +  if (GL(dl_elf_thp_control) <= 0 || p_align_max >= thp_pagesize)
> >      return p_align_max;
> >  
> > -  if (__glibc_unlikely (thp_mode == thp_mode_not_supported
> > -                        || thp_pagesize == 0))
> > -    {
> > -      unsigned long int default_thp_pagesize = DL_MAP_DEFAULT_THP_PAGESIZE;
> > -      thp_mode = default_thp_pagesize ? thp_mode_always : __get_thp_mode ();
> > -      thp_pagesize = default_thp_pagesize ? : __get_thp_size ();
> > -    }
> > -
> > -  /* Aligning load segments that are large enough to the PMD size helps
> > -     improve THP eligibility and reduces TLB pressure.
> > -     We cap the huge page size at MAX_THP_PAGESIZE to avoid over-aligning
> > -     on systems with very large normal pages (like 64K pages with 512M
> > -     huge pages). */
> > -  if (thp_mode == thp_mode_always
> > -      && thp_pagesize <= MAX_THP_PAGESIZE
> > -      && ((c->mapstart | c->mapoff) & (thp_pagesize - 1)) == 0
> > -      && (c->mapend - c->mapstart) >= thp_pagesize
> > -      && p_align_max < thp_pagesize
> > -      && (c->prot & PROT_WRITE) == 0)
> > +  /* Return true if the segment is THP eligible.  It helps improve THP
> > +     eligibility and reduces TLB pressure.  */
> > +  if (_dl_segment_thp_eligible (c, thp_pagesize))
> >      return thp_pagesize;
> >  
> >    return p_align_max;
> 
> Ok.
> 
> > diff --git a/sysdeps/unix/sysv/linux/dl-map-segment-align.h b/sysdeps/unix/sysv/linux/dl-map-segment-align.h
> > index d9b05181b7..b904e128d8 100644
> > --- a/sysdeps/unix/sysv/linux/dl-map-segment-align.h
> > +++ b/sysdeps/unix/sysv/linux/dl-map-segment-align.h
> > @@ -19,9 +19,18 @@
> >  
> >  #include <dl-load.h>
> >  
> > -#ifndef DL_MAP_DEFAULT_THP_PAGESIZE
> > -# define DL_MAP_DEFAULT_THP_PAGESIZE	0
> > -#endif
> > -
> >  extern ElfW (Addr) _dl_map_segment_align
> >    (const struct loadcmd *, ElfW (Addr)) attribute_hidden;
> > +
> > +/* Return true only if the loadcmd C is THP eligible with THP page size
> > +   THP_PAGESIZE, which means that it is read-only, its size >= THP page
> > +   size, its offset and address of the loadcmd C are aligned to THP page
> > +   size.  */
> > +
> > +static inline bool
> > +_dl_segment_thp_eligible (const struct loadcmd *c, size_t thp_pagesize)
> > +{
> > +  return ((c->prot & PROT_WRITE) == 0
> > +	  && (c->mapend - c->mapstart) >= thp_pagesize
> > +	  && ((c->mapstart | c->mapoff) & (thp_pagesize - 1)) == 0);
> > +}
> 
> Ok.
> 
> > diff --git a/sysdeps/unix/sysv/linux/ldsodefs.h b/sysdeps/unix/sysv/linux/ldsodefs.h
> > index c63b649432..e39d9afe34 100644
> > --- a/sysdeps/unix/sysv/linux/ldsodefs.h
> > +++ b/sysdeps/unix/sysv/linux/ldsodefs.h
> > @@ -21,6 +21,9 @@
> >  /* We have the auxiliary vector.  */
> >  #define HAVE_AUX_VECTOR
> >  
> > +/* We have transparent huge page.  */
> > +#define HAVE_THP
> > +
> >  /* Get the real definitions.  */
> >  #include_next <ldsodefs.h>
> >  
> > diff --git a/sysdeps/unix/sysv/linux/loongarch/Makefile b/sysdeps/unix/sysv/linux/loongarch/Makefile> index 0d5f087862..d5beb62440 100644
> > --- a/sysdeps/unix/sysv/linux/loongarch/Makefile
> > +++ b/sysdeps/unix/sysv/linux/loongarch/Makefile
> > @@ -12,3 +12,6 @@ abi-ilp32s-condition	:= __WORDSIZE == 32 && defined __loongarch_soft_float
> >  abi-ilp32d-condition	:= __WORDSIZE == 32 && defined __loongarch_double_float
> >  abi-lp64s-condition	:= __WORDSIZE == 64 && defined __loongarch_soft_float
> >  abi-lp64d-condition	:= __WORDSIZE == 64 && defined __loongarch_double_float
> > +
> > +# Align THP tests to 32MB.
> > +THP-PAGE-SIZE = 0x2000000
> > diff --git a/sysdeps/unix/sysv/linux/loongarch/lp64/dl-map-segment-align.h b/sysdeps/unix/sysv/linux/loongarch/lp64/hugepages.h
> > similarity index 90%
> > rename from sysdeps/unix/sysv/linux/loongarch/lp64/dl-map-segment-align.h
> > rename to sysdeps/unix/sysv/linux/loongarch/lp64/hugepages.h
> > index c51ee4ac47..30252a9b86 100644
> > --- a/sysdeps/unix/sysv/linux/loongarch/lp64/dl-map-segment-align.h
> > +++ b/sysdeps/unix/sysv/linux/loongarch/lp64/hugepages.h
> > @@ -1,4 +1,4 @@
> > -/* _dl_map_segment_align.  LoongArch64 Linux version.
> > +/* Huge Page support.  LoongArch64 Linux version.
> >     Copyright (C) 2026 Free Software Foundation, Inc.
> >     Copyright The GNU Toolchain Authors.
> >     This file is part of the GNU C Library.
> > @@ -19,4 +19,4 @@
> >  
> >  #define DL_MAP_DEFAULT_THP_PAGESIZE (32 * 1024 * 1024)
> >  
> > -#include_next <dl-map-segment-align.h>
> > +#include_next <hugepages.h>
> 
> Ok.
> 
> > diff --git a/sysdeps/unix/sysv/linux/strace-tst-thp.sh b/sysdeps/unix/sysv/linux/strace-tst-thp.sh
> > new file mode 100644
> > index 0000000000..3ffb256c71
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/strace-tst-thp.sh
> > @@ -0,0 +1,80 @@
> > +#!/bin/bash
> > +# Run THP test under strace to verify control of the THP segment load.
> > +# 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/>.
> > +
> > +set -e
> > +
> > +case x"${1}" in
> > +*-static)
> > +  rtld=
> > +  test_wrapper_env="$2"
> > +  run_program_env="$3"
> > +  library_path=
> > +  test_prog="$1"
> > +  ;;
> > +*)
> > +  rtld="$1"
> > +  test_wrapper_env="$2"
> > +  run_program_env="$3"
> > +  library_path="$4"
> > +  test_prog="$5"
> > +  ;;
> > +esac
> > +
> > +
> > +if test x"${rtld}" = x; then
> > +  cmd="${test_wrapper_env} ${run_program_env} strace ${test_prog}"
> > +else
> > +  cmd="${test_wrapper_env} ${run_program_env} strace ${rtld} \
> > +       --library-path ${library_path} ${test_prog}"
> > +fi
> > +
> > +TIMEOUTFACTOR=${TIMEOUTFACTOR:-1}
> > +
> > +case x"${run_program_env}" in
> > +*glibc.elf.thp=1*)
> > +  strace_expected=yes
> > +  ;;
> > +*)
> > +  strace_expected=no
> > +  ;;
> > +esac
> > +
> > +# Verify strace is not just present, but works in this environment.  If
> > +# not, skip the test.
> > +/bin/sh -c \
> > + "${test_wrapper_env} ${run_program_env} \
> > +  strace -e trace=none -- /bin/true" > /dev/null 2>&1 || exit 77
> > +
> > +# Finally the actual test inside the test environment, using the just
> > +# build ld.so and new libraries to run the THP test under strace.
> > +if /bin/sh -c \
> > +  "timeout -k 4 $((3*$TIMEOUTFACTOR)) ${cmd} --direct 2>&1 \
> > +   | grep -E \"madvise.*, MADV_HUGEPAGE\""; then
> > +  if test ${strace_expected} = yes; then
> > +    exit 0
> > +  else
> > +    exit 1
> > +  fi
> > +else
> > +  if test ${strace_expected} = no; then
> > +    exit 0
> > +  else
> > +    exit 1
> > +  fi
> > +fi
> 
> Ok.
> 
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c
> > new file mode 100644
> > index 0000000000..3fd01e9bfe
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c
> > @@ -0,0 +1,19 @@
> > +/* Test PDE with THP segment load linked with -Wl,-z,noseparate-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
> > +   <http://www.gnu.org/licenses/>.  */
> > +
> > +#include "tst-thp-1.c"
> 
> Ok.
> 
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c
> > new file mode 100644
> > index 0000000000..d0ae0f1ff0
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c
> > @@ -0,0 +1,19 @@
> > +/* Test static with THP segment load linked with -Wl,-z,noseparate-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
> > +   <http://www.gnu.org/licenses/>.  */
> > +
> > +#include "tst-thp-1.c"
> 
> 
> Ok.
> 
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c
> > new file mode 100644
> > index 0000000000..5eb1e005ed
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c
> > @@ -0,0 +1,19 @@
> > +/* Test THP segment load linked with -Wl,-z,noseparate-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
> > +   <http://www.gnu.org/licenses/>.  */
> > +
> > +#include "tst-thp-1.c"
> 
> 
> Ok.
> 
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-pde.c b/sysdeps/unix/sysv/linux/tst-thp-1-pde.c
> > new file mode 100644
> > index 0000000000..d854dd43da
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-1-pde.c
> > @@ -0,0 +1,19 @@
> > +/* Test PDE with THP segment load linked with -Wl,-z,separate-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
> > +   <http://www.gnu.org/licenses/>.  */
> > +
> > +#include "tst-thp-1.c"
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-static.c b/sysdeps/unix/sysv/linux/tst-thp-1-static.c
> > new file mode 100644
> > index 0000000000..66d7e12954
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-1-static.c
> > @@ -0,0 +1,19 @@
> > +/* Test static with THP segment load linked with -Wl,-z,separate-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
> > +   <http://www.gnu.org/licenses/>.  */
> > +
> > +#include "tst-thp-1.c"
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-1.c b/sysdeps/unix/sysv/linux/tst-thp-1.c
> > new file mode 100644
> > index 0000000000..49eea7069c
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-1.c
> > @@ -0,0 +1,28 @@
> > +/* Test THP segment load linked with -Wl,-z,separate-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
> > +   <http://www.gnu.org/licenses/>.  */
> > +
> > +#include "tst-thp-align-check.h"
> > +
> > +static int
> > +do_test (void)
> > +{
> > +  check_align ("tst-thp-1");
> > +  return 0;
> > +}
> > +
> > +#include <support/test-driver.c>
> 
> 
> Ok.
> 
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-align-check.h b/sysdeps/unix/sysv/linux/tst-thp-align-check.h
> > new file mode 100644
> > index 0000000000..8f1efc0ef1
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-align-check.h
> > @@ -0,0 +1,124 @@
> > +/* Test the THP compatible alignment of PT_LOAD segments.
> > +
> > +   Copyright (C) 2026 Free Software Foundation, Inc.
> > +
> > +   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 <fcntl.h>
> > +#include <stdint.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <intprops.h>
> > +#include <inttypes.h>
> > +#include <support/check.h>
> > +#include <support/xstdio.h>
> > +#include <support/xunistd.h>
> > +#undef attribute_hidden
> > +#define attribute_hidden
> > +#include <hugepages.h>	/* For enum thp_mode_t and MAX_THP_PAGESIZE.  */
> > +#undef attribute_hidden
> > +
> > +static unsigned long int
> > +get_thp_size (void)
> > +{
> > +  int fd = open ("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size",
> > +                 O_RDONLY, 0);
> > +  if (fd == -1)
> > +    return 0;
> > +
> > +  char str[INT_BUFSIZE_BOUND (unsigned long int)];
> > +  ssize_t s = read (fd, str, sizeof (str));
> > +  close (fd);
> > +  if (s < 0)	
> > +    return 0;
> > +
> > +  unsigned long int r = 0;
> > +  for (ssize_t i = 0; i < s; i++)
> > +    {
> > +      if (str[i] == '\n')
> > +    break;
> > +      r *= 10;
> > +      r += str[i] - '0';
> > +    }
> > +  return r;
> > +}
> > +
> > +static enum thp_mode_t
> > +get_thp_mode (void)
> > +{
> > +  int fd = open ("/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY, 0);
> > +  if (fd == -1)
> > +    return thp_mode_not_supported;
> > +
> > +  static const char mode_always[]  = "[always] madvise never\n";
> > +  static const char mode_madvise[] = "always [madvise] never\n";
> > +  static const char mode_never[]   = "always madvise [never]\n";
> > +
> > +  char str[sizeof(mode_always)];
> > +  ssize_t s = read (fd, str, sizeof (str));
> 
> Move the the close (fd) here.  
> 
> And now that we have support/support_check_hugetlb.c, maybe we can move the 
> get_thp_mode to libsupport, along with get_thp_size.

Fixed in v12.

> 
> > +  if (s >= sizeof str || s < 0)
> > +    return thp_mode_not_supported;
> > +  str[s] = '\0';
> > +  close (fd);
> > +
> > +  if (s == sizeof (mode_always) - 1)
> > +    {
> > +      if (strcmp (str, mode_always) == 0)
> > +    return thp_mode_always;
> > +      else if (strcmp (str, mode_madvise) == 0)
> > +    return thp_mode_madvise;
> > +      else if (strcmp (str, mode_never) == 0)
> > +    return thp_mode_never;
> > +    }
> > +  return thp_mode_not_supported;
> > +}
> > +
> > +static void
> > +check_align (const char *name)
> > +{
> > +  unsigned long int thp_size = get_thp_size ();
> > +  enum thp_mode_t thp_mode = get_thp_mode ();
> > +
> > +  if (thp_size == 0)
> > +    FAIL_UNSUPPORTED ("unable to get THP size.\n");
> > +
> > +  if (thp_size > MAX_THP_PAGESIZE)
> > +    FAIL_UNSUPPORTED ("THP size exceeds MAX_THP_PAGESIZE.\n");
> > +
> > +  if (thp_mode != thp_mode_always && thp_mode != thp_mode_madvise)
> > +    FAIL_UNSUPPORTED ("THP mode is not always nor madvise.\n");
> > +
> > +  FILE *f = xfopen ("/proc/self/maps", "r");
> > +  char *line = NULL;
> > +  size_t len;
> > +
> > +  while (xgetline (&line, &len, f))
> > +    {
> > +      uintptr_t from, to;
> > +      char *prot = NULL, *path = NULL;
> > +      int r = sscanf (line, "%" SCNxPTR "-%" SCNxPTR "%ms%*s%*s%*s%ms",
> > +                      &from, &to, &prot, &path);
> > +
> > +      TEST_VERIFY (r == 3 || r == 4);
> 
> When sscanf returns r == 3 (executable mapping with no pathname), path is NULL; 
> strstr (path, name) would deref NULL. Not really an issue because most tests
> won't have an exacutable mapping (specially on Linux), but I think we should
> add a 'path != NULL' just to be safe. 

Fixed.

> 
> > +
> > +      if (strstr (prot, "x") && strstr (path, name))
> > +        TEST_COMPARE (from % thp_size, 0);
> > +
> > +      free (path);
> 
> And add free (prot) as well.

Fixed.

> 
> > +    }
> > +
> > +  free (line);
> > +  xfclose (f);
> > +}
> > diff --git a/sysdeps/unix/sysv/linux/tst-thp-align.c b/sysdeps/unix/sysv/linux/tst-thp-align.c
> > index 0b3f18e000..2e44109ba6 100644
> > --- a/sysdeps/unix/sysv/linux/tst-thp-align.c
> > +++ b/sysdeps/unix/sysv/linux/tst-thp-align.c
> > @@ -16,129 +16,10 @@
> >     License along with the GNU C Library; if not, see
> >     <https://www.gnu.org/licenses/>.  */
> >  
> > -#include <fcntl.h>
> > -#include <stdint.h>
> > -#include <stdlib.h>
> > -#include <string.h>
> > -#include <intprops.h>
> > -#include <inttypes.h>
> > -#include <support/check.h>
> >  #include <support/xdlfcn.h>
> > -#include <support/xstdio.h>
> > -#include <support/xunistd.h>
> > +#include "tst-thp-align-check.h"
> >  
> >  #define THP_SIZE_MOD_NAME "tst-thp-size-mod.so"
> > -#define MAX_THP_PAGESIZE (32 * 1024 * 1024)
> > -
> > -enum thp_mode_t
> > -{
> > -  thp_mode_always,
> > -  thp_mode_madvise,
> > -  thp_mode_never,
> > -  thp_mode_not_supported
> > -};
> > -
> > -static unsigned long int
> > -get_thp_size (void)
> > -{
> > -  int fd = open ("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size",
> > -                 O_RDONLY, 0);
> > -  if (fd == -1)
> > -    return 0;
> > -
> > -  char str[INT_BUFSIZE_BOUND (unsigned long int)];
> > -  ssize_t s = read (fd, str, sizeof (str));
> > -  close (fd);
> > -  if (s < 0)
> > -    return 0;
> > -
> > -  unsigned long int r = 0;
> > -  for (ssize_t i = 0; i < s; i++)
> > -    {
> > -      if (str[i] == '\n')
> > -    break;
> > -      r *= 10;
> > -      r += str[i] - '0';
> > -    }
> > -  return r;
> > -}
> > -
> > -static enum thp_mode_t
> > -get_thp_mode (void)
> > -{
> > -  int fd = open ("/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY, 0);
> > -  if (fd == -1)
> > -    return thp_mode_not_supported;
> > -
> > -  static const char mode_always[]  = "[always] madvise never\n";
> > -  static const char mode_madvise[] = "always [madvise] never\n";
> > -  static const char mode_never[]   = "always madvise [never]\n";
> > -
> > -  char str[sizeof(mode_always)];
> > -  ssize_t s = read (fd, str, sizeof (str));
> > -  if (s >= sizeof str || s < 0)
> > -    return thp_mode_not_supported;
> > -  str[s] = '\0';
> > -  close (fd);
> > -
> > -  if (s == sizeof (mode_always) - 1)
> > -    {
> > -      if (strcmp (str, mode_always) == 0)
> > -    return thp_mode_always;
> > -      else if (strcmp (str, mode_madvise) == 0)
> > -    return thp_mode_madvise;
> > -      else if (strcmp (str, mode_never) == 0)
> > -    return thp_mode_never;
> > -    }
> > -  return thp_mode_not_supported;
> > -}
> > -
> > -static void
> > -check_align (void)
> > -{
> > -  unsigned long int thp_size = get_thp_size ();
> > -  enum thp_mode_t thp_mode = get_thp_mode ();
> > -
> > -  if (thp_size == 0)
> > -    {
> > -      FAIL_UNSUPPORTED ("unable to get THP size.\n");
> > -      return;
> > -    }
> > -
> > -  if (thp_size > MAX_THP_PAGESIZE)
> > -    {
> > -      FAIL_UNSUPPORTED ("THP size exceeds MAX_THP_PAGESIZE.\n");
> > -      return;
> > -    }
> > -
> > -  if (thp_mode != thp_mode_always)
> > -    {
> > -      FAIL_UNSUPPORTED ("THP mode is not always.\n");
> > -      return;
> > -    }
> > -
> > -  FILE *f = xfopen ("/proc/self/maps", "r");
> > -  char *line = NULL;
> > -  size_t len;
> > -
> > -  while (xgetline (&line, &len, f))
> > -    {
> > -      uintptr_t from, to;
> > -      char *prot = NULL, *path = NULL;
> > -      int r = sscanf (line, "%" SCNxPTR "-%" SCNxPTR "%ms%*s%*s%*s%ms",
> > -                      &from, &to, &prot, &path);
> > -
> > -      TEST_VERIFY (r == 3 || r == 4);
> > -
> > -      if (strstr (prot, "x") && strstr (path, THP_SIZE_MOD_NAME))
> > -        TEST_COMPARE (from % thp_size, 0);
> > -
> > -      free (path);
> > -    }
> > -
> > -  free (line);
> > -  xfclose (f);
> > -}
> >  
> >  static int
> >  do_test (void)
> > @@ -146,7 +27,7 @@ do_test (void)
> >    void *dl;
> >  
> >    dl = xdlopen (THP_SIZE_MOD_NAME, RTLD_NOW);
> > -  check_align ();
> > +  check_align (THP_SIZE_MOD_NAME);
> >    xdlclose (dl);
> >  
> >    return 0;
> 
> 
> Ok.
> 
> > diff --git a/sysdeps/unix/sysv/linux/x86/hugepages.h b/sysdeps/unix/sysv/linux/x86/hugepages.h
> > new file mode 100644
> > index 0000000000..1a8c370969
> > --- /dev/null
> > +++ b/sysdeps/unix/sysv/linux/x86/hugepages.h
> > @@ -0,0 +1,22 @@
> > +/* Huge Page support.  Linux/x86 version.
> > +   Copyright (C) 2026 Free Software Foundation, Inc.
> > +   Copyright The GNU Toolchain Authors.
> > +   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/>.  */
> > +
> > +#define DL_MAP_DEFAULT_THP_PAGESIZE (2 * 1024 * 1024)
> > +
> > +#include_next <hugepages.h>
> > -- 
> > 2.54.0
> > 
> 

Thanks.

H.J.
  

Patch

From 75a068e17c8104beb1f5a982fa3330cc2b3ef06b Mon Sep 17 00:00:00 2001
From: "H.J. Lu" <hjl.tools@gmail.com>
Date: Mon, 13 Apr 2026 08:23:05 +0800
Subject: [PATCH v11] elf: Support THP segment load with madvise enabled THP

The current THP segment load approach works only when THP is enabled
with always in the kernel.  If THP is enabled with madvise in the
kernel, to enable THP segment load in an application, madvise should
be called with MADV_HUGEPAGE on all THP eligible PT_LOAD segments:

1. Define DL_MAP_DEFAULT_THP_PAGESIZE in hugepages.h and default it to 0.
If DL_MAP_DEFAULT_THP_PAGESIZE is defined, assume kernel THP madvise mode.
If kernel THP mode is always or never, there is an extra madvise call
which has no impact.  DL_MAP_DEFAULT_THP_PAGESIZE is defined for x86 and
64-bit loongarch.
2. Update _dl_map_segment_align to support madvise THP mode.  This fixes
BZ #34079.
3. Call _dl_executable_postprocess in rtld_setup_main_map for dynamic
executables and in LIBC_START_MAIN for static executables, which calls
madvise with MADV_HUGEPAGE on all THP eligible PT_LOAD segments in
executable.  This fixes BZ #34080 for both dynamic and static executables.
4. Call _dl_postprocess_loadcmd_extra in _dl_postprocess_loadcmd, which
calls madvise with MADV_HUGEPAGE on all THP eligible PT_LOAD segments
when loading an object after they have been mapped in.  This fixes
BZ #34080 for shared objects.
5. Set the maximum page alignment on THP tests to THP page size as the
default maximum page alignment may be smaller than THP page size.
6. Add tests to verify that large executable PT_LOAD segments in
executables are mapped at addresses aligned to THP page size when the
kernel is configured to use THP in "always" mode or "madvise" mode by
inspecting /proc/self/maps to check that the mapping address is aligned
to THP page size reported by the kernel.  Also verify that madvise is
called with MADV_HUGEPAGE when the glibc tunable glibc.elf.thp=1 is used
and madvise isn't called with MADV_HUGEPAGE when the glibc tunable
glibc.elf.thp=0 is used.

Skip these tests if THP page size cannot be determined or if THP is not
enabled in "always" mode nor "madvise" mode.

Quote WANG Rui <wangrui@loongson.cn>:

From benchmarking a clang build of the Linux kernel on x86_64 with
your patch in THP madvise mode, I observed that iTLB misses were
reduced, similar to what we see in THP always mode.

NB: Some THP tests fail on arm due to limitations of arm32 kABI:

https://sourceware.org/bugzilla/show_bug.cgi?id=34096

Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
---
 csu/libc-start.c                              |   4 +
 elf/dl-load.h                                 |   3 +
 elf/dl-support.c                              |   6 +
 elf/rtld.c                                    |  17 +-
 sysdeps/generic/dl-exec-post.h                |  34 ++++
 sysdeps/generic/dl-load-post.h                |  23 +++
 sysdeps/generic/hugepages.h                   |   8 +-
 sysdeps/generic/ldsodefs.h                    |  12 ++
 sysdeps/unix/sysv/linux/Makefile              | 165 +++++++++++++++++-
 sysdeps/unix/sysv/linux/arm/Makefile          |   7 +
 sysdeps/unix/sysv/linux/dl-exec-post.h        | 126 +++++++++++++
 sysdeps/unix/sysv/linux/dl-load-post.h        |  32 ++++
 .../unix/sysv/linux/dl-map-segment-align.c    |  33 +---
 .../unix/sysv/linux/dl-map-segment-align.h    |  17 +-
 sysdeps/unix/sysv/linux/ldsodefs.h            |   3 +
 sysdeps/unix/sysv/linux/loongarch/Makefile    |   3 +
 .../{dl-map-segment-align.h => hugepages.h}   |   4 +-
 sysdeps/unix/sysv/linux/strace-tst-thp.sh     |  80 +++++++++
 .../unix/sysv/linux/tst-thp-1-no-s-code-pde.c |  19 ++
 .../sysv/linux/tst-thp-1-no-s-code-static.c   |  19 ++
 sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c |  19 ++
 sysdeps/unix/sysv/linux/tst-thp-1-pde.c       |  19 ++
 sysdeps/unix/sysv/linux/tst-thp-1-static.c    |  19 ++
 sysdeps/unix/sysv/linux/tst-thp-1.c           |  28 +++
 sysdeps/unix/sysv/linux/tst-thp-align-check.h | 124 +++++++++++++
 sysdeps/unix/sysv/linux/tst-thp-align.c       | 123 +------------
 sysdeps/unix/sysv/linux/x86/hugepages.h       |  22 +++
 27 files changed, 805 insertions(+), 164 deletions(-)
 create mode 100644 sysdeps/generic/dl-exec-post.h
 create mode 100644 sysdeps/generic/dl-load-post.h
 create mode 100644 sysdeps/unix/sysv/linux/dl-exec-post.h
 create mode 100644 sysdeps/unix/sysv/linux/dl-load-post.h
 rename sysdeps/unix/sysv/linux/loongarch/lp64/{dl-map-segment-align.h => hugepages.h} (90%)
 create mode 100644 sysdeps/unix/sysv/linux/strace-tst-thp.sh
 create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-pde.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1-static.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-thp-1.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-thp-align-check.h
 create mode 100644 sysdeps/unix/sysv/linux/x86/hugepages.h

diff --git a/csu/libc-start.c b/csu/libc-start.c
index 03d770ef15..bb106b524a 100644
--- a/csu/libc-start.c
+++ b/csu/libc-start.c
@@ -205,6 +205,7 @@  call_fini (void *unused)
 #endif /* !SHARED */
 
 #include <libc-start.h>
+#include <dl-exec-post.h>
 
 STATIC int LIBC_START_MAIN (int (*main) (int, char **, char **
 					 MAIN_AUXVEC_DECL),
@@ -300,6 +301,9 @@  LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
   __pointer_chk_guard_local = pointer_chk_guard;
 # endif
 
+  struct link_map *main_map = _dl_get_dl_main_map ();
+  _dl_executable_postprocess (main_map, GL(dl_phdr), GL(dl_phnum));
+
   /* Now that the TCB, canary, and pointer guard are in place, run the
      deferred IFUNC relocations.  For non-PIE static binaries this is
      ARCH_SETUP_IREL (apply_irel); for static-pie it is the IRELATIVE
diff --git a/elf/dl-load.h b/elf/dl-load.h
index 80ae5db4b3..84b82e183d 100644
--- a/elf/dl-load.h
+++ b/elf/dl-load.h
@@ -112,6 +112,7 @@  struct loadcmd
   int prot;                             /* PROT_* bits.  */
 };
 
+#include <dl-load-post.h>
 
 /* Iterator for program header segments.  Initialize with
    _dl_pt_load_iterator_init, then either walk PT_LOAD segments via
@@ -215,6 +216,8 @@  _dl_postprocess_loadcmd (struct link_map *l, const ElfW(Ehdr) *header,
     /* Found the program header in this segment.  */
     l->l_phdr = (void *) (uintptr_t) (c->mapstart + header->e_phoff
                                       - c->mapoff);
+
+  _dl_postprocess_loadcmd_extra (l, c);
 }
 
 
diff --git a/elf/dl-support.c b/elf/dl-support.c
index 0508d6113b..a8114de003 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -179,6 +179,12 @@  int _dl_stack_cache_lock;
 #endif
 struct dl_scope_free_list *_dl_scope_free_list;
 
+#ifdef HAVE_THP
+int _dl_elf_thp_control = -1;
+enum thp_mode_t _dl_thp_mode;
+size_t _dl_elf_thp_pagesize;
+#endif
+
 #ifdef NEED_DL_SYSINFO
 /* Needed for improved syscall handling on at least x86/Linux.  NB: Don't
    initialize it here to avoid RELATIVE relocation in static PIE.  */
diff --git a/elf/rtld.c b/elf/rtld.c
index 12e1b4dd71..e1ec899756 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -52,6 +52,7 @@ 
 #include <dl-find_object.h>
 #include <dl-audit-check.h>
 #include <dl-call_tls_init_tp.h>
+#include <dl-exec-post.h>
 
 #include <assert.h>
 
@@ -323,6 +324,9 @@  struct rtld_global _rtld_global =
     /* Generally the default presumption without further information is an
      * executable stack but this is not true for all platforms.  */
     ._dl_stack_prot_flags = DEFAULT_STACK_PROT_PERMS,
+#ifdef HAVE_THP
+    ._dl_elf_thp_control = -1,
+#endif
 #ifdef _LIBC_REENTRANT
     ._dl_load_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
     ._dl_load_write_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,
@@ -1209,14 +1213,8 @@  rtld_setup_main_map (struct link_map *main_map)
 	main_map->l_relro_size = ph->p_memsz;
 	break;
       }
-  /* Process program headers again, but scan them backwards since
-     PT_GNU_PROPERTY is close to the end of program headers.   */
-  for (const ElfW(Phdr) *ph = &phdr[phnum]; ph != phdr; --ph)
-    if (ph[-1].p_type == PT_GNU_PROPERTY)
-      {
-	_dl_process_pt_gnu_property (main_map, -1, &ph[-1]);
-	break;
-      }
+
+  _dl_executable_postprocess (main_map, phdr, phnum);
 
   /* Adjust the address of the TLS initialization image in case
      the executable is actually an ET_DYN object.  */
@@ -1589,6 +1587,9 @@  dl_main (const ElfW(Phdr) *phdr,
 	{
 	  RTLD_TIMING_VAR (start);
 	  rtld_timer_start (&start);
+#ifdef HAVE_THP
+	  _dl_get_thp_config ();
+#endif
 	  _dl_map_object (NULL, rtld_progname, lt_executable, 0,
 			  __RTLD_OPENEXEC, LM_ID_BASE);
 	  rtld_timer_stop (&load_time, start);
diff --git a/sysdeps/generic/dl-exec-post.h b/sysdeps/generic/dl-exec-post.h
new file mode 100644
index 0000000000..f5dcdc093a
--- /dev/null
+++ b/sysdeps/generic/dl-exec-post.h
@@ -0,0 +1,34 @@ 
+/* _dl_executable_postprocess.  Generic version.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   Copyright The GNU Toolchain Authors.
+   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/>.  */
+
+static inline void
+_dl_executable_postprocess (struct link_map *main_map,
+			    const ElfW(Phdr) *phdr, ElfW(Word) phnum)
+{
+#ifdef SHARED
+  /* Process program headers again, but scan them backwards since
+     PT_GNU_PROPERTY is close to the end of program headers.   */
+  for (const ElfW(Phdr) *ph = &phdr[phnum]; ph != phdr; --ph)
+    if (ph[-1].p_type == PT_GNU_PROPERTY)
+      {
+	_dl_process_pt_gnu_property (main_map, -1, &ph[-1]);
+	break;
+      }
+#endif
+}
diff --git a/sysdeps/generic/dl-load-post.h b/sysdeps/generic/dl-load-post.h
new file mode 100644
index 0000000000..1a6d205e33
--- /dev/null
+++ b/sysdeps/generic/dl-load-post.h
@@ -0,0 +1,23 @@ 
+/* _dl_postprocess_loadcmd_extra.  Generic version.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   Copyright The GNU Toolchain Authors.
+   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/>.  */
+
+static inline void
+_dl_postprocess_loadcmd_extra (struct link_map *l, const struct loadcmd *c)
+{
+}
diff --git a/sysdeps/generic/hugepages.h b/sysdeps/generic/hugepages.h
index 5fc9b5c8de..f7f4957e79 100644
--- a/sysdeps/generic/hugepages.h
+++ b/sysdeps/generic/hugepages.h
@@ -26,10 +26,10 @@  unsigned long int __get_thp_size (void) attribute_hidden;
 
 enum thp_mode_t
 {
+  thp_mode_not_supported = 0,
   thp_mode_always,
   thp_mode_madvise,
-  thp_mode_never,
-  thp_mode_not_supported
+  thp_mode_never
 };
 
 enum thp_mode_t __get_thp_mode (void) attribute_hidden;
@@ -45,6 +45,10 @@  void __get_hugepage_config (size_t requested, size_t *pagesize, int *flags)
 # define MALLOC_DEFAULT_THP_PAGESIZE	0
 #endif
 
+#ifndef DL_MAP_DEFAULT_THP_PAGESIZE
+# define DL_MAP_DEFAULT_THP_PAGESIZE	0
+#endif
+
 #ifndef MAX_THP_PAGESIZE
 # define MAX_THP_PAGESIZE	(32 * 1024 * 1024)
 #endif
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 24529db8a1..ee58e2778a 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -39,6 +39,7 @@ 
 #include <libc-lock.h>
 #include <hp-timing.h>
 #include <list_t.h>
+#include <hugepages.h>
 
 __BEGIN_DECLS
 
@@ -477,6 +478,17 @@  struct rtld_global
   EXTERN struct __pthread **_dl_pthread_threads;
   __mach_rwlock_define (EXTERN, _dl_pthread_threads_lock)
 #endif
+#ifdef HAVE_THP
+  /* The THP segment load control:
+      > 0: Enabled by GLIBC_TUNABLES=glibc.elf.thp=1.
+        0: Disabled by GLIBC_TUNABLES=glibc.elf.thp=0.
+      < 0: To be enabled or disabled by GLIBC_TUNABLES.  */
+  EXTERN int _dl_elf_thp_control;
+  /* The kernel THP mode.  */
+  EXTERN enum thp_mode_t _dl_thp_mode;
+  /* Page size used for THP segment load.  */
+  EXTERN size_t _dl_elf_thp_pagesize;
+#endif
 #ifdef SHARED
 };
 # define __rtld_global_attribute__
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index 63e7046cb3..d825fe853a 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -698,11 +698,15 @@  $(objpfx)pldd: $(objpfx)xmalloc.o
 tests += \
   tst-rseq-tls-range \
   tst-rseq-tls-range-4096 \
+  tst-thp-1 \
+  tst-thp-1-pde \
+  tst-thp-1-static \
   tst-thp-align \
 # tests
 tests-static += \
   tst-rseq-tls-range-4096-static \
   tst-rseq-tls-range-static \
+  tst-thp-1-static \
 # tests-static
 modules-names += \
   tst-rseq-tls-range-mod \
@@ -712,15 +716,12 @@  CFLAGS-tst-rseq-tls-range.c += -DMAIN_TLS_ALIGN=4
 CFLAGS-tst-rseq-tls-range-4096.c += -DMAIN_TLS_ALIGN=4096
 CFLAGS-tst-rseq-tls-range-static.c += -DMAIN_TLS_ALIGN=4
 CFLAGS-tst-rseq-tls-range-4096-static.c += -DMAIN_TLS_ALIGN=4096
-LDFLAGS-tst-thp-size-mod.so += -Wl,-z,noseparate-code
 $(objpfx)tst-rseq-tls-range.out: $(objpfx)tst-rseq-tls-range-mod.so
 $(objpfx)tst-rseq-tls-range-4096.out: $(objpfx)tst-rseq-tls-range-mod.so
 $(objpfx)tst-rseq-tls-range-static.out: $(objpfx)tst-rseq-tls-range-mod.so
 $(objpfx)tst-rseq-tls-range-4096-static.out: $(objpfx)tst-rseq-tls-range-mod.so
-$(objpfx)tst-thp-align.out: $(objpfx)tst-thp-size-mod.so
 tst-rseq-tls-range-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx)
 tst-rseq-tls-range-4096-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx)
-tst-thp-align-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
 
 test-internal-extras += tst-nolink-libc
 ifeq ($(run-built-tests),yes)
@@ -729,6 +730,164 @@  tests-special += \
   $(objpfx)tst-nolink-libc-2.out \
   # tests-special
 endif
+
+ifndef THP-PAGE-SIZE
+# Align PT_LOAD segments in THP tests to THP page size so that kernel will
+# map PIE to the address aligned to THP page size.  Default THP page size
+# to 2MB which can be overridden in Makefile in subdirectories.
+THP-PAGE-SIZE = 0x200000
+endif
+
+THP-PAGE-SIZE-LDFLAGS = -Wl,-z,max-page-size=$(THP-PAGE-SIZE)
+
+# -Wl,-z,max-page-size=$(THP-PAGE-SIZE) alone doesn't work for PDE when
+# text-segment address is lower than the maximum page size:
+# https://sourceware.org/bugzilla/show_bug.cgi?id=34184
+ifneq (,$(load-address-ldflag))
+LOAD-THP-ADDRESS-LDFLAGS = $(load-address-ldflag)=$(THP-PAGE-SIZE)
+endif
+
+LDFLAGS-tst-thp-size-mod.so = -Wl,-z,noseparate-code \
+			      $(THP-PAGE-SIZE-LDFLAGS)
+tst-thp-align-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
+$(objpfx)tst-thp-align.out: $(objpfx)tst-thp-size-mod.so
+
+tests += \
+  tst-thp-1-no-s-code \
+  tst-thp-1-no-s-code-pde \
+  tst-thp-1-no-s-code-static \
+# tests
+tests-static += \
+  tst-thp-1-no-s-code-static \
+# tests-static
+
+LDFLAGS-tst-thp-1 = -Wl,-z,separate-code $(THP-PAGE-SIZE-LDFLAGS)
+LDFLAGS-tst-thp-1-pde = -Wl,-z,separate-code $(THP-PAGE-SIZE-LDFLAGS) \
+			$(LOAD-THP-ADDRESS-LDFLAGS)
+LDFLAGS-tst-thp-1-static = -Wl,-z,separate-code $(THP-PAGE-SIZE-LDFLAGS)
+ifneq (yes,$(enable-static-pie))
+LDFLAGS-tst-thp-1-static += $(LOAD-THP-ADDRESS-LDFLAGS)
+endif
+LDFLAGS-tst-thp-1-no-s-code = -Wl,-z,noseparate-code \
+			      $(THP-PAGE-SIZE-LDFLAGS)
+LDFLAGS-tst-thp-1-no-s-code-pde = -Wl,-z,noseparate-code \
+				  $(THP-PAGE-SIZE-LDFLAGS) \
+				  $(LOAD-THP-ADDRESS-LDFLAGS)
+LDFLAGS-tst-thp-1-no-s-code-static = -Wl,-z,noseparate-code \
+				     $(THP-PAGE-SIZE-LDFLAGS)
+ifneq (yes,$(enable-static-pie))
+LDFLAGS-tst-thp-1-no-s-code-static += $(LOAD-THP-ADDRESS-LDFLAGS)
+endif
+
+$(objpfx)tst-thp-1-no-s-code: $(objpfx)tst-thp-size-mod.o
+$(objpfx)tst-thp-1-no-s-code-pde: $(objpfx)tst-thp-size-mod.o
+$(objpfx)tst-thp-1-no-s-code-static: $(objpfx)tst-thp-size-mod.o
+
+tst-thp-1-no-s-code-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
+tst-thp-1-no-s-code-pde-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
+tst-thp-1-no-s-code-static-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
+
+tst-thp-1-no-s-code-pde-no-pie = yes
+
+tst-thp-1-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
+tst-thp-1-pde-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
+tst-thp-1-static-ENV = GLIBC_TUNABLES=glibc.elf.thp=1
+
+$(objpfx)tst-thp-1: $(objpfx)tst-thp-size-mod.o
+$(objpfx)tst-thp-1-pde: $(objpfx)tst-thp-size-mod.o
+$(objpfx)tst-thp-1-static: $(objpfx)tst-thp-size-mod.o
+
+tst-thp-1-pde-no-pie = yes
+
+# Don't run strace tests for cross-compiling.
+ifeq (no,$(cross-compiling))
+thp-kernel-status = $(shell grep madvise /sys/kernel/mm/transparent_hugepage/enabled)
+# Verify that madvise is called with MADV_HUGEPAGE when THP is enabled
+# under madvise THP kernel.
+ifneq ($(findstring [madvise],$(thp-kernel-status)),)
+tests-special += \
+  $(objpfx)strace-tst-thp-1-disabled.out \
+  $(objpfx)strace-tst-thp-1-enabled.out \
+  $(objpfx)strace-tst-thp-1-pde-disabled.out \
+  $(objpfx)strace-tst-thp-1-pde-enabled.out \
+  $(objpfx)strace-tst-thp-1-static-disabled.out \
+  $(objpfx)strace-tst-thp-1-static-enabled.out \
+  $(objpfx)strace-tst-thp-align-default.out \
+  $(objpfx)strace-tst-thp-align-disabled.out \
+  $(objpfx)strace-tst-thp-align-enabled.out \
+# tests-special
+
+$(objpfx)strace-tst-thp-1-enabled.out: \
+  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
+  $(objpfx)tst-thp-1
+	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
+		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' \
+		'$(rpath-link)' $(objpfx)tst-thp-1 > $@; \
+	  $(evaluate-test)
+
+$(objpfx)strace-tst-thp-1-disabled.out: \
+  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
+  $(objpfx)tst-thp-1
+	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
+		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' \
+		'$(rpath-link)' $(objpfx)tst-thp-1 > $@; \
+	  $(evaluate-test)
+
+$(objpfx)strace-tst-thp-1-pde-enabled.out: \
+  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
+  $(objpfx)tst-thp-1-pde
+	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
+		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' \
+		'$(rpath-link)' $(objpfx)tst-thp-1-pde > $@; \
+	  $(evaluate-test)
+
+$(objpfx)strace-tst-thp-1-pde-disabled.out: \
+  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
+  $(objpfx)tst-thp-1-pde
+	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
+		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' \
+		'$(rpath-link)' $(objpfx)tst-thp-1-pde > $@; \
+	  $(evaluate-test)
+
+$(objpfx)strace-tst-thp-1-static-enabled.out: \
+  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh \
+  $(objpfx)tst-thp-1-static
+	$(SHELL) $< $(objpfx)tst-thp-1-static '$(test-wrapper-env)' \
+		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' > $@; \
+	  $(evaluate-test)
+
+$(objpfx)strace-tst-thp-1-static-disabled.out: \
+  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh \
+  $(objpfx)tst-thp-1-static
+	$(SHELL) $< $(objpfx)tst-thp-1-static '$(test-wrapper-env)' \
+		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' > $@; \
+	  $(evaluate-test)
+
+$(objpfx)strace-tst-thp-align-default.out: \
+  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
+  $(objpfx)tst-thp-align
+	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
+		'$(run-program-env)' \
+		'$(rpath-link)' $(objpfx)tst-thp-align > $@; \
+	  $(evaluate-test)
+
+$(objpfx)strace-tst-thp-align-enabled.out: \
+  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
+  $(objpfx)tst-thp-align
+	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
+		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=1' \
+		'$(rpath-link)' $(objpfx)tst-thp-align > $@; \
+	  $(evaluate-test)
+
+$(objpfx)strace-tst-thp-align-disabled.out: \
+  $(..)sysdeps/unix/sysv/linux/strace-tst-thp.sh $(objpfx)ld.so \
+  $(objpfx)tst-thp-align
+	$(SHELL) $< $(objpfx)ld.so '$(test-wrapper-env)' \
+		'$(run-program-env) GLIBC_TUNABLES=glibc.elf.thp=0' \
+		'$(rpath-link)' $(objpfx)tst-thp-align > $@; \
+	  $(evaluate-test)
+endif # [madvise]
+endif # $(cross-compiling)
 endif # $(subdir) == elf
 
 ifeq ($(subdir),rt)
diff --git a/sysdeps/unix/sysv/linux/arm/Makefile b/sysdeps/unix/sysv/linux/arm/Makefile
index e73ce4f811..1ee8bec9b9 100644
--- a/sysdeps/unix/sysv/linux/arm/Makefile
+++ b/sysdeps/unix/sysv/linux/arm/Makefile
@@ -3,6 +3,13 @@  sysdep-rtld-routines += aeabi_read_tp libc-do-syscall
 # The test uses INTERNAL_SYSCALL_CALL.  In thumb mode, this uses
 # an undefined reference to __libc_do_syscall.
 CFLAGS-tst-nolink-libc.c += -marm
+
+# These tests fail on arm due to limitations of arm32 kABI:
+# https://sourceware.org/bugzilla/show_bug.cgi?id=34096
+test-xfail-tst-thp-1-no-s-code-pde = yes
+test-xfail-tst-thp-1-no-s-code-static = yes
+test-xfail-tst-thp-1-pde = yes
+test-xfail-tst-thp-1-static = yes
 endif
 
 ifeq ($(subdir),misc)
diff --git a/sysdeps/unix/sysv/linux/dl-exec-post.h b/sysdeps/unix/sysv/linux/dl-exec-post.h
new file mode 100644
index 0000000000..69505df402
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/dl-exec-post.h
@@ -0,0 +1,126 @@ 
+/* _dl_executable_postprocess.  Linux version.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   Copyright The GNU Toolchain Authors.
+   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/>.  */
+
+static inline void
+_dl_get_thp_config (void)
+{
+  /* Check if there is GLIBC_TUNABLES=glibc.elf.thp=[0|1].  */
+  GL(dl_elf_thp_control) = TUNABLE_GET_FULL (glibc, elf, thp, int32_t,
+					     NULL);
+
+  /* Return if the tunable is not set or THP is disabled by the
+     tunable.  */
+  if (GL(dl_elf_thp_control) == 0)
+    return;
+
+  _Static_assert (DL_MAP_DEFAULT_THP_PAGESIZE <= MAX_THP_PAGESIZE,
+		  "DL_MAP_DEFAULT_THP_PAGESIZE <= MAX_THP_PAGESIZE");
+
+  /* NB: Accessing /sys/kernel/mm files is quite expensive and the file
+     may not be accessible in containers.  If DL_MAP_DEFAULT_THP_PAGESIZE
+     is non-zero, assume THP mode is madvise and always call madvise.
+     Since madvise is a fast system call, it adds only a small overhead
+     compared to the cost of accessing /sys/kernel/mm files.  */
+  if (DL_MAP_DEFAULT_THP_PAGESIZE != 0)
+    {
+      GL(dl_elf_thp_pagesize) = DL_MAP_DEFAULT_THP_PAGESIZE;
+      GL(dl_thp_mode) = thp_mode_madvise;
+    }
+  else
+    {
+      GL(dl_thp_mode) = __get_thp_mode ();
+      if (GL(dl_thp_mode) == thp_mode_always
+	  || GL(dl_thp_mode) == thp_mode_madvise)
+	{
+	  GL(dl_elf_thp_pagesize) = __get_thp_size ();
+	  /* We cap the huge page size at MAX_THP_PAGESIZE to avoid
+	     over-aligning on systems with very large normal pages
+	     (like 64K pages with 512M huge pages).  */
+	  if (GL(dl_elf_thp_pagesize) > MAX_THP_PAGESIZE)
+	    GL(dl_elf_thp_pagesize) = 0;
+	}
+      else
+	GL(dl_elf_thp_pagesize) = 0;
+
+      if (GL(dl_elf_thp_pagesize) == 0)
+	{
+	  GL(dl_elf_thp_control) = 0;
+	  GL(dl_thp_mode) = thp_mode_not_supported;
+	}
+    }
+}
+
+static inline void
+_dl_executable_postprocess (struct link_map *main_map,
+			    const ElfW(Phdr) *phdr, ElfW(Word) phnum)
+{
+  /* NB: In static executable, PT_GNU_PROPERTY is processed in target
+     libc-start.h if it is needed by target.  When ld.so is used, if
+     a target doesn't need PT_GNU_PROPERTY, _dl_process_pt_gnu_property
+     is an empty function.  */
+#ifdef SHARED
+  /* Process program headers again, but scan them backwards since
+     PT_GNU_PROPERTY is close to the end of program headers.   */
+  for (const ElfW(Phdr) *ph = &phdr[phnum]; ph != phdr; --ph)
+    if (ph[-1].p_type == PT_GNU_PROPERTY)
+      {
+	_dl_process_pt_gnu_property (main_map, -1, &ph[-1]);
+	break;
+      }
+#endif
+
+  /* If THP state was not yet initialized, the main executable was mapped
+     by the kernel; in that case this function is the only place that can
+     apply MADV_HUGEPAGE to the main executable's segments.  Otherwise,
+     _dl_get_thp_config has already run earlier in dl_main and
+     _dl_map_segments has just mapped the main executable, so
+     _dl_postprocess_loadcmd_extra has already done the madvise pass; do
+     not repeat it here.  */
+  if (GL(dl_elf_thp_control) != -1)
+    return;
+
+   _dl_get_thp_config ();
+
+  /* Return if THP segment load isn't enabled.  */
+  if (GL(dl_elf_thp_control) <= 0)
+    return;
+
+  /* NB: If DL_MAP_DEFAULT_THP_PAGESIZE is non-zero, dl_thp_mode is set
+     to thp_mode_madvise.  */
+  if (DL_MAP_DEFAULT_THP_PAGESIZE == 0
+      && GL(dl_thp_mode) != thp_mode_madvise)
+    return;
+
+  /* When we get here, the main executable have been mapped in.  Call
+     madvise with MADV_HUGEPAGE for all THP eligible PT_LOAD segments.  */
+
+  const ElfW(Phdr) *ph;
+
+  size_t thp_pagesize = GL(dl_elf_thp_pagesize);
+
+  /* Call __madvise if offset and address of the PT_LOAD segment are
+     aligned to THP page size and it is read-only.  */
+  for (ph = phdr; ph < &phdr[phnum]; ++ph)
+    if (ph->p_type == PT_LOAD
+	&& ph->p_memsz >= thp_pagesize
+	&& ((ph->p_vaddr | ph->p_offset) & (thp_pagesize - 1)) == 0
+	&& (ph->p_flags & (PF_W | PF_R)) == PF_R)
+      __madvise ((void *) (main_map->l_addr + ph->p_vaddr),
+		 ph->p_memsz, MADV_HUGEPAGE);
+}
diff --git a/sysdeps/unix/sysv/linux/dl-load-post.h b/sysdeps/unix/sysv/linux/dl-load-post.h
new file mode 100644
index 0000000000..88467764d4
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/dl-load-post.h
@@ -0,0 +1,32 @@ 
+/* _dl_postprocess_loadcmd_extra.  Linux version.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   Copyright The GNU Toolchain Authors.
+   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/>.  */
+
+static bool _dl_segment_thp_eligible (const struct loadcmd *, size_t);
+
+/* After L has been mapped in, call madvise with MADV_HUGEPAGE for THP
+   madvise mode if L is THP eligible.  */
+
+static inline void
+_dl_postprocess_loadcmd_extra (struct link_map *l, const struct loadcmd *c)
+{
+  if (GL(dl_thp_mode) == thp_mode_madvise
+      && _dl_segment_thp_eligible (c, GL(dl_elf_thp_pagesize)))
+    __madvise ((void *) (l->l_addr + c->mapstart),
+	       c->mapend - c->mapstart, MADV_HUGEPAGE);
+}
diff --git a/sysdeps/unix/sysv/linux/dl-map-segment-align.c b/sysdeps/unix/sysv/linux/dl-map-segment-align.c
index a39e74d91b..1260b26c22 100644
--- a/sysdeps/unix/sysv/linux/dl-map-segment-align.c
+++ b/sysdeps/unix/sysv/linux/dl-map-segment-align.c
@@ -17,38 +17,23 @@ 
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+#include <ldsodefs.h>
 #include <dl-map-segment-align.h>
-#include <dl-tunables.h>
-#include <hugepages.h>
+
+/* Return the alignment of the PT_LOAD segment for THP.  P_ALIGN_MAX is
+   the maximum p_align value in the PT_LOAD segment.  */
 
 ElfW (Addr)
 _dl_map_segment_align (const struct loadcmd *c, ElfW (Addr) p_align_max)
 {
-  static enum thp_mode_t thp_mode = thp_mode_not_supported;
-  static unsigned long int thp_pagesize;
+  size_t thp_pagesize = GL(dl_elf_thp_pagesize);
 
-  if (TUNABLE_GET (glibc, elf, thp, int32_t, NULL) == 0)
+  if (GL(dl_elf_thp_control) <= 0 || p_align_max >= thp_pagesize)
     return p_align_max;
 
-  if (__glibc_unlikely (thp_mode == thp_mode_not_supported
-                        || thp_pagesize == 0))
-    {
-      unsigned long int default_thp_pagesize = DL_MAP_DEFAULT_THP_PAGESIZE;
-      thp_mode = default_thp_pagesize ? thp_mode_always : __get_thp_mode ();
-      thp_pagesize = default_thp_pagesize ? : __get_thp_size ();
-    }
-
-  /* Aligning load segments that are large enough to the PMD size helps
-     improve THP eligibility and reduces TLB pressure.
-     We cap the huge page size at MAX_THP_PAGESIZE to avoid over-aligning
-     on systems with very large normal pages (like 64K pages with 512M
-     huge pages). */
-  if (thp_mode == thp_mode_always
-      && thp_pagesize <= MAX_THP_PAGESIZE
-      && ((c->mapstart | c->mapoff) & (thp_pagesize - 1)) == 0
-      && (c->mapend - c->mapstart) >= thp_pagesize
-      && p_align_max < thp_pagesize
-      && (c->prot & PROT_WRITE) == 0)
+  /* Return true if the segment is THP eligible.  It helps improve THP
+     eligibility and reduces TLB pressure.  */
+  if (_dl_segment_thp_eligible (c, thp_pagesize))
     return thp_pagesize;
 
   return p_align_max;
diff --git a/sysdeps/unix/sysv/linux/dl-map-segment-align.h b/sysdeps/unix/sysv/linux/dl-map-segment-align.h
index d9b05181b7..b904e128d8 100644
--- a/sysdeps/unix/sysv/linux/dl-map-segment-align.h
+++ b/sysdeps/unix/sysv/linux/dl-map-segment-align.h
@@ -19,9 +19,18 @@ 
 
 #include <dl-load.h>
 
-#ifndef DL_MAP_DEFAULT_THP_PAGESIZE
-# define DL_MAP_DEFAULT_THP_PAGESIZE	0
-#endif
-
 extern ElfW (Addr) _dl_map_segment_align
   (const struct loadcmd *, ElfW (Addr)) attribute_hidden;
+
+/* Return true only if the loadcmd C is THP eligible with THP page size
+   THP_PAGESIZE, which means that it is read-only, its size >= THP page
+   size, its offset and address of the loadcmd C are aligned to THP page
+   size.  */
+
+static inline bool
+_dl_segment_thp_eligible (const struct loadcmd *c, size_t thp_pagesize)
+{
+  return ((c->prot & PROT_WRITE) == 0
+	  && (c->mapend - c->mapstart) >= thp_pagesize
+	  && ((c->mapstart | c->mapoff) & (thp_pagesize - 1)) == 0);
+}
diff --git a/sysdeps/unix/sysv/linux/ldsodefs.h b/sysdeps/unix/sysv/linux/ldsodefs.h
index c63b649432..e39d9afe34 100644
--- a/sysdeps/unix/sysv/linux/ldsodefs.h
+++ b/sysdeps/unix/sysv/linux/ldsodefs.h
@@ -21,6 +21,9 @@ 
 /* We have the auxiliary vector.  */
 #define HAVE_AUX_VECTOR
 
+/* We have transparent huge page.  */
+#define HAVE_THP
+
 /* Get the real definitions.  */
 #include_next <ldsodefs.h>
 
diff --git a/sysdeps/unix/sysv/linux/loongarch/Makefile b/sysdeps/unix/sysv/linux/loongarch/Makefile
index 0d5f087862..d5beb62440 100644
--- a/sysdeps/unix/sysv/linux/loongarch/Makefile
+++ b/sysdeps/unix/sysv/linux/loongarch/Makefile
@@ -12,3 +12,6 @@  abi-ilp32s-condition	:= __WORDSIZE == 32 && defined __loongarch_soft_float
 abi-ilp32d-condition	:= __WORDSIZE == 32 && defined __loongarch_double_float
 abi-lp64s-condition	:= __WORDSIZE == 64 && defined __loongarch_soft_float
 abi-lp64d-condition	:= __WORDSIZE == 64 && defined __loongarch_double_float
+
+# Align THP tests to 32MB.
+THP-PAGE-SIZE = 0x2000000
diff --git a/sysdeps/unix/sysv/linux/loongarch/lp64/dl-map-segment-align.h b/sysdeps/unix/sysv/linux/loongarch/lp64/hugepages.h
similarity index 90%
rename from sysdeps/unix/sysv/linux/loongarch/lp64/dl-map-segment-align.h
rename to sysdeps/unix/sysv/linux/loongarch/lp64/hugepages.h
index c51ee4ac47..30252a9b86 100644
--- a/sysdeps/unix/sysv/linux/loongarch/lp64/dl-map-segment-align.h
+++ b/sysdeps/unix/sysv/linux/loongarch/lp64/hugepages.h
@@ -1,4 +1,4 @@ 
-/* _dl_map_segment_align.  LoongArch64 Linux version.
+/* Huge Page support.  LoongArch64 Linux version.
    Copyright (C) 2026 Free Software Foundation, Inc.
    Copyright The GNU Toolchain Authors.
    This file is part of the GNU C Library.
@@ -19,4 +19,4 @@ 
 
 #define DL_MAP_DEFAULT_THP_PAGESIZE (32 * 1024 * 1024)
 
-#include_next <dl-map-segment-align.h>
+#include_next <hugepages.h>
diff --git a/sysdeps/unix/sysv/linux/strace-tst-thp.sh b/sysdeps/unix/sysv/linux/strace-tst-thp.sh
new file mode 100644
index 0000000000..3ffb256c71
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/strace-tst-thp.sh
@@ -0,0 +1,80 @@ 
+#!/bin/bash
+# Run THP test under strace to verify control of the THP segment load.
+# 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/>.
+
+set -e
+
+case x"${1}" in
+*-static)
+  rtld=
+  test_wrapper_env="$2"
+  run_program_env="$3"
+  library_path=
+  test_prog="$1"
+  ;;
+*)
+  rtld="$1"
+  test_wrapper_env="$2"
+  run_program_env="$3"
+  library_path="$4"
+  test_prog="$5"
+  ;;
+esac
+
+
+if test x"${rtld}" = x; then
+  cmd="${test_wrapper_env} ${run_program_env} strace ${test_prog}"
+else
+  cmd="${test_wrapper_env} ${run_program_env} strace ${rtld} \
+       --library-path ${library_path} ${test_prog}"
+fi
+
+TIMEOUTFACTOR=${TIMEOUTFACTOR:-1}
+
+case x"${run_program_env}" in
+*glibc.elf.thp=1*)
+  strace_expected=yes
+  ;;
+*)
+  strace_expected=no
+  ;;
+esac
+
+# Verify strace is not just present, but works in this environment.  If
+# not, skip the test.
+/bin/sh -c \
+ "${test_wrapper_env} ${run_program_env} \
+  strace -e trace=none -- /bin/true" > /dev/null 2>&1 || exit 77
+
+# Finally the actual test inside the test environment, using the just
+# build ld.so and new libraries to run the THP test under strace.
+if /bin/sh -c \
+  "timeout -k 4 $((3*$TIMEOUTFACTOR)) ${cmd} --direct 2>&1 \
+   | grep -E \"madvise.*, MADV_HUGEPAGE\""; then
+  if test ${strace_expected} = yes; then
+    exit 0
+  else
+    exit 1
+  fi
+else
+  if test ${strace_expected} = no; then
+    exit 0
+  else
+    exit 1
+  fi
+fi
diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c
new file mode 100644
index 0000000000..3fd01e9bfe
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-pde.c
@@ -0,0 +1,19 @@ 
+/* Test PDE with THP segment load linked with -Wl,-z,noseparate-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
+   <http://www.gnu.org/licenses/>.  */
+
+#include "tst-thp-1.c"
diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c
new file mode 100644
index 0000000000..d0ae0f1ff0
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code-static.c
@@ -0,0 +1,19 @@ 
+/* Test static with THP segment load linked with -Wl,-z,noseparate-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
+   <http://www.gnu.org/licenses/>.  */
+
+#include "tst-thp-1.c"
diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c
new file mode 100644
index 0000000000..5eb1e005ed
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-thp-1-no-s-code.c
@@ -0,0 +1,19 @@ 
+/* Test THP segment load linked with -Wl,-z,noseparate-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
+   <http://www.gnu.org/licenses/>.  */
+
+#include "tst-thp-1.c"
diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-pde.c b/sysdeps/unix/sysv/linux/tst-thp-1-pde.c
new file mode 100644
index 0000000000..d854dd43da
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-thp-1-pde.c
@@ -0,0 +1,19 @@ 
+/* Test PDE with THP segment load linked with -Wl,-z,separate-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
+   <http://www.gnu.org/licenses/>.  */
+
+#include "tst-thp-1.c"
diff --git a/sysdeps/unix/sysv/linux/tst-thp-1-static.c b/sysdeps/unix/sysv/linux/tst-thp-1-static.c
new file mode 100644
index 0000000000..66d7e12954
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-thp-1-static.c
@@ -0,0 +1,19 @@ 
+/* Test static with THP segment load linked with -Wl,-z,separate-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
+   <http://www.gnu.org/licenses/>.  */
+
+#include "tst-thp-1.c"
diff --git a/sysdeps/unix/sysv/linux/tst-thp-1.c b/sysdeps/unix/sysv/linux/tst-thp-1.c
new file mode 100644
index 0000000000..49eea7069c
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-thp-1.c
@@ -0,0 +1,28 @@ 
+/* Test THP segment load linked with -Wl,-z,separate-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
+   <http://www.gnu.org/licenses/>.  */
+
+#include "tst-thp-align-check.h"
+
+static int
+do_test (void)
+{
+  check_align ("tst-thp-1");
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/tst-thp-align-check.h b/sysdeps/unix/sysv/linux/tst-thp-align-check.h
new file mode 100644
index 0000000000..8f1efc0ef1
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-thp-align-check.h
@@ -0,0 +1,124 @@ 
+/* Test the THP compatible alignment of PT_LOAD segments.
+
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   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 <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <intprops.h>
+#include <inttypes.h>
+#include <support/check.h>
+#include <support/xstdio.h>
+#include <support/xunistd.h>
+#undef attribute_hidden
+#define attribute_hidden
+#include <hugepages.h>	/* For enum thp_mode_t and MAX_THP_PAGESIZE.  */
+#undef attribute_hidden
+
+static unsigned long int
+get_thp_size (void)
+{
+  int fd = open ("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size",
+                 O_RDONLY, 0);
+  if (fd == -1)
+    return 0;
+
+  char str[INT_BUFSIZE_BOUND (unsigned long int)];
+  ssize_t s = read (fd, str, sizeof (str));
+  close (fd);
+  if (s < 0)
+    return 0;
+
+  unsigned long int r = 0;
+  for (ssize_t i = 0; i < s; i++)
+    {
+      if (str[i] == '\n')
+    break;
+      r *= 10;
+      r += str[i] - '0';
+    }
+  return r;
+}
+
+static enum thp_mode_t
+get_thp_mode (void)
+{
+  int fd = open ("/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY, 0);
+  if (fd == -1)
+    return thp_mode_not_supported;
+
+  static const char mode_always[]  = "[always] madvise never\n";
+  static const char mode_madvise[] = "always [madvise] never\n";
+  static const char mode_never[]   = "always madvise [never]\n";
+
+  char str[sizeof(mode_always)];
+  ssize_t s = read (fd, str, sizeof (str));
+  if (s >= sizeof str || s < 0)
+    return thp_mode_not_supported;
+  str[s] = '\0';
+  close (fd);
+
+  if (s == sizeof (mode_always) - 1)
+    {
+      if (strcmp (str, mode_always) == 0)
+    return thp_mode_always;
+      else if (strcmp (str, mode_madvise) == 0)
+    return thp_mode_madvise;
+      else if (strcmp (str, mode_never) == 0)
+    return thp_mode_never;
+    }
+  return thp_mode_not_supported;
+}
+
+static void
+check_align (const char *name)
+{
+  unsigned long int thp_size = get_thp_size ();
+  enum thp_mode_t thp_mode = get_thp_mode ();
+
+  if (thp_size == 0)
+    FAIL_UNSUPPORTED ("unable to get THP size.\n");
+
+  if (thp_size > MAX_THP_PAGESIZE)
+    FAIL_UNSUPPORTED ("THP size exceeds MAX_THP_PAGESIZE.\n");
+
+  if (thp_mode != thp_mode_always && thp_mode != thp_mode_madvise)
+    FAIL_UNSUPPORTED ("THP mode is not always nor madvise.\n");
+
+  FILE *f = xfopen ("/proc/self/maps", "r");
+  char *line = NULL;
+  size_t len;
+
+  while (xgetline (&line, &len, f))
+    {
+      uintptr_t from, to;
+      char *prot = NULL, *path = NULL;
+      int r = sscanf (line, "%" SCNxPTR "-%" SCNxPTR "%ms%*s%*s%*s%ms",
+                      &from, &to, &prot, &path);
+
+      TEST_VERIFY (r == 3 || r == 4);
+
+      if (strstr (prot, "x") && strstr (path, name))
+        TEST_COMPARE (from % thp_size, 0);
+
+      free (path);
+    }
+
+  free (line);
+  xfclose (f);
+}
diff --git a/sysdeps/unix/sysv/linux/tst-thp-align.c b/sysdeps/unix/sysv/linux/tst-thp-align.c
index 0b3f18e000..2e44109ba6 100644
--- a/sysdeps/unix/sysv/linux/tst-thp-align.c
+++ b/sysdeps/unix/sysv/linux/tst-thp-align.c
@@ -16,129 +16,10 @@ 
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-#include <fcntl.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <intprops.h>
-#include <inttypes.h>
-#include <support/check.h>
 #include <support/xdlfcn.h>
-#include <support/xstdio.h>
-#include <support/xunistd.h>
+#include "tst-thp-align-check.h"
 
 #define THP_SIZE_MOD_NAME "tst-thp-size-mod.so"
-#define MAX_THP_PAGESIZE (32 * 1024 * 1024)
-
-enum thp_mode_t
-{
-  thp_mode_always,
-  thp_mode_madvise,
-  thp_mode_never,
-  thp_mode_not_supported
-};
-
-static unsigned long int
-get_thp_size (void)
-{
-  int fd = open ("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size",
-                 O_RDONLY, 0);
-  if (fd == -1)
-    return 0;
-
-  char str[INT_BUFSIZE_BOUND (unsigned long int)];
-  ssize_t s = read (fd, str, sizeof (str));
-  close (fd);
-  if (s < 0)
-    return 0;
-
-  unsigned long int r = 0;
-  for (ssize_t i = 0; i < s; i++)
-    {
-      if (str[i] == '\n')
-    break;
-      r *= 10;
-      r += str[i] - '0';
-    }
-  return r;
-}
-
-static enum thp_mode_t
-get_thp_mode (void)
-{
-  int fd = open ("/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY, 0);
-  if (fd == -1)
-    return thp_mode_not_supported;
-
-  static const char mode_always[]  = "[always] madvise never\n";
-  static const char mode_madvise[] = "always [madvise] never\n";
-  static const char mode_never[]   = "always madvise [never]\n";
-
-  char str[sizeof(mode_always)];
-  ssize_t s = read (fd, str, sizeof (str));
-  if (s >= sizeof str || s < 0)
-    return thp_mode_not_supported;
-  str[s] = '\0';
-  close (fd);
-
-  if (s == sizeof (mode_always) - 1)
-    {
-      if (strcmp (str, mode_always) == 0)
-    return thp_mode_always;
-      else if (strcmp (str, mode_madvise) == 0)
-    return thp_mode_madvise;
-      else if (strcmp (str, mode_never) == 0)
-    return thp_mode_never;
-    }
-  return thp_mode_not_supported;
-}
-
-static void
-check_align (void)
-{
-  unsigned long int thp_size = get_thp_size ();
-  enum thp_mode_t thp_mode = get_thp_mode ();
-
-  if (thp_size == 0)
-    {
-      FAIL_UNSUPPORTED ("unable to get THP size.\n");
-      return;
-    }
-
-  if (thp_size > MAX_THP_PAGESIZE)
-    {
-      FAIL_UNSUPPORTED ("THP size exceeds MAX_THP_PAGESIZE.\n");
-      return;
-    }
-
-  if (thp_mode != thp_mode_always)
-    {
-      FAIL_UNSUPPORTED ("THP mode is not always.\n");
-      return;
-    }
-
-  FILE *f = xfopen ("/proc/self/maps", "r");
-  char *line = NULL;
-  size_t len;
-
-  while (xgetline (&line, &len, f))
-    {
-      uintptr_t from, to;
-      char *prot = NULL, *path = NULL;
-      int r = sscanf (line, "%" SCNxPTR "-%" SCNxPTR "%ms%*s%*s%*s%ms",
-                      &from, &to, &prot, &path);
-
-      TEST_VERIFY (r == 3 || r == 4);
-
-      if (strstr (prot, "x") && strstr (path, THP_SIZE_MOD_NAME))
-        TEST_COMPARE (from % thp_size, 0);
-
-      free (path);
-    }
-
-  free (line);
-  xfclose (f);
-}
 
 static int
 do_test (void)
@@ -146,7 +27,7 @@  do_test (void)
   void *dl;
 
   dl = xdlopen (THP_SIZE_MOD_NAME, RTLD_NOW);
-  check_align ();
+  check_align (THP_SIZE_MOD_NAME);
   xdlclose (dl);
 
   return 0;
diff --git a/sysdeps/unix/sysv/linux/x86/hugepages.h b/sysdeps/unix/sysv/linux/x86/hugepages.h
new file mode 100644
index 0000000000..1a8c370969
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/x86/hugepages.h
@@ -0,0 +1,22 @@ 
+/* Huge Page support.  Linux/x86 version.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   Copyright The GNU Toolchain Authors.
+   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/>.  */
+
+#define DL_MAP_DEFAULT_THP_PAGESIZE (2 * 1024 * 1024)
+
+#include_next <hugepages.h>
-- 
2.54.0