[v5,1/1] elf: Handle static PIE loaded at the specific address [BZ #31799]

Message ID 20240527143803.3550703-2-hjl.tools@gmail.com
State Under Review
Delegated to: Carlos O'Donell
Headers
Series elf: Handle static PIE loaded at the specific address |

Checks

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

Commit Message

H.J. Lu May 27, 2024, 2:38 p.m. UTC
  When a static PIE is loaded at the specific address, its PT_DYNAMIC
segment entries contain the relocated values for the load address.
Compute the load address from the p_vaddr of the PT_LOAD segement
which covers the file start.  This fixes BZ #31799.

Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
---
 configure                    | 67 ++++++++++++++++++++++++++++++++++++
 configure.ac                 | 30 ++++++++++++++++
 elf/Makefile                 | 20 +++++++++++
 elf/dl-reloc-static-pie.c    | 25 ++++++++++----
 elf/tst-pie-address-static.c | 19 ++++++++++
 elf/tst-pie-address.c        | 28 +++++++++++++++
 6 files changed, 182 insertions(+), 7 deletions(-)
 create mode 100644 elf/tst-pie-address-static.c
 create mode 100644 elf/tst-pie-address.c
  

Comments

Florian Weimer May 28, 2024, 6:05 a.m. UTC | #1
* H. J. Lu:

> When a static PIE is loaded at the specific address, its PT_DYNAMIC
> segment entries contain the relocated values for the load address.
> Compute the load address from the p_vaddr of the PT_LOAD segement
> which covers the file start.  This fixes BZ #31799.

A PIE loaded at a specific address is no longer PIE.

I think this should work:

cat > t.c <<EOF
#include <stdio.h>

int
main (void)
{
  puts ("Hello, world!");
}
EOF
ld --verbose  | sed -n -e '/^=/,/^=/{//!{s/0x400000/0x100000000/g;p}}' \
  > ld.script
gcc -fpie -static -Wl,-T,ld.script t.c

But it fails with:

/usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in function `_start':
(.text+0x1b): failed to convert GOTPCREL relocation against 'main'; relink with --no-relax
collect2: error: ld returned 1 exit status

This is the relocation:

  18:   48 8b 3d 00 00 00 00    mov    0x0(%rip),%rdi        # 1f <_start+0x1f>
                        1b: R_X86_64_REX_GOTPCRELX      main-0x4
  1f:   ff 15 00 00 00 00       call   *0x0(%rip)        # 25 <_start+0x25>
                        21: R_X86_64_GOTPCRELX  __libc_start_main-0x4

Shouldn't it be possible to relax it to this?

	lea main(%rip),%rdi

Thanks,
Florian
  
H.J. Lu May 28, 2024, 12:21 p.m. UTC | #2
On Mon, May 27, 2024 at 11:05 PM Florian Weimer <fweimer@redhat.com> wrote:
>
> * H. J. Lu:
>
> > When a static PIE is loaded at the specific address, its PT_DYNAMIC
> > segment entries contain the relocated values for the load address.
> > Compute the load address from the p_vaddr of the PT_LOAD segement
> > which covers the file start.  This fixes BZ #31799.
>
> A PIE loaded at a specific address is no longer PIE.
>
> I think this should work:
>
> cat > t.c <<EOF
> #include <stdio.h>
>
> int
> main (void)
> {
>   puts ("Hello, world!");
> }
> EOF
> ld --verbose  | sed -n -e '/^=/,/^=/{//!{s/0x400000/0x100000000/g;p}}' \
>   > ld.script
> gcc -fpie -static -Wl,-T,ld.script t.c
>
> But it fails with:
>
> /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in function `_start':
> (.text+0x1b): failed to convert GOTPCREL relocation against 'main'; relink with --no-relax
> collect2: error: ld returned 1 exit status

Did you mean?

[hjl@gnu-cfl-3 tmp]$ gcc -static -Wl,-Ttext-segment=0x100000000  x.c
/usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in
function `_start':
(.text+0x1b): failed to convert GOTPCREL relocation against 'main';
relink with --no-relax
/usr/local/bin/ld: final link failed
collect2: error: ld returned 1 exit status
[hjl@gnu-cfl-3 tmp]$ gcc -static -Wl,-Ttext-segment=0x100000000  x.c
/usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in
function `_start':
(.text+0x1b): failed to convert GOTPCREL relocation against 'main';
relink with --no-relax
/usr/local/bin/ld: final link failed
collect2: error: ld returned 1 exit status
[hjl@gnu-cfl-3 tmp]$

This no longer fits in the small model.

> This is the relocation:
>
>   18:   48 8b 3d 00 00 00 00    mov    0x0(%rip),%rdi        # 1f <_start+0x1f>
>                         1b: R_X86_64_REX_GOTPCRELX      main-0x4
>   1f:   ff 15 00 00 00 00       call   *0x0(%rip)        # 25 <_start+0x25>
>                         21: R_X86_64_GOTPCRELX  __libc_start_main-0x4
>
> Shouldn't it be possible to relax it to this?
>
>         lea main(%rip),%rdi
>

This is just a tip of the icebug when you try -Wl,-Ttext-segment=0x100000000
with PDE.  Linker can't help here.
  
Adhemerval Zanella Netto May 28, 2024, 2:28 p.m. UTC | #3
On 27/05/24 11:38, H.J. Lu wrote:
> When a static PIE is loaded at the specific address, its PT_DYNAMIC
> segment entries contain the relocated values for the load address.
> Compute the load address from the p_vaddr of the PT_LOAD segement
> which covers the file start.  This fixes BZ #31799.
> 
> Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
> ---
>  configure                    | 67 ++++++++++++++++++++++++++++++++++++
>  configure.ac                 | 30 ++++++++++++++++
>  elf/Makefile                 | 20 +++++++++++
>  elf/dl-reloc-static-pie.c    | 25 ++++++++++----
>  elf/tst-pie-address-static.c | 19 ++++++++++
>  elf/tst-pie-address.c        | 28 +++++++++++++++
>  6 files changed, 182 insertions(+), 7 deletions(-)
>  create mode 100644 elf/tst-pie-address-static.c
>  create mode 100644 elf/tst-pie-address.c
> 
> diff --git a/configure b/configure
> index 49b093043c..612fa0cf77 100755
> --- a/configure
> +++ b/configure
> @@ -7820,6 +7820,73 @@ printf "%s\n" "$libc_cv_cc_pie_default" >&6; }
>  config_vars="$config_vars
>  cc-pie-default = $libc_cv_cc_pie_default"
>  
> +# Get PDE load address.
> +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PDE load address" >&5
> +printf %s "checking PDE load address... " >&6; }
> +if test ${libc_cv_pde_load_address+y}
> +then :
> +  printf %s "(cached) " >&6
> +else $as_nop
> +  cat > conftest.S <<EOF
> +.globl _start
> +_start:
> +.globl __start
> +__start:
> +EOF
> +if test $libc_cv_cc_pie_default = yes; then
> +  pde_ld_flags="-no-pie"
> +fi
> +if ${CC-cc} $pde_ld_flags $CFLAGS $CPPFLAGS $LDFLAGS \
> +	    -nostartfiles -nostdlib $no_ssp \
> +	    -o conftest conftest.S 1>&5 2>&5; then
> +  # Get the load address of the first PT_LOAD segment.
> +  libc_cv_pde_load_address=$(LC_ALL=C $READELF -Wl conftest \
> +			     | $AWK '/LOAD/ { print $3; exit 0; }')
> +else
> +  as_fn_error $? "${CC-cc} can not create PDE" "$LINENO" 5
> +fi
> +rm -f conftest*
> +fi
> +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_cv_pde_load_address" >&5
> +printf "%s\n" "$libc_cv_pde_load_address" >&6; }
> +config_vars="$config_vars
> +pde-load-address = $libc_cv_pde_load_address"
> +
> +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for linker that supports -Ttext-segment=$libc_cv_pde_load_address" >&5
> +printf %s "checking for linker that supports -Ttext-segment=$libc_cv_pde_load_address... " >&6; }
> +libc_linker_feature=no
> +cat > conftest.c <<EOF
> +int _start (void) { return 42; }
> +EOF
> +if { ac_try='${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS $no_ssp
> +		  -Wl,-Ttext-segment=$libc_cv_pde_load_address -nostdlib -nostartfiles
> +		  -fPIC -shared -o conftest.so conftest.c
> +		  1>&5'
> +  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
> +  (eval $ac_try) 2>&5
> +  ac_status=$?
> +  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
> +  test $ac_status = 0; }; }
> +then
> +  if ${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS $no_ssp -Wl,-Ttext-segment=$libc_cv_pde_load_address -nostdlib \
> +      -nostartfiles -fPIC -shared -o conftest.so conftest.c 2>&1 \
> +      | grep "warning: -Ttext-segment=$libc_cv_pde_load_address ignored" > /dev/null 2>&1; then
> +    true
> +  else
> +    libc_linker_feature=yes
> +  fi
> +fi
> +rm -f conftest*
> +if test $libc_linker_feature = yes; then
> +  libc_cv_load_address=-Wl,-Ttext-segment
> +else
> +  libc_cv_load_address=
> +fi
> +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_linker_feature" >&5
> +printf "%s\n" "$libc_linker_feature" >&6; }
> +config_vars="$config_vars
> +load-address-ldflag = $libc_cv_load_address"
> +
>  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if we can build programs as PIE" >&5
>  printf %s "checking if we can build programs as PIE... " >&6; }
>  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
> diff --git a/configure.ac b/configure.ac
> index e48957f318..89a2f5d842 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -1721,6 +1721,36 @@ fi
>  rm -f conftest.*])
>  LIBC_CONFIG_VAR([cc-pie-default], [$libc_cv_cc_pie_default])
>  
> +# Get PDE load address.
> +AC_CACHE_CHECK([PDE load address],
> +	       libc_cv_pde_load_address, [dnl
> +cat > conftest.S <<EOF
> +.globl _start
> +_start:
> +.globl __start
> +__start:
> +EOF
> +if test $libc_cv_cc_pie_default = yes; then
> +  pde_ld_flags="-no-pie"
> +fi
> +if ${CC-cc} $pde_ld_flags $CFLAGS $CPPFLAGS $LDFLAGS \
> +	    -nostartfiles -nostdlib $no_ssp \
> +	    -o conftest conftest.S 1>&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD; then
> +  # Get the load address of the first PT_LOAD segment.
> +  libc_cv_pde_load_address=$(LC_ALL=C $READELF -Wl conftest \
> +			     | $AWK '/LOAD/ { print $3; exit 0; }')
> +else
> +  AC_MSG_ERROR([${CC-cc} can not create PDE])
> +fi
> +rm -f conftest*])
> +LIBC_CONFIG_VAR([pde-load-address], [$libc_cv_pde_load_address])

This does seems to work on ARM, RISCV, nor on loongarch (the current 
architectures that support static-pie):

aarch64-linux-gnu$ cat t.S
.globl _start
_start:
.globl __start
__start:
aarch64-linux-gnu$ aarch64-glibc-linux-gnu-gcc -g -O2 -static -nostartfiles -nostdlib -fno-stack-protector -o t t.S
aarch64-linux-gnu$ aarch64-glibc-linux-gnu-readelf -Wl t

There are no program headers in this file.
$ aarch64-glibc-linux-gnu-ld -v
GNU ld (GNU Binutils) 2.42.0.20240312

armv7a-linux-gnueabihf$ arm-glibc-linux-gnueabihf-gcc -g -O2 -static -nostartfiles -nostdlib -fno-stack-protector -o t t.S
armv7a-linux-gnueabihf$ arm-glibc-linux-gnueabihf-readelf -Wl -t

There are no program headers in this file.
armv7a-linux-gnueabihf$ arm-glibc-linux-ld -v
GNU ld (GNU Binutils) 2.42.0.20240312

I am not sure why BFD is not creating programs headers for this scenario,
but this makes the test innefective on these architectures.

> +
> +LIBC_LINKER_FEATURE([-Ttext-segment=$libc_cv_pde_load_address],
> +		    [-Wl,-Ttext-segment=$libc_cv_pde_load_address],
> +		    [libc_cv_load_address=-Wl,-Ttext-segment],
> +		    [libc_cv_load_address=])
> +LIBC_CONFIG_VAR([load-address-ldflag], [$libc_cv_load_address])
> +
>  AC_MSG_CHECKING(if we can build programs as PIE)
>  AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#ifdef PIE_UNSUPPORTED
>  # error PIE is not supported
> diff --git a/elf/Makefile b/elf/Makefile
> index 57b3a19d36..4e2d123b8e 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -1046,6 +1046,25 @@ tests-pie += \
>    tst-pie1 \
>    tst-pie2 \
>    # tests-pie
> +ifneq (,$(load-address-ldflag))
> +tests += \
> +  tst-pie-address \
> +  # tests
> +tests-pie += \
> +  tst-pie-address \
> +  # tests-pie
> +LDFLAGS-tst-pie-address += $(load-address-ldflag)=$(pde-load-address)
> +ifeq (yes,$(enable-static-pie))
> +tests += \
> +  tst-pie-address-static \
> +  # tests
> +tests-static += \
> +  tst-pie-address-static \
> +  # tests-static
> +LDFLAGS-tst-pie-address-static += \
> +  $(load-address-ldflag)=$(pde-load-address)
> +endif
> +endif


For the tst-pie-address-static test (where although is set as ET_EXEC, still has
DF_1_PIE) will the kernel always enforce the loading address set by -Wl,-Ttext-segment
or it might eventually take this just a hint and thus creating a possible false negative
in the future?

And reading all the discussion on BZ#31795, I am not really convinced that setting
ET_EXEC for a static-pie with a defined loading address makes much sense (even though
it tried to overcome a kernel limitation), specially that BFD seems to be only linker
that does it. Maybe we should check with the kernel so it can enforce the loading
address for DF_1_PIE instead of trying the ET_EXEC hack.

>  ifeq (yes,$(have-protected-data))
>  tests += vismain
>  tests-pie += vismain
> @@ -1896,6 +1915,7 @@ $(objpfx)tst-array5-static-cmp.out: tst-array5-static.exp \
>  
>  CFLAGS-tst-pie1.c += $(pie-ccflag)
>  CFLAGS-tst-pie2.c += $(pie-ccflag)
> +CFLAGS-tst-pie-address.c += $(pie-ccflag)
>  
>  $(objpfx)tst-piemod1.so: $(libsupport)
>  $(objpfx)tst-pie1: $(objpfx)tst-piemod1.so
> diff --git a/elf/dl-reloc-static-pie.c b/elf/dl-reloc-static-pie.c
> index 10c23d0bf0..6286f19f1f 100644
> --- a/elf/dl-reloc-static-pie.c
> +++ b/elf/dl-reloc-static-pie.c
> @@ -37,21 +37,32 @@ _dl_relocate_static_pie (void)
>  {
>    struct link_map *main_map = _dl_get_dl_main_map ();
>  
> -  /* Figure out the run-time load address of static PIE.  */
> -  main_map->l_addr = elf_machine_load_address ();
> -
> -  /* Read our own dynamic section and fill in the info array.  */
> -  main_map->l_ld = ((void *) main_map->l_addr + elf_machine_dynamic ());
> -
> +  ElfW(Addr) file_p_vaddr = 0;
>    const ElfW(Phdr) *ph, *phdr = GL(dl_phdr);
>    size_t phnum = GL(dl_phnum);
>    for (ph = phdr; ph < &phdr[phnum]; ++ph)
> -    if (ph->p_type == PT_DYNAMIC)
> +    switch (ph->p_type)
>        {
> +      case PT_LOAD:
> +	/* Get p_vaddr of the PT_LOAD segement which covers the file
> +	   start.  */
> +	if (ph->p_offset == 0)
> +	  file_p_vaddr = ph->p_vaddr;
> +	break;
> +      case PT_DYNAMIC:
>  	main_map->l_ld_readonly = (ph->p_flags & PF_W) == 0;
>  	break;
> +      default:
> +	break;
>        }
>  
> +  /* Figure out the run-time load address of static PIE.  */
> +  ElfW(Addr) l_addr = elf_machine_load_address ();
> +  main_map->l_addr = l_addr - file_p_vaddr;
> +
> +  /* Read our own dynamic section and fill in the info array.  */
> +  main_map->l_ld = ((void *) l_addr + elf_machine_dynamic ());
> +
>    elf_get_dynamic_info (main_map, false, true);
>  
>  # ifdef ELF_MACHINE_BEFORE_RTLD_RELOC
> diff --git a/elf/tst-pie-address-static.c b/elf/tst-pie-address-static.c
> new file mode 100644
> index 0000000000..25d27fb962
> --- /dev/null
> +++ b/elf/tst-pie-address-static.c
> @@ -0,0 +1,19 @@
> +/* Test static PIE loaded at the specific address.
> +   Copyright (C) 2024 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include "tst-pie-address.c"
> diff --git a/elf/tst-pie-address.c b/elf/tst-pie-address.c
> new file mode 100644
> index 0000000000..1f01783631
> --- /dev/null
> +++ b/elf/tst-pie-address.c
> @@ -0,0 +1,28 @@
> +/* Test PIE loaded at the specific address.
> +   Copyright (C) 2024 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <stdio.h>
> +
> +static int
> +do_test (void)
> +{
> +  printf ("Hello\n");
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
  
H.J. Lu May 28, 2024, 2:45 p.m. UTC | #4
On Tue, May 28, 2024 at 7:28 AM Adhemerval Zanella Netto
<adhemerval.zanella@linaro.org> wrote:
>
>
>
> On 27/05/24 11:38, H.J. Lu wrote:
> > When a static PIE is loaded at the specific address, its PT_DYNAMIC
> > segment entries contain the relocated values for the load address.
> > Compute the load address from the p_vaddr of the PT_LOAD segement
> > which covers the file start.  This fixes BZ #31799.
> >
> > Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
> > ---
> >  configure                    | 67 ++++++++++++++++++++++++++++++++++++
> >  configure.ac                 | 30 ++++++++++++++++
> >  elf/Makefile                 | 20 +++++++++++
> >  elf/dl-reloc-static-pie.c    | 25 ++++++++++----
> >  elf/tst-pie-address-static.c | 19 ++++++++++
> >  elf/tst-pie-address.c        | 28 +++++++++++++++
> >  6 files changed, 182 insertions(+), 7 deletions(-)
> >  create mode 100644 elf/tst-pie-address-static.c
> >  create mode 100644 elf/tst-pie-address.c
> >
> > diff --git a/configure b/configure
> > index 49b093043c..612fa0cf77 100755
> > --- a/configure
> > +++ b/configure
> > @@ -7820,6 +7820,73 @@ printf "%s\n" "$libc_cv_cc_pie_default" >&6; }
> >  config_vars="$config_vars
> >  cc-pie-default = $libc_cv_cc_pie_default"
> >
> > +# Get PDE load address.
> > +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PDE load address" >&5
> > +printf %s "checking PDE load address... " >&6; }
> > +if test ${libc_cv_pde_load_address+y}
> > +then :
> > +  printf %s "(cached) " >&6
> > +else $as_nop
> > +  cat > conftest.S <<EOF
> > +.globl _start
> > +_start:
> > +.globl __start
> > +__start:
> > +EOF
> > +if test $libc_cv_cc_pie_default = yes; then
> > +  pde_ld_flags="-no-pie"
> > +fi
> > +if ${CC-cc} $pde_ld_flags $CFLAGS $CPPFLAGS $LDFLAGS \
> > +         -nostartfiles -nostdlib $no_ssp \
> > +         -o conftest conftest.S 1>&5 2>&5; then
> > +  # Get the load address of the first PT_LOAD segment.
> > +  libc_cv_pde_load_address=$(LC_ALL=C $READELF -Wl conftest \
> > +                          | $AWK '/LOAD/ { print $3; exit 0; }')
> > +else
> > +  as_fn_error $? "${CC-cc} can not create PDE" "$LINENO" 5
> > +fi
> > +rm -f conftest*
> > +fi
> > +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_cv_pde_load_address" >&5
> > +printf "%s\n" "$libc_cv_pde_load_address" >&6; }
> > +config_vars="$config_vars
> > +pde-load-address = $libc_cv_pde_load_address"
> > +
> > +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for linker that supports -Ttext-segment=$libc_cv_pde_load_address" >&5
> > +printf %s "checking for linker that supports -Ttext-segment=$libc_cv_pde_load_address... " >&6; }
> > +libc_linker_feature=no
> > +cat > conftest.c <<EOF
> > +int _start (void) { return 42; }
> > +EOF
> > +if { ac_try='${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS $no_ssp
> > +               -Wl,-Ttext-segment=$libc_cv_pde_load_address -nostdlib -nostartfiles
> > +               -fPIC -shared -o conftest.so conftest.c
> > +               1>&5'
> > +  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
> > +  (eval $ac_try) 2>&5
> > +  ac_status=$?
> > +  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
> > +  test $ac_status = 0; }; }
> > +then
> > +  if ${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS $no_ssp -Wl,-Ttext-segment=$libc_cv_pde_load_address -nostdlib \
> > +      -nostartfiles -fPIC -shared -o conftest.so conftest.c 2>&1 \
> > +      | grep "warning: -Ttext-segment=$libc_cv_pde_load_address ignored" > /dev/null 2>&1; then
> > +    true
> > +  else
> > +    libc_linker_feature=yes
> > +  fi
> > +fi
> > +rm -f conftest*
> > +if test $libc_linker_feature = yes; then
> > +  libc_cv_load_address=-Wl,-Ttext-segment
> > +else
> > +  libc_cv_load_address=
> > +fi
> > +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_linker_feature" >&5
> > +printf "%s\n" "$libc_linker_feature" >&6; }
> > +config_vars="$config_vars
> > +load-address-ldflag = $libc_cv_load_address"
> > +
> >  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if we can build programs as PIE" >&5
> >  printf %s "checking if we can build programs as PIE... " >&6; }
> >  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
> > diff --git a/configure.ac b/configure.ac
> > index e48957f318..89a2f5d842 100644
> > --- a/configure.ac
> > +++ b/configure.ac
> > @@ -1721,6 +1721,36 @@ fi
> >  rm -f conftest.*])
> >  LIBC_CONFIG_VAR([cc-pie-default], [$libc_cv_cc_pie_default])
> >
> > +# Get PDE load address.
> > +AC_CACHE_CHECK([PDE load address],
> > +            libc_cv_pde_load_address, [dnl
> > +cat > conftest.S <<EOF
> > +.globl _start
> > +_start:
> > +.globl __start
> > +__start:
> > +EOF
> > +if test $libc_cv_cc_pie_default = yes; then
> > +  pde_ld_flags="-no-pie"
> > +fi
> > +if ${CC-cc} $pde_ld_flags $CFLAGS $CPPFLAGS $LDFLAGS \
> > +         -nostartfiles -nostdlib $no_ssp \
> > +         -o conftest conftest.S 1>&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD; then
> > +  # Get the load address of the first PT_LOAD segment.
> > +  libc_cv_pde_load_address=$(LC_ALL=C $READELF -Wl conftest \
> > +                          | $AWK '/LOAD/ { print $3; exit 0; }')
> > +else
> > +  AC_MSG_ERROR([${CC-cc} can not create PDE])
> > +fi
> > +rm -f conftest*])
> > +LIBC_CONFIG_VAR([pde-load-address], [$libc_cv_pde_load_address])
>
> This does seems to work on ARM, RISCV, nor on loongarch (the current
> architectures that support static-pie):
>
> aarch64-linux-gnu$ cat t.S
> .globl _start
> _start:
> .globl __start
> __start:
> aarch64-linux-gnu$ aarch64-glibc-linux-gnu-gcc -g -O2 -static -nostartfiles -nostdlib -fno-stack-protector -o t t.S
> aarch64-linux-gnu$ aarch64-glibc-linux-gnu-readelf -Wl t
>
> There are no program headers in this file.
> $ aarch64-glibc-linux-gnu-ld -v
> GNU ld (GNU Binutils) 2.42.0.20240312
>
> armv7a-linux-gnueabihf$ arm-glibc-linux-gnueabihf-gcc -g -O2 -static -nostartfiles -nostdlib -fno-stack-protector -o t t.S
> armv7a-linux-gnueabihf$ arm-glibc-linux-gnueabihf-readelf -Wl -t
>
> There are no program headers in this file.
> armv7a-linux-gnueabihf$ arm-glibc-linux-ld -v
> GNU ld (GNU Binutils) 2.42.0.20240312
>
> I am not sure why BFD is not creating programs headers for this scenario,
> but this makes the test innefective on these architectures.
>
> > +
> > +LIBC_LINKER_FEATURE([-Ttext-segment=$libc_cv_pde_load_address],
> > +                 [-Wl,-Ttext-segment=$libc_cv_pde_load_address],
> > +                 [libc_cv_load_address=-Wl,-Ttext-segment],
> > +                 [libc_cv_load_address=])
> > +LIBC_CONFIG_VAR([load-address-ldflag], [$libc_cv_load_address])
> > +
> >  AC_MSG_CHECKING(if we can build programs as PIE)
> >  AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#ifdef PIE_UNSUPPORTED
> >  # error PIE is not supported
> > diff --git a/elf/Makefile b/elf/Makefile
> > index 57b3a19d36..4e2d123b8e 100644
> > --- a/elf/Makefile
> > +++ b/elf/Makefile
> > @@ -1046,6 +1046,25 @@ tests-pie += \
> >    tst-pie1 \
> >    tst-pie2 \
> >    # tests-pie
> > +ifneq (,$(load-address-ldflag))
> > +tests += \
> > +  tst-pie-address \
> > +  # tests
> > +tests-pie += \
> > +  tst-pie-address \
> > +  # tests-pie
> > +LDFLAGS-tst-pie-address += $(load-address-ldflag)=$(pde-load-address)
> > +ifeq (yes,$(enable-static-pie))
> > +tests += \
> > +  tst-pie-address-static \
> > +  # tests
> > +tests-static += \
> > +  tst-pie-address-static \
> > +  # tests-static
> > +LDFLAGS-tst-pie-address-static += \
> > +  $(load-address-ldflag)=$(pde-load-address)
> > +endif
> > +endif
>
>
> For the tst-pie-address-static test (where although is set as ET_EXEC, still has
> DF_1_PIE) will the kernel always enforce the loading address set by -Wl,-Ttext-segment
> or it might eventually take this just a hint and thus creating a possible false negative
> in the future?

Kernel will guarantee PT_LOAD segment p_vaddr for ET_EXEC.   For ET_DYN,
kernel may load ET_DYN at some random address.   ET_EXEC is the only way
to always load a small model binary above 4G.

> And reading all the discussion on BZ#31795, I am not really convinced that setting
> ET_EXEC for a static-pie with a defined loading address makes much sense (even though

Without ET_EXEC, you don't know if a binary will be loaded at the
specific address.

> it tried to overcome a kernel limitation), specially that BFD seems to be only linker
> that does it. Maybe we should check with the kernel so it can enforce the loading
> address for DF_1_PIE instead of trying the ET_EXEC hack.
>
> >  ifeq (yes,$(have-protected-data))
> >  tests += vismain
> >  tests-pie += vismain
> > @@ -1896,6 +1915,7 @@ $(objpfx)tst-array5-static-cmp.out: tst-array5-static.exp \
> >
> >  CFLAGS-tst-pie1.c += $(pie-ccflag)
> >  CFLAGS-tst-pie2.c += $(pie-ccflag)
> > +CFLAGS-tst-pie-address.c += $(pie-ccflag)
> >
> >  $(objpfx)tst-piemod1.so: $(libsupport)
> >  $(objpfx)tst-pie1: $(objpfx)tst-piemod1.so
> > diff --git a/elf/dl-reloc-static-pie.c b/elf/dl-reloc-static-pie.c
> > index 10c23d0bf0..6286f19f1f 100644
> > --- a/elf/dl-reloc-static-pie.c
> > +++ b/elf/dl-reloc-static-pie.c
> > @@ -37,21 +37,32 @@ _dl_relocate_static_pie (void)
> >  {
> >    struct link_map *main_map = _dl_get_dl_main_map ();
> >
> > -  /* Figure out the run-time load address of static PIE.  */
> > -  main_map->l_addr = elf_machine_load_address ();
> > -
> > -  /* Read our own dynamic section and fill in the info array.  */
> > -  main_map->l_ld = ((void *) main_map->l_addr + elf_machine_dynamic ());
> > -
> > +  ElfW(Addr) file_p_vaddr = 0;
> >    const ElfW(Phdr) *ph, *phdr = GL(dl_phdr);
> >    size_t phnum = GL(dl_phnum);
> >    for (ph = phdr; ph < &phdr[phnum]; ++ph)
> > -    if (ph->p_type == PT_DYNAMIC)
> > +    switch (ph->p_type)
> >        {
> > +      case PT_LOAD:
> > +     /* Get p_vaddr of the PT_LOAD segement which covers the file
> > +        start.  */
> > +     if (ph->p_offset == 0)
> > +       file_p_vaddr = ph->p_vaddr;
> > +     break;
> > +      case PT_DYNAMIC:
> >       main_map->l_ld_readonly = (ph->p_flags & PF_W) == 0;
> >       break;
> > +      default:
> > +     break;
> >        }
> >
> > +  /* Figure out the run-time load address of static PIE.  */
> > +  ElfW(Addr) l_addr = elf_machine_load_address ();
> > +  main_map->l_addr = l_addr - file_p_vaddr;
> > +
> > +  /* Read our own dynamic section and fill in the info array.  */
> > +  main_map->l_ld = ((void *) l_addr + elf_machine_dynamic ());
> > +
> >    elf_get_dynamic_info (main_map, false, true);
> >
> >  # ifdef ELF_MACHINE_BEFORE_RTLD_RELOC
> > diff --git a/elf/tst-pie-address-static.c b/elf/tst-pie-address-static.c
> > new file mode 100644
> > index 0000000000..25d27fb962
> > --- /dev/null
> > +++ b/elf/tst-pie-address-static.c
> > @@ -0,0 +1,19 @@
> > +/* Test static PIE loaded at the specific address.
> > +   Copyright (C) 2024 Free Software Foundation, Inc.
> > +   This file is part of the GNU C Library.
> > +
> > +   The GNU C Library is free software; you can redistribute it and/or
> > +   modify it under the terms of the GNU Lesser General Public
> > +   License as published by the Free Software Foundation; either
> > +   version 2.1 of the License, or (at your option) any later version.
> > +
> > +   The GNU C Library is distributed in the hope that it will be useful,
> > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> > +   Lesser General Public License for more details.
> > +
> > +   You should have received a copy of the GNU Lesser General Public
> > +   License along with the GNU C Library; if not, see
> > +   <https://www.gnu.org/licenses/>.  */
> > +
> > +#include "tst-pie-address.c"
> > diff --git a/elf/tst-pie-address.c b/elf/tst-pie-address.c
> > new file mode 100644
> > index 0000000000..1f01783631
> > --- /dev/null
> > +++ b/elf/tst-pie-address.c
> > @@ -0,0 +1,28 @@
> > +/* Test PIE loaded at the specific address.
> > +   Copyright (C) 2024 Free Software Foundation, Inc.
> > +   This file is part of the GNU C Library.
> > +
> > +   The GNU C Library is free software; you can redistribute it and/or
> > +   modify it under the terms of the GNU Lesser General Public
> > +   License as published by the Free Software Foundation; either
> > +   version 2.1 of the License, or (at your option) any later version.
> > +
> > +   The GNU C Library is distributed in the hope that it will be useful,
> > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> > +   Lesser General Public License for more details.
> > +
> > +   You should have received a copy of the GNU Lesser General Public
> > +   License along with the GNU C Library; if not, see
> > +   <https://www.gnu.org/licenses/>.  */
> > +
> > +#include <stdio.h>
> > +
> > +static int
> > +do_test (void)
> > +{
> > +  printf ("Hello\n");
> > +  return 0;
> > +}
> > +
> > +#include <support/test-driver.c>
  
Florian Weimer June 14, 2024, 10:36 a.m. UTC | #5
* H. J. Lu:

> On Mon, May 27, 2024 at 11:05 PM Florian Weimer <fweimer@redhat.com> wrote:
>>
>> * H. J. Lu:
>>
>> > When a static PIE is loaded at the specific address, its PT_DYNAMIC
>> > segment entries contain the relocated values for the load address.
>> > Compute the load address from the p_vaddr of the PT_LOAD segement
>> > which covers the file start.  This fixes BZ #31799.
>>
>> A PIE loaded at a specific address is no longer PIE.
>>
>> I think this should work:
>>
>> cat > t.c <<EOF
>> #include <stdio.h>
>>
>> int
>> main (void)
>> {
>>   puts ("Hello, world!");
>> }
>> EOF
>> ld --verbose  | sed -n -e '/^=/,/^=/{//!{s/0x400000/0x100000000/g;p}}' \
>>   > ld.script
>> gcc -fpie -static -Wl,-T,ld.script t.c
>>
>> But it fails with:
>>
>> /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in function `_start':
>> (.text+0x1b): failed to convert GOTPCREL relocation against 'main'; relink with --no-relax
>> collect2: error: ld returned 1 exit status
>
> Did you mean?
>
> [hjl@gnu-cfl-3 tmp]$ gcc -static -Wl,-Ttext-segment=0x100000000  x.c
> /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in
> function `_start':
> (.text+0x1b): failed to convert GOTPCREL relocation against 'main';
> relink with --no-relax
> /usr/local/bin/ld: final link failed
> collect2: error: ld returned 1 exit status
> [hjl@gnu-cfl-3 tmp]$ gcc -static -Wl,-Ttext-segment=0x100000000  x.c
> /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in
> function `_start':
> (.text+0x1b): failed to convert GOTPCREL relocation against 'main';
> relink with --no-relax
> /usr/local/bin/ld: final link failed
> collect2: error: ld returned 1 exit status
> [hjl@gnu-cfl-3 tmp]$

Sort of, but I compiled with PIE as well.

> This no longer fits in the small model.

You need to *compile* as PIE.  The link editor should be able to produce
a position-dependent executable that is loaded at an arbitrary address
from such relocatable input files.  The small model and GOTPCREL still
work because the GOT is still less than 2 GiB away from the code.  This
is just a linker limitation.

Thanks,
Florian
  
H.J. Lu June 15, 2024, 1:59 a.m. UTC | #6
On Fri, Jun 14, 2024 at 12:36:30PM +0200, Florian Weimer wrote:
> * H. J. Lu:
> 
> > On Mon, May 27, 2024 at 11:05 PM Florian Weimer <fweimer@redhat.com> wrote:
> >>
> >> * H. J. Lu:
> >>
> >> > When a static PIE is loaded at the specific address, its PT_DYNAMIC
> >> > segment entries contain the relocated values for the load address.
> >> > Compute the load address from the p_vaddr of the PT_LOAD segement
> >> > which covers the file start.  This fixes BZ #31799.
> >>
> >> A PIE loaded at a specific address is no longer PIE.
> >>
> >> I think this should work:
> >>
> >> cat > t.c <<EOF
> >> #include <stdio.h>
> >>
> >> int
> >> main (void)
> >> {
> >>   puts ("Hello, world!");
> >> }
> >> EOF
> >> ld --verbose  | sed -n -e '/^=/,/^=/{//!{s/0x400000/0x100000000/g;p}}' \
> >>   > ld.script
> >> gcc -fpie -static -Wl,-T,ld.script t.c
> >>
> >> But it fails with:
> >>
> >> /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in function `_start':
> >> (.text+0x1b): failed to convert GOTPCREL relocation against 'main'; relink with --no-relax
> >> collect2: error: ld returned 1 exit status
> >
> > Did you mean?
> >
> > [hjl@gnu-cfl-3 tmp]$ gcc -static -Wl,-Ttext-segment=0x100000000  x.c
> > /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in
> > function `_start':
> > (.text+0x1b): failed to convert GOTPCREL relocation against 'main';
> > relink with --no-relax
> > /usr/local/bin/ld: final link failed
> > collect2: error: ld returned 1 exit status
> > [hjl@gnu-cfl-3 tmp]$ gcc -static -Wl,-Ttext-segment=0x100000000  x.c
> > /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in
> > function `_start':
> > (.text+0x1b): failed to convert GOTPCREL relocation against 'main';
> > relink with --no-relax
> > /usr/local/bin/ld: final link failed
> > collect2: error: ld returned 1 exit status
> > [hjl@gnu-cfl-3 tmp]$
> 
> Sort of, but I compiled with PIE as well.
> 
> > This no longer fits in the small model.
> 
> You need to *compile* as PIE.  The link editor should be able to produce
> a position-dependent executable that is loaded at an arbitrary address
> from such relocatable input files.  The small model and GOTPCREL still
> work because the GOT is still less than 2 GiB away from the code.  This
> is just a linker limitation.
> 

There are no large model *crt*.o nor .a files.  The only way to create
a small model executable loaded at a specific address above 4GB is to
build it as PIE with non-zero load address and marked as ET_EXEC.

If you believe it is a linker issue, please open a linker bug.

H.J.
  
H.J. Lu June 30, 2024, 8:47 p.m. UTC | #7
On Mon, May 27, 2024, 10:38 PM H.J. Lu <hjl.tools@gmail.com> wrote:

> When a static PIE is loaded at the specific address, its PT_DYNAMIC
> segment entries contain the relocated values for the load address.
> Compute the load address from the p_vaddr of the PT_LOAD segement
> which covers the file start.  This fixes BZ #31799.
>
> Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
> ---
>  configure                    | 67 ++++++++++++++++++++++++++++++++++++
>  configure.ac                 | 30 ++++++++++++++++
>  elf/Makefile                 | 20 +++++++++++
>  elf/dl-reloc-static-pie.c    | 25 ++++++++++----
>  elf/tst-pie-address-static.c | 19 ++++++++++
>  elf/tst-pie-address.c        | 28 +++++++++++++++
>  6 files changed, 182 insertions(+), 7 deletions(-)
>  create mode 100644 elf/tst-pie-address-static.c
>  create mode 100644 elf/tst-pie-address.c
>
> diff --git a/configure b/configure
> index 49b093043c..612fa0cf77 100755
> --- a/configure
> +++ b/configure
> @@ -7820,6 +7820,73 @@ printf "%s\n" "$libc_cv_cc_pie_default" >&6; }
>  config_vars="$config_vars
>  cc-pie-default = $libc_cv_cc_pie_default"
>
> +# Get PDE load address.
> +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PDE load address"
> >&5
> +printf %s "checking PDE load address... " >&6; }
> +if test ${libc_cv_pde_load_address+y}
> +then :
> +  printf %s "(cached) " >&6
> +else $as_nop
> +  cat > conftest.S <<EOF
> +.globl _start
> +_start:
> +.globl __start
> +__start:
> +EOF
> +if test $libc_cv_cc_pie_default = yes; then
> +  pde_ld_flags="-no-pie"
> +fi
> +if ${CC-cc} $pde_ld_flags $CFLAGS $CPPFLAGS $LDFLAGS \
> +           -nostartfiles -nostdlib $no_ssp \
> +           -o conftest conftest.S 1>&5 2>&5; then
> +  # Get the load address of the first PT_LOAD segment.
> +  libc_cv_pde_load_address=$(LC_ALL=C $READELF -Wl conftest \
> +                            | $AWK '/LOAD/ { print $3; exit 0; }')
> +else
> +  as_fn_error $? "${CC-cc} can not create PDE" "$LINENO" 5
> +fi
> +rm -f conftest*
> +fi
> +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result:
> $libc_cv_pde_load_address" >&5
> +printf "%s\n" "$libc_cv_pde_load_address" >&6; }
> +config_vars="$config_vars
> +pde-load-address = $libc_cv_pde_load_address"
> +
> +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for linker that
> supports -Ttext-segment=$libc_cv_pde_load_address" >&5
> +printf %s "checking for linker that supports
> -Ttext-segment=$libc_cv_pde_load_address... " >&6; }
> +libc_linker_feature=no
> +cat > conftest.c <<EOF
> +int _start (void) { return 42; }
> +EOF
> +if { ac_try='${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS $no_ssp
> +                 -Wl,-Ttext-segment=$libc_cv_pde_load_address -nostdlib
> -nostartfiles
> +                 -fPIC -shared -o conftest.so conftest.c
> +                 1>&5'
> +  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
> +  (eval $ac_try) 2>&5
> +  ac_status=$?
> +  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
> +  test $ac_status = 0; }; }
> +then
> +  if ${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS $no_ssp
> -Wl,-Ttext-segment=$libc_cv_pde_load_address -nostdlib \
> +      -nostartfiles -fPIC -shared -o conftest.so conftest.c 2>&1 \
> +      | grep "warning: -Ttext-segment=$libc_cv_pde_load_address ignored"
> > /dev/null 2>&1; then
> +    true
> +  else
> +    libc_linker_feature=yes
> +  fi
> +fi
> +rm -f conftest*
> +if test $libc_linker_feature = yes; then
> +  libc_cv_load_address=-Wl,-Ttext-segment
> +else
> +  libc_cv_load_address=
> +fi
> +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result:
> $libc_linker_feature" >&5
> +printf "%s\n" "$libc_linker_feature" >&6; }
> +config_vars="$config_vars
> +load-address-ldflag = $libc_cv_load_address"
> +
>  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if we can build
> programs as PIE" >&5
>  printf %s "checking if we can build programs as PIE... " >&6; }
>  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
> diff --git a/configure.ac b/configure.ac
> index e48957f318..89a2f5d842 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -1721,6 +1721,36 @@ fi
>  rm -f conftest.*])
>  LIBC_CONFIG_VAR([cc-pie-default], [$libc_cv_cc_pie_default])
>
> +# Get PDE load address.
> +AC_CACHE_CHECK([PDE load address],
> +              libc_cv_pde_load_address, [dnl
> +cat > conftest.S <<EOF
> +.globl _start
> +_start:
> +.globl __start
> +__start:
> +EOF
> +if test $libc_cv_cc_pie_default = yes; then
> +  pde_ld_flags="-no-pie"
> +fi
> +if ${CC-cc} $pde_ld_flags $CFLAGS $CPPFLAGS $LDFLAGS \
> +           -nostartfiles -nostdlib $no_ssp \
> +           -o conftest conftest.S 1>&AS_MESSAGE_LOG_FD
> 2>&AS_MESSAGE_LOG_FD; then
> +  # Get the load address of the first PT_LOAD segment.
> +  libc_cv_pde_load_address=$(LC_ALL=C $READELF -Wl conftest \
> +                            | $AWK '/LOAD/ { print $3; exit 0; }')
> +else
> +  AC_MSG_ERROR([${CC-cc} can not create PDE])
> +fi
> +rm -f conftest*])
> +LIBC_CONFIG_VAR([pde-load-address], [$libc_cv_pde_load_address])
> +
> +LIBC_LINKER_FEATURE([-Ttext-segment=$libc_cv_pde_load_address],
> +                   [-Wl,-Ttext-segment=$libc_cv_pde_load_address],
> +                   [libc_cv_load_address=-Wl,-Ttext-segment],
> +                   [libc_cv_load_address=])
> +LIBC_CONFIG_VAR([load-address-ldflag], [$libc_cv_load_address])
> +
>  AC_MSG_CHECKING(if we can build programs as PIE)
>  AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#ifdef PIE_UNSUPPORTED
>  # error PIE is not supported
> diff --git a/elf/Makefile b/elf/Makefile
> index 57b3a19d36..4e2d123b8e 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -1046,6 +1046,25 @@ tests-pie += \
>    tst-pie1 \
>    tst-pie2 \
>    # tests-pie
> +ifneq (,$(load-address-ldflag))
> +tests += \
> +  tst-pie-address \
> +  # tests
> +tests-pie += \
> +  tst-pie-address \
> +  # tests-pie
> +LDFLAGS-tst-pie-address += $(load-address-ldflag)=$(pde-load-address)
> +ifeq (yes,$(enable-static-pie))
> +tests += \
> +  tst-pie-address-static \
> +  # tests
> +tests-static += \
> +  tst-pie-address-static \
> +  # tests-static
> +LDFLAGS-tst-pie-address-static += \
> +  $(load-address-ldflag)=$(pde-load-address)
> +endif
> +endif
>  ifeq (yes,$(have-protected-data))
>  tests += vismain
>  tests-pie += vismain
> @@ -1896,6 +1915,7 @@ $(objpfx)tst-array5-static-cmp.out:
> tst-array5-static.exp \
>
>  CFLAGS-tst-pie1.c += $(pie-ccflag)
>  CFLAGS-tst-pie2.c += $(pie-ccflag)
> +CFLAGS-tst-pie-address.c += $(pie-ccflag)
>
>  $(objpfx)tst-piemod1.so: $(libsupport)
>  $(objpfx)tst-pie1: $(objpfx)tst-piemod1.so
> diff --git a/elf/dl-reloc-static-pie.c b/elf/dl-reloc-static-pie.c
> index 10c23d0bf0..6286f19f1f 100644
> --- a/elf/dl-reloc-static-pie.c
> +++ b/elf/dl-reloc-static-pie.c
> @@ -37,21 +37,32 @@ _dl_relocate_static_pie (void)
>  {
>    struct link_map *main_map = _dl_get_dl_main_map ();
>
> -  /* Figure out the run-time load address of static PIE.  */
> -  main_map->l_addr = elf_machine_load_address ();
> -
> -  /* Read our own dynamic section and fill in the info array.  */
> -  main_map->l_ld = ((void *) main_map->l_addr + elf_machine_dynamic ());
> -
> +  ElfW(Addr) file_p_vaddr = 0;
>    const ElfW(Phdr) *ph, *phdr = GL(dl_phdr);
>    size_t phnum = GL(dl_phnum);
>    for (ph = phdr; ph < &phdr[phnum]; ++ph)
> -    if (ph->p_type == PT_DYNAMIC)
> +    switch (ph->p_type)
>        {
> +      case PT_LOAD:
> +       /* Get p_vaddr of the PT_LOAD segement which covers the file
> +          start.  */
> +       if (ph->p_offset == 0)
> +         file_p_vaddr = ph->p_vaddr;
> +       break;
> +      case PT_DYNAMIC:
>         main_map->l_ld_readonly = (ph->p_flags & PF_W) == 0;
>         break;
> +      default:
> +       break;
>        }
>
> +  /* Figure out the run-time load address of static PIE.  */
> +  ElfW(Addr) l_addr = elf_machine_load_address ();
> +  main_map->l_addr = l_addr - file_p_vaddr;
> +
> +  /* Read our own dynamic section and fill in the info array.  */
> +  main_map->l_ld = ((void *) l_addr + elf_machine_dynamic ());
> +
>    elf_get_dynamic_info (main_map, false, true);
>
>  # ifdef ELF_MACHINE_BEFORE_RTLD_RELOC
> diff --git a/elf/tst-pie-address-static.c b/elf/tst-pie-address-static.c
> new file mode 100644
> index 0000000000..25d27fb962
> --- /dev/null
> +++ b/elf/tst-pie-address-static.c
> @@ -0,0 +1,19 @@
> +/* Test static PIE loaded at the specific address.
> +   Copyright (C) 2024 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include "tst-pie-address.c"
> diff --git a/elf/tst-pie-address.c b/elf/tst-pie-address.c
> new file mode 100644
> index 0000000000..1f01783631
> --- /dev/null
> +++ b/elf/tst-pie-address.c
> @@ -0,0 +1,28 @@
> +/* Test PIE loaded at the specific address.
> +   Copyright (C) 2024 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <stdio.h>
> +
> +static int
> +do_test (void)
> +{
> +  printf ("Hello\n");
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> --
> 2.45.1
>

Ping.


>
  
Florian Weimer July 1, 2024, 5:22 p.m. UTC | #8
* H. J. Lu:

> On Fri, Jun 14, 2024 at 12:36:30PM +0200, Florian Weimer wrote:
>> * H. J. Lu:
>> 
>> > On Mon, May 27, 2024 at 11:05 PM Florian Weimer <fweimer@redhat.com> wrote:
>> >>
>> >> * H. J. Lu:
>> >>
>> >> > When a static PIE is loaded at the specific address, its PT_DYNAMIC
>> >> > segment entries contain the relocated values for the load address.
>> >> > Compute the load address from the p_vaddr of the PT_LOAD segement
>> >> > which covers the file start.  This fixes BZ #31799.
>> >>
>> >> A PIE loaded at a specific address is no longer PIE.
>> >>
>> >> I think this should work:
>> >>
>> >> cat > t.c <<EOF
>> >> #include <stdio.h>
>> >>
>> >> int
>> >> main (void)
>> >> {
>> >>   puts ("Hello, world!");
>> >> }
>> >> EOF
>> >> ld --verbose  | sed -n -e '/^=/,/^=/{//!{s/0x400000/0x100000000/g;p}}' \
>> >>   > ld.script
>> >> gcc -fpie -static -Wl,-T,ld.script t.c
>> >>
>> >> But it fails with:
>> >>
>> >> /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in function `_start':
>> >> (.text+0x1b): failed to convert GOTPCREL relocation against 'main'; relink with --no-relax
>> >> collect2: error: ld returned 1 exit status
>> >
>> > Did you mean?
>> >
>> > [hjl@gnu-cfl-3 tmp]$ gcc -static -Wl,-Ttext-segment=0x100000000  x.c
>> > /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in
>> > function `_start':
>> > (.text+0x1b): failed to convert GOTPCREL relocation against 'main';
>> > relink with --no-relax
>> > /usr/local/bin/ld: final link failed
>> > collect2: error: ld returned 1 exit status
>> > [hjl@gnu-cfl-3 tmp]$ gcc -static -Wl,-Ttext-segment=0x100000000  x.c
>> > /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in
>> > function `_start':
>> > (.text+0x1b): failed to convert GOTPCREL relocation against 'main';
>> > relink with --no-relax
>> > /usr/local/bin/ld: final link failed
>> > collect2: error: ld returned 1 exit status
>> > [hjl@gnu-cfl-3 tmp]$
>> 
>> Sort of, but I compiled with PIE as well.
>> 
>> > This no longer fits in the small model.
>> 
>> You need to *compile* as PIE.  The link editor should be able to produce
>> a position-dependent executable that is loaded at an arbitrary address
>> from such relocatable input files.  The small model and GOTPCREL still
>> work because the GOT is still less than 2 GiB away from the code.  This
>> is just a linker limitation.
>> 
>
> There are no large model *crt*.o nor .a files.  The only way to create
> a small model executable loaded at a specific address above 4GB is to
> build it as PIE with non-zero load address and marked as ET_EXEC.

This works for me:

cat > t.c <<EOF
#include <stdio.h>

char *volatile s = "Hello, world!"; /* Force relocation.  */

int
main (void)
{
  printf ("%s at %p\n", s, s);
}
EOF
ld --verbose  | sed -n -e '/^=/,/^=/{//!{s/0x400000/0x100000000/g;p}}' \
  > ld.script
gcc -fpie -pie -Wl,-T,ld.script,--no-relax t.c

$ ./a.out 
Hello, world! at 0x100002004
$ readelf -hW ./a.out 
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x100001050
  Start of program headers:          64 (bytes into file)
  Start of section headers:          14728 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         13
  Size of section headers:           64 (bytes)
  Number of section headers:         33
  Section header string table index: 32

So it can be done, for dynamic linking.

My preference would still be to change the static non-PIE objects not to
use absolute 32-bit relocations, so that they can be used for this.
Rather than changing the PIE-specific objects to work with ET_EXEC
binaries, too.

Thanks,
Florian
  
H.J. Lu July 1, 2024, 9:26 p.m. UTC | #9
On Tue, Jul 2, 2024, 1:22 AM Florian Weimer <fweimer@redhat.com> wrote:

> * H. J. Lu:
>
> > On Fri, Jun 14, 2024 at 12:36:30PM +0200, Florian Weimer wrote:
> >> * H. J. Lu:
> >>
> >> > On Mon, May 27, 2024 at 11:05 PM Florian Weimer <fweimer@redhat.com>
> wrote:
> >> >>
> >> >> * H. J. Lu:
> >> >>
> >> >> > When a static PIE is loaded at the specific address, its PT_DYNAMIC
> >> >> > segment entries contain the relocated values for the load address.
> >> >> > Compute the load address from the p_vaddr of the PT_LOAD segement
> >> >> > which covers the file start.  This fixes BZ #31799.
> >> >>
> >> >> A PIE loaded at a specific address is no longer PIE.
> >> >>
> >> >> I think this should work:
> >> >>
> >> >> cat > t.c <<EOF
> >> >> #include <stdio.h>
> >> >>
> >> >> int
> >> >> main (void)
> >> >> {
> >> >>   puts ("Hello, world!");
> >> >> }
> >> >> EOF
> >> >> ld --verbose  | sed -n -e
> '/^=/,/^=/{//!{s/0x400000/0x100000000/g;p}}' \
> >> >>   > ld.script
> >> >> gcc -fpie -static -Wl,-T,ld.script t.c
> >> >>
> >> >> But it fails with:
> >> >>
> >> >> /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in
> function `_start':
> >> >> (.text+0x1b): failed to convert GOTPCREL relocation against 'main';
> relink with --no-relax
> >> >> collect2: error: ld returned 1 exit status
> >> >
> >> > Did you mean?
> >> >
> >> > [hjl@gnu-cfl-3 tmp]$ gcc -static -Wl,-Ttext-segment=0x100000000  x.c
> >> > /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in
> >> > function `_start':
> >> > (.text+0x1b): failed to convert GOTPCREL relocation against 'main';
> >> > relink with --no-relax
> >> > /usr/local/bin/ld: final link failed
> >> > collect2: error: ld returned 1 exit status
> >> > [hjl@gnu-cfl-3 tmp]$ gcc -static -Wl,-Ttext-segment=0x100000000  x.c
> >> > /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o: in
> >> > function `_start':
> >> > (.text+0x1b): failed to convert GOTPCREL relocation against 'main';
> >> > relink with --no-relax
> >> > /usr/local/bin/ld: final link failed
> >> > collect2: error: ld returned 1 exit status
> >> > [hjl@gnu-cfl-3 tmp]$
> >>
> >> Sort of, but I compiled with PIE as well.
> >>
> >> > This no longer fits in the small model.
> >>
> >> You need to *compile* as PIE.  The link editor should be able to produce
> >> a position-dependent executable that is loaded at an arbitrary address
> >> from such relocatable input files.  The small model and GOTPCREL still
> >> work because the GOT is still less than 2 GiB away from the code.  This
> >> is just a linker limitation.
> >>
> >
> > There are no large model *crt*.o nor .a files.  The only way to create
> > a small model executable loaded at a specific address above 4GB is to
> > build it as PIE with non-zero load address and marked as ET_EXEC.
>
> This works for me:
>
> cat > t.c <<EOF
> #include <stdio.h>
>
> char *volatile s = "Hello, world!"; /* Force relocation.  */
>
> int
> main (void)
> {
>   printf ("%s at %p\n", s, s);
> }
> EOF
> ld --verbose  | sed -n -e '/^=/,/^=/{//!{s/0x400000/0x100000000/g;p}}' \
>   > ld.script
> gcc -fpie -pie -Wl,-T,ld.script,--no-relax t.c
>
> $ ./a.out
> Hello, world! at 0x100002004
> $ readelf -hW ./a.out
> ELF Header:
>   Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
>   Class:                             ELF64
>   Data:                              2's complement, little endian
>   Version:                           1 (current)
>   OS/ABI:                            UNIX - System V
>   ABI Version:                       0
>   Type:                              EXEC (Executable file)
>   Machine:                           Advanced Micro Devices X86-64
>   Version:                           0x1
>   Entry point address:               0x100001050
>   Start of program headers:          64 (bytes into file)
>   Start of section headers:          14728 (bytes into file)
>   Flags:                             0x0
>   Size of this header:               64 (bytes)
>   Size of program headers:           56 (bytes)
>   Number of program headers:         13
>   Size of section headers:           64 (bytes)
>   Number of section headers:         33
>   Section header string table index: 32
>
> So it can be done, for dynamic linking.
>

Of course, PIE can be loaded above 4GB. But this is unrelated to my glibc
patch,
which applies to both ET_DYN and ET_EXEC.

The reason for ET_EXEC is that the non zero load address in ET_DYN is a hint
and kernel may ignore it.


> My preference would still be to change the static non-PIE objects not to
> use absolute 32-bit relocations, so that they can be used for this.
> Rather than changing the PIE-specific objects to work with ET_EXEC
> binaries, too.
>
> Thanks,
> Florian
>
>
  

Patch

diff --git a/configure b/configure
index 49b093043c..612fa0cf77 100755
--- a/configure
+++ b/configure
@@ -7820,6 +7820,73 @@  printf "%s\n" "$libc_cv_cc_pie_default" >&6; }
 config_vars="$config_vars
 cc-pie-default = $libc_cv_cc_pie_default"
 
+# Get PDE load address.
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PDE load address" >&5
+printf %s "checking PDE load address... " >&6; }
+if test ${libc_cv_pde_load_address+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  cat > conftest.S <<EOF
+.globl _start
+_start:
+.globl __start
+__start:
+EOF
+if test $libc_cv_cc_pie_default = yes; then
+  pde_ld_flags="-no-pie"
+fi
+if ${CC-cc} $pde_ld_flags $CFLAGS $CPPFLAGS $LDFLAGS \
+	    -nostartfiles -nostdlib $no_ssp \
+	    -o conftest conftest.S 1>&5 2>&5; then
+  # Get the load address of the first PT_LOAD segment.
+  libc_cv_pde_load_address=$(LC_ALL=C $READELF -Wl conftest \
+			     | $AWK '/LOAD/ { print $3; exit 0; }')
+else
+  as_fn_error $? "${CC-cc} can not create PDE" "$LINENO" 5
+fi
+rm -f conftest*
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_cv_pde_load_address" >&5
+printf "%s\n" "$libc_cv_pde_load_address" >&6; }
+config_vars="$config_vars
+pde-load-address = $libc_cv_pde_load_address"
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for linker that supports -Ttext-segment=$libc_cv_pde_load_address" >&5
+printf %s "checking for linker that supports -Ttext-segment=$libc_cv_pde_load_address... " >&6; }
+libc_linker_feature=no
+cat > conftest.c <<EOF
+int _start (void) { return 42; }
+EOF
+if { ac_try='${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS $no_ssp
+		  -Wl,-Ttext-segment=$libc_cv_pde_load_address -nostdlib -nostartfiles
+		  -fPIC -shared -o conftest.so conftest.c
+		  1>&5'
+  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }
+then
+  if ${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS $no_ssp -Wl,-Ttext-segment=$libc_cv_pde_load_address -nostdlib \
+      -nostartfiles -fPIC -shared -o conftest.so conftest.c 2>&1 \
+      | grep "warning: -Ttext-segment=$libc_cv_pde_load_address ignored" > /dev/null 2>&1; then
+    true
+  else
+    libc_linker_feature=yes
+  fi
+fi
+rm -f conftest*
+if test $libc_linker_feature = yes; then
+  libc_cv_load_address=-Wl,-Ttext-segment
+else
+  libc_cv_load_address=
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_linker_feature" >&5
+printf "%s\n" "$libc_linker_feature" >&6; }
+config_vars="$config_vars
+load-address-ldflag = $libc_cv_load_address"
+
 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if we can build programs as PIE" >&5
 printf %s "checking if we can build programs as PIE... " >&6; }
 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
diff --git a/configure.ac b/configure.ac
index e48957f318..89a2f5d842 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1721,6 +1721,36 @@  fi
 rm -f conftest.*])
 LIBC_CONFIG_VAR([cc-pie-default], [$libc_cv_cc_pie_default])
 
+# Get PDE load address.
+AC_CACHE_CHECK([PDE load address],
+	       libc_cv_pde_load_address, [dnl
+cat > conftest.S <<EOF
+.globl _start
+_start:
+.globl __start
+__start:
+EOF
+if test $libc_cv_cc_pie_default = yes; then
+  pde_ld_flags="-no-pie"
+fi
+if ${CC-cc} $pde_ld_flags $CFLAGS $CPPFLAGS $LDFLAGS \
+	    -nostartfiles -nostdlib $no_ssp \
+	    -o conftest conftest.S 1>&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD; then
+  # Get the load address of the first PT_LOAD segment.
+  libc_cv_pde_load_address=$(LC_ALL=C $READELF -Wl conftest \
+			     | $AWK '/LOAD/ { print $3; exit 0; }')
+else
+  AC_MSG_ERROR([${CC-cc} can not create PDE])
+fi
+rm -f conftest*])
+LIBC_CONFIG_VAR([pde-load-address], [$libc_cv_pde_load_address])
+
+LIBC_LINKER_FEATURE([-Ttext-segment=$libc_cv_pde_load_address],
+		    [-Wl,-Ttext-segment=$libc_cv_pde_load_address],
+		    [libc_cv_load_address=-Wl,-Ttext-segment],
+		    [libc_cv_load_address=])
+LIBC_CONFIG_VAR([load-address-ldflag], [$libc_cv_load_address])
+
 AC_MSG_CHECKING(if we can build programs as PIE)
 AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#ifdef PIE_UNSUPPORTED
 # error PIE is not supported
diff --git a/elf/Makefile b/elf/Makefile
index 57b3a19d36..4e2d123b8e 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -1046,6 +1046,25 @@  tests-pie += \
   tst-pie1 \
   tst-pie2 \
   # tests-pie
+ifneq (,$(load-address-ldflag))
+tests += \
+  tst-pie-address \
+  # tests
+tests-pie += \
+  tst-pie-address \
+  # tests-pie
+LDFLAGS-tst-pie-address += $(load-address-ldflag)=$(pde-load-address)
+ifeq (yes,$(enable-static-pie))
+tests += \
+  tst-pie-address-static \
+  # tests
+tests-static += \
+  tst-pie-address-static \
+  # tests-static
+LDFLAGS-tst-pie-address-static += \
+  $(load-address-ldflag)=$(pde-load-address)
+endif
+endif
 ifeq (yes,$(have-protected-data))
 tests += vismain
 tests-pie += vismain
@@ -1896,6 +1915,7 @@  $(objpfx)tst-array5-static-cmp.out: tst-array5-static.exp \
 
 CFLAGS-tst-pie1.c += $(pie-ccflag)
 CFLAGS-tst-pie2.c += $(pie-ccflag)
+CFLAGS-tst-pie-address.c += $(pie-ccflag)
 
 $(objpfx)tst-piemod1.so: $(libsupport)
 $(objpfx)tst-pie1: $(objpfx)tst-piemod1.so
diff --git a/elf/dl-reloc-static-pie.c b/elf/dl-reloc-static-pie.c
index 10c23d0bf0..6286f19f1f 100644
--- a/elf/dl-reloc-static-pie.c
+++ b/elf/dl-reloc-static-pie.c
@@ -37,21 +37,32 @@  _dl_relocate_static_pie (void)
 {
   struct link_map *main_map = _dl_get_dl_main_map ();
 
-  /* Figure out the run-time load address of static PIE.  */
-  main_map->l_addr = elf_machine_load_address ();
-
-  /* Read our own dynamic section and fill in the info array.  */
-  main_map->l_ld = ((void *) main_map->l_addr + elf_machine_dynamic ());
-
+  ElfW(Addr) file_p_vaddr = 0;
   const ElfW(Phdr) *ph, *phdr = GL(dl_phdr);
   size_t phnum = GL(dl_phnum);
   for (ph = phdr; ph < &phdr[phnum]; ++ph)
-    if (ph->p_type == PT_DYNAMIC)
+    switch (ph->p_type)
       {
+      case PT_LOAD:
+	/* Get p_vaddr of the PT_LOAD segement which covers the file
+	   start.  */
+	if (ph->p_offset == 0)
+	  file_p_vaddr = ph->p_vaddr;
+	break;
+      case PT_DYNAMIC:
 	main_map->l_ld_readonly = (ph->p_flags & PF_W) == 0;
 	break;
+      default:
+	break;
       }
 
+  /* Figure out the run-time load address of static PIE.  */
+  ElfW(Addr) l_addr = elf_machine_load_address ();
+  main_map->l_addr = l_addr - file_p_vaddr;
+
+  /* Read our own dynamic section and fill in the info array.  */
+  main_map->l_ld = ((void *) l_addr + elf_machine_dynamic ());
+
   elf_get_dynamic_info (main_map, false, true);
 
 # ifdef ELF_MACHINE_BEFORE_RTLD_RELOC
diff --git a/elf/tst-pie-address-static.c b/elf/tst-pie-address-static.c
new file mode 100644
index 0000000000..25d27fb962
--- /dev/null
+++ b/elf/tst-pie-address-static.c
@@ -0,0 +1,19 @@ 
+/* Test static PIE loaded at the specific address.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include "tst-pie-address.c"
diff --git a/elf/tst-pie-address.c b/elf/tst-pie-address.c
new file mode 100644
index 0000000000..1f01783631
--- /dev/null
+++ b/elf/tst-pie-address.c
@@ -0,0 +1,28 @@ 
+/* Test PIE loaded at the specific address.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+
+static int
+do_test (void)
+{
+  printf ("Hello\n");
+  return 0;
+}
+
+#include <support/test-driver.c>