diff mbox series

[v4,3/5] Add GLIBC_ABI_DT_RELR for DT_RELR support

Message ID 20220301161706.185216-4-hjl.tools@gmail.com
State New
Headers show
Series Support DT_RELR relative relocation format | expand

Commit Message

H.J. Lu March 1, 2022, 4:17 p.m. UTC
The EI_ABIVERSION field of the ELF header in executables and shared
libraries can be bumped to indicate the minimum ABI requirement on the
dynamic linker.  However, EI_ABIVERSION in executables isn't checked by
the Linux kernel ELF loader nor the existing dynamic linker.  Executables
will crash mysteriously if the dynamic linker doesn't support the ABI
features required by the EI_ABIVERSION field.  The dynamic linker should
be changed to check EI_ABIVERSION in executables.

Add a glibc version, GLIBC_ABI_DT_RELR, to indicate DT_RELR support so
that the existing dynamic linkers will issue an error on executables with
GLIBC_ABI_DT_RELR dependency.  Issue an error if there is a DT_RELR entry
without GLIBC_ABI_DT_RELR dependency nor GLIBC_PRIVATE definition.

Support __placeholder_only_for_empty_version_map as the placeholder symbol
used only for empty version map to generate GLIBC_ABI_DT_RELR without any
symbols.
---
 elf/Makefile             | 16 ++++++++++++++--
 elf/Versions             |  5 +++++
 elf/dl-version.c         | 33 +++++++++++++++++++++++++++++++--
 elf/libc-abi-version.exp |  1 +
 include/link.h           |  6 ++++++
 scripts/abilist.awk      |  2 ++
 scripts/versions.awk     |  7 ++++++-
 7 files changed, 65 insertions(+), 5 deletions(-)
 create mode 100644 elf/libc-abi-version.exp

Comments

Fangrui Song March 1, 2022, 7:31 p.m. UTC | #1
On 2022-03-01, H.J. Lu wrote:
>The EI_ABIVERSION field of the ELF header in executables and shared
>libraries can be bumped to indicate the minimum ABI requirement on the
>dynamic linker.  However, EI_ABIVERSION in executables isn't checked by
>the Linux kernel ELF loader nor the existing dynamic linker.  Executables
>will crash mysteriously if the dynamic linker doesn't support the ABI
>features required by the EI_ABIVERSION field.  The dynamic linker should
>be changed to check EI_ABIVERSION in executables.
>
>Add a glibc version, GLIBC_ABI_DT_RELR, to indicate DT_RELR support so
>that the existing dynamic linkers will issue an error on executables with
>GLIBC_ABI_DT_RELR dependency.  Issue an error if there is a DT_RELR entry
>without GLIBC_ABI_DT_RELR dependency nor GLIBC_PRIVATE definition.
>
>Support __placeholder_only_for_empty_version_map as the placeholder symbol
>used only for empty version map to generate GLIBC_ABI_DT_RELR without any
>symbols.
>---
> elf/Makefile             | 16 ++++++++++++++--
> elf/Versions             |  5 +++++
> elf/dl-version.c         | 33 +++++++++++++++++++++++++++++++--
> elf/libc-abi-version.exp |  1 +
> include/link.h           |  6 ++++++
> scripts/abilist.awk      |  2 ++
> scripts/versions.awk     |  7 ++++++-
> 7 files changed, 65 insertions(+), 5 deletions(-)
> create mode 100644 elf/libc-abi-version.exp
>
>diff --git a/elf/Makefile b/elf/Makefile
>index fd462ba315..f533d377fd 100644
>--- a/elf/Makefile
>+++ b/elf/Makefile
>@@ -1113,8 +1113,10 @@ $(eval $(call include_dsosort_tests,dso-sort-tests-1.def))
> $(eval $(call include_dsosort_tests,dso-sort-tests-2.def))
> endif
>
>-check-abi: $(objpfx)check-abi-ld.out
>-tests-special += $(objpfx)check-abi-ld.out
>+check-abi: $(objpfx)check-abi-ld.out \
>+	   $(objpfx)check-abi-version-libc.out
>+tests-special += $(objpfx)check-abi-ld.out \
>+	   $(objpfx)check-abi-version-libc.out
> update-abi: update-abi-ld
> update-all-abi: update-all-abi-ld
>
>@@ -2739,3 +2741,13 @@ $(objpfx)check-tst-relr-pie.out: $(objpfx)tst-relr-pie
> 		| sed -ne '/required from libc.so/,$$ p' \
> 		| grep GLIBC_ABI_DT_RELR > $@; \
> 	$(evaluate-test)
>+
>+$(objpfx)check-abi-version-libc.out: libc-abi-version.exp \
>+  $(objpfx)libc.symlist-abi-version
>+	cmp $^ > $@; \
>+	$(evaluate-test)
>+
>+$(objpfx)libc.symlist-abi-version: $(common-objpfx)libc.so
>+	LC_ALL=C $(OBJDUMP) --dynamic-syms $< | grep " GLIBC_ABI_" \
>+		| sed "s/0\+/00000000/g;s/[ \t]\+/ /g" > $@T
>+	mv -f $@T $@

As just mentioned on https://sourceware.org/pipermail/libc-alpha/2022-March/136764.html ,
perhaps use something like $(READELF) -V $< | grep GLIBC_ABI_DT_RELR


% nm -D a/glibc
                  U stat@GLIBC_2.33
% objdump --dynamic-syms a/glibc

a/glibc:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
0000000000000000      D  *UND*  0000000000000000 (GLIBC_2.33) stat


% readelf -V a/glibc

Version symbols section '.gnu.version' contains 2 entries:
  Addr: 0x0000000000000230  Offset: 0x000230  Link: 1 (.dynsym)
   000:   0 (*local*)       2 (GLIBC_2.33)

Version needs section '.gnu.version_r' contains 1 entry:
  Addr: 0x0000000000000234  Offset: 0x000234  Link: 6 (.dynstr)
   000000: Version: 1  File: libc.so.6  Cnt: 2
   0x0010:   Name: GLIBC_2.33  Flags: none  Version: 2
   0x0020:   Name: GLIBC_ABI_DT_RELR  Flags: none  Version: 3


Is there a static pie test? A static pie has DT_RELR but does not have verneed.

>diff --git a/elf/Versions b/elf/Versions
>index 8bed855d8c..a9ff278de7 100644
>--- a/elf/Versions
>+++ b/elf/Versions
>@@ -23,6 +23,11 @@ libc {
>   GLIBC_2.35 {
>     _dl_find_object;
>   }
>+  GLIBC_ABI_DT_RELR {
>+    # This symbol is used only for empty version map and will be removed
>+    # by scripts/versions.awk.
>+    __placeholder_only_for_empty_version_map;
>+  }
>   GLIBC_PRIVATE {
>     # functions used in other libraries
>     __libc_early_init;
>diff --git a/elf/dl-version.c b/elf/dl-version.c
>index b47bd91727..720ec596a5 100644
>--- a/elf/dl-version.c
>+++ b/elf/dl-version.c
>@@ -214,12 +214,20 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
> 	      while (1)
> 		{
> 		  /* Match the symbol.  */
>+		  const char *string = strtab + aux->vna_name;
> 		  result |= match_symbol (DSO_FILENAME (map->l_name),
> 					  map->l_ns, aux->vna_hash,
>-					  strtab + aux->vna_name,
>-					  needed->l_real, verbose,
>+					  string, needed->l_real, verbose,
> 					  aux->vna_flags & VER_FLG_WEAK);
>
>+		  if (map->l_abi_version == lav_none
>+		      /* 0xfd0e42: _dl_elf_hash ("GLIBC_ABI_DT_RELR").  */
>+		      && aux->vna_hash == 0xfd0e42
>+		      && __glibc_likely (strcmp (string,
>+						 "GLIBC_ABI_DT_RELR")
>+					 == 0))
>+		    map->l_abi_version = lav_dt_relr_ref;
>+
> 		  /* Compare the version index.  */
> 		  if ((unsigned int) (aux->vna_other & 0x7fff) > ndx_high)
> 		    ndx_high = aux->vna_other & 0x7fff;
>@@ -253,6 +261,16 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
>       ent = (ElfW(Verdef) *) (map->l_addr + def->d_un.d_ptr);
>       while (1)
> 	{
>+	  /* 0x0963cf85: _dl_elf_hash ("GLIBC_PRIVATE").  */
>+	  if (ent->vd_hash == 0x0963cf85)
>+	    {
>+	      ElfW(Verdaux) *aux = (ElfW(Verdaux) *) ((char *) ent
>+						      + ent->vd_aux);
>+	      if (__glibc_likely (strcmp ("GLIBC_PRIVATE",
>+					  strtab + aux->vda_name) == 0))
>+		map->l_abi_version = lav_private_def;
>+	    }
>+
> 	  if ((unsigned int) (ent->vd_ndx & 0x7fff) > ndx_high)
> 	    ndx_high = ent->vd_ndx & 0x7fff;
>
>@@ -352,6 +370,17 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
> 	}
>     }
>
>+  /* Issue an error if there is a DT_RELR entry without GLIBC_ABI_DT_RELR
>+     dependency nor GLIBC_PRIVATE definition.  */
>+  if (map->l_info[DT_RELR] != NULL
>+      && __glibc_unlikely (map->l_abi_version == lav_none))
>+    {
>+      _dl_exception_create
>+	(&exception, DSO_FILENAME (map->l_name),
>+	 N_("DT_RELR without GLIBC_ABI_DT_RELR dependency"));
>+      goto call_error;
>+    }
>+
>   return result;
> }
>
>diff --git a/elf/libc-abi-version.exp b/elf/libc-abi-version.exp
>new file mode 100644
>index 0000000000..ff8506b3ba
>--- /dev/null
>+++ b/elf/libc-abi-version.exp
>@@ -0,0 +1 @@
>+00000000 g DO *ABS* 00000000 GLIBC_ABI_DT_RELR GLIBC_ABI_DT_RELR
>diff --git a/include/link.h b/include/link.h
>index 03db14c7b0..8ec5e35cf2 100644
>--- a/include/link.h
>+++ b/include/link.h
>@@ -177,6 +177,12 @@ struct link_map
> 	lt_library,		/* Library needed by main executable.  */
> 	lt_loaded		/* Extra run-time loaded shared object.  */
>       } l_type:2;
>+    enum			/* ABI dependency of this object.  */
>+      {
>+	lav_none,		/* No ABI dependency.  */
>+	lav_dt_relr_ref,	/* Need GLIBC_ABI_DT_RELR.  */
>+	lav_private_def		/* Define GLIBC_PRIVATE.  */
>+      } l_abi_version:2;
>     unsigned int l_relocated:1;	/* Nonzero if object's relocations done.  */
>     unsigned int l_init_called:1; /* Nonzero if DT_INIT function called.  */
>     unsigned int l_global:1;	/* Nonzero if object in _dl_global_scope.  */
>diff --git a/scripts/abilist.awk b/scripts/abilist.awk
>index 24a34ccbed..6cc7af6ac8 100644
>--- a/scripts/abilist.awk
>+++ b/scripts/abilist.awk
>@@ -55,6 +55,8 @@ $2 == "g" || $2 == "w" && (NF == 7 || NF == 8) {
>   # caused STV_HIDDEN symbols to appear in .dynsym, though that is useless.
>   if (NF > 7 && $7 == ".hidden") next;
>
>+  if (version ~ /^GLIBC_ABI_/ && !include_abi_version) next;
>+
>   if (version == "GLIBC_PRIVATE" && !include_private) next;
>
>   desc = "";
>diff --git a/scripts/versions.awk b/scripts/versions.awk
>index 357ad1355e..d70b07bd1a 100644
>--- a/scripts/versions.awk
>+++ b/scripts/versions.awk
>@@ -185,8 +185,13 @@ END {
> 	closeversion(oldver, veryoldver);
> 	veryoldver = oldver;
>       }
>-      printf("%s {\n  global:\n", $2) > outfile;
>       oldver = $2;
>+      # Skip the placeholder symbol used only for empty version map.
>+      if ($3 == "__placeholder_only_for_empty_version_map;") {
>+	printf("%s {\n", $2) > outfile;
>+	continue;
>+      }
>+      printf("%s {\n  global:\n", $2) > outfile;
>     }
>     printf("   ") > outfile;
>     for (n = 3; n <= NF; ++n) {
>-- 
>2.35.1
>
H.J. Lu March 1, 2022, 10:33 p.m. UTC | #2
On Tue, Mar 1, 2022 at 11:31 AM Fangrui Song <maskray@google.com> wrote:
>
> On 2022-03-01, H.J. Lu wrote:
> >The EI_ABIVERSION field of the ELF header in executables and shared
> >libraries can be bumped to indicate the minimum ABI requirement on the
> >dynamic linker.  However, EI_ABIVERSION in executables isn't checked by
> >the Linux kernel ELF loader nor the existing dynamic linker.  Executables
> >will crash mysteriously if the dynamic linker doesn't support the ABI
> >features required by the EI_ABIVERSION field.  The dynamic linker should
> >be changed to check EI_ABIVERSION in executables.
> >
> >Add a glibc version, GLIBC_ABI_DT_RELR, to indicate DT_RELR support so
> >that the existing dynamic linkers will issue an error on executables with
> >GLIBC_ABI_DT_RELR dependency.  Issue an error if there is a DT_RELR entry
> >without GLIBC_ABI_DT_RELR dependency nor GLIBC_PRIVATE definition.
> >
> >Support __placeholder_only_for_empty_version_map as the placeholder symbol
> >used only for empty version map to generate GLIBC_ABI_DT_RELR without any
> >symbols.
> >---
> > elf/Makefile             | 16 ++++++++++++++--
> > elf/Versions             |  5 +++++
> > elf/dl-version.c         | 33 +++++++++++++++++++++++++++++++--
> > elf/libc-abi-version.exp |  1 +
> > include/link.h           |  6 ++++++
> > scripts/abilist.awk      |  2 ++
> > scripts/versions.awk     |  7 ++++++-
> > 7 files changed, 65 insertions(+), 5 deletions(-)
> > create mode 100644 elf/libc-abi-version.exp
> >
> >diff --git a/elf/Makefile b/elf/Makefile
> >index fd462ba315..f533d377fd 100644
> >--- a/elf/Makefile
> >+++ b/elf/Makefile
> >@@ -1113,8 +1113,10 @@ $(eval $(call include_dsosort_tests,dso-sort-tests-1.def))
> > $(eval $(call include_dsosort_tests,dso-sort-tests-2.def))
> > endif
> >
> >-check-abi: $(objpfx)check-abi-ld.out
> >-tests-special += $(objpfx)check-abi-ld.out
> >+check-abi: $(objpfx)check-abi-ld.out \
> >+         $(objpfx)check-abi-version-libc.out
> >+tests-special += $(objpfx)check-abi-ld.out \
> >+         $(objpfx)check-abi-version-libc.out
> > update-abi: update-abi-ld
> > update-all-abi: update-all-abi-ld
> >
> >@@ -2739,3 +2741,13 @@ $(objpfx)check-tst-relr-pie.out: $(objpfx)tst-relr-pie
> >               | sed -ne '/required from libc.so/,$$ p' \
> >               | grep GLIBC_ABI_DT_RELR > $@; \
> >       $(evaluate-test)
> >+
> >+$(objpfx)check-abi-version-libc.out: libc-abi-version.exp \
> >+  $(objpfx)libc.symlist-abi-version
> >+      cmp $^ > $@; \
> >+      $(evaluate-test)
> >+
> >+$(objpfx)libc.symlist-abi-version: $(common-objpfx)libc.so
> >+      LC_ALL=C $(OBJDUMP) --dynamic-syms $< | grep " GLIBC_ABI_" \
> >+              | sed "s/0\+/00000000/g;s/[ \t]\+/ /g" > $@T
> >+      mv -f $@T $@
>
> As just mentioned on https://sourceware.org/pipermail/libc-alpha/2022-March/136764.html ,
> perhaps use something like $(READELF) -V $< | grep GLIBC_ABI_DT_RELR
>
>
> % nm -D a/glibc
>                   U stat@GLIBC_2.33
> % objdump --dynamic-syms a/glibc
>
> a/glibc:     file format elf64-x86-64
>
> DYNAMIC SYMBOL TABLE:
> 0000000000000000      D  *UND*  0000000000000000 (GLIBC_2.33) stat

It is easier to run cmp on "objdump --dynamic-syms" outputs on
different targets.

>
> % readelf -V a/glibc
>
> Version symbols section '.gnu.version' contains 2 entries:
>   Addr: 0x0000000000000230  Offset: 0x000230  Link: 1 (.dynsym)
>    000:   0 (*local*)       2 (GLIBC_2.33)
>
> Version needs section '.gnu.version_r' contains 1 entry:
>   Addr: 0x0000000000000234  Offset: 0x000234  Link: 6 (.dynstr)
>    000000: Version: 1  File: libc.so.6  Cnt: 2
>    0x0010:   Name: GLIBC_2.33  Flags: none  Version: 2
>    0x0020:   Name: GLIBC_ABI_DT_RELR  Flags: none  Version: 3
>
>
> Is there a static pie test? A static pie has DT_RELR but does not have verneed.

If linker supports DT_RELR, static PIE programs and tests will have
DT_RELR.  They will be tested.

> >diff --git a/elf/Versions b/elf/Versions
> >index 8bed855d8c..a9ff278de7 100644
> >--- a/elf/Versions
> >+++ b/elf/Versions
> >@@ -23,6 +23,11 @@ libc {
> >   GLIBC_2.35 {
> >     _dl_find_object;
> >   }
> >+  GLIBC_ABI_DT_RELR {
> >+    # This symbol is used only for empty version map and will be removed
> >+    # by scripts/versions.awk.
> >+    __placeholder_only_for_empty_version_map;
> >+  }
> >   GLIBC_PRIVATE {
> >     # functions used in other libraries
> >     __libc_early_init;
> >diff --git a/elf/dl-version.c b/elf/dl-version.c
> >index b47bd91727..720ec596a5 100644
> >--- a/elf/dl-version.c
> >+++ b/elf/dl-version.c
> >@@ -214,12 +214,20 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
> >             while (1)
> >               {
> >                 /* Match the symbol.  */
> >+                const char *string = strtab + aux->vna_name;
> >                 result |= match_symbol (DSO_FILENAME (map->l_name),
> >                                         map->l_ns, aux->vna_hash,
> >-                                        strtab + aux->vna_name,
> >-                                        needed->l_real, verbose,
> >+                                        string, needed->l_real, verbose,
> >                                         aux->vna_flags & VER_FLG_WEAK);
> >
> >+                if (map->l_abi_version == lav_none
> >+                    /* 0xfd0e42: _dl_elf_hash ("GLIBC_ABI_DT_RELR").  */
> >+                    && aux->vna_hash == 0xfd0e42
> >+                    && __glibc_likely (strcmp (string,
> >+                                               "GLIBC_ABI_DT_RELR")
> >+                                       == 0))
> >+                  map->l_abi_version = lav_dt_relr_ref;
> >+
> >                 /* Compare the version index.  */
> >                 if ((unsigned int) (aux->vna_other & 0x7fff) > ndx_high)
> >                   ndx_high = aux->vna_other & 0x7fff;
> >@@ -253,6 +261,16 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
> >       ent = (ElfW(Verdef) *) (map->l_addr + def->d_un.d_ptr);
> >       while (1)
> >       {
> >+        /* 0x0963cf85: _dl_elf_hash ("GLIBC_PRIVATE").  */
> >+        if (ent->vd_hash == 0x0963cf85)
> >+          {
> >+            ElfW(Verdaux) *aux = (ElfW(Verdaux) *) ((char *) ent
> >+                                                    + ent->vd_aux);
> >+            if (__glibc_likely (strcmp ("GLIBC_PRIVATE",
> >+                                        strtab + aux->vda_name) == 0))
> >+              map->l_abi_version = lav_private_def;
> >+          }
> >+
> >         if ((unsigned int) (ent->vd_ndx & 0x7fff) > ndx_high)
> >           ndx_high = ent->vd_ndx & 0x7fff;
> >
> >@@ -352,6 +370,17 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
> >       }
> >     }
> >
> >+  /* Issue an error if there is a DT_RELR entry without GLIBC_ABI_DT_RELR
> >+     dependency nor GLIBC_PRIVATE definition.  */
> >+  if (map->l_info[DT_RELR] != NULL
> >+      && __glibc_unlikely (map->l_abi_version == lav_none))
> >+    {
> >+      _dl_exception_create
> >+      (&exception, DSO_FILENAME (map->l_name),
> >+       N_("DT_RELR without GLIBC_ABI_DT_RELR dependency"));
> >+      goto call_error;
> >+    }
> >+
> >   return result;
> > }
> >
> >diff --git a/elf/libc-abi-version.exp b/elf/libc-abi-version.exp
> >new file mode 100644
> >index 0000000000..ff8506b3ba
> >--- /dev/null
> >+++ b/elf/libc-abi-version.exp
> >@@ -0,0 +1 @@
> >+00000000 g DO *ABS* 00000000 GLIBC_ABI_DT_RELR GLIBC_ABI_DT_RELR
> >diff --git a/include/link.h b/include/link.h
> >index 03db14c7b0..8ec5e35cf2 100644
> >--- a/include/link.h
> >+++ b/include/link.h
> >@@ -177,6 +177,12 @@ struct link_map
> >       lt_library,             /* Library needed by main executable.  */
> >       lt_loaded               /* Extra run-time loaded shared object.  */
> >       } l_type:2;
> >+    enum                      /* ABI dependency of this object.  */
> >+      {
> >+      lav_none,               /* No ABI dependency.  */
> >+      lav_dt_relr_ref,        /* Need GLIBC_ABI_DT_RELR.  */
> >+      lav_private_def         /* Define GLIBC_PRIVATE.  */
> >+      } l_abi_version:2;
> >     unsigned int l_relocated:1;       /* Nonzero if object's relocations done.  */
> >     unsigned int l_init_called:1; /* Nonzero if DT_INIT function called.  */
> >     unsigned int l_global:1;  /* Nonzero if object in _dl_global_scope.  */
> >diff --git a/scripts/abilist.awk b/scripts/abilist.awk
> >index 24a34ccbed..6cc7af6ac8 100644
> >--- a/scripts/abilist.awk
> >+++ b/scripts/abilist.awk
> >@@ -55,6 +55,8 @@ $2 == "g" || $2 == "w" && (NF == 7 || NF == 8) {
> >   # caused STV_HIDDEN symbols to appear in .dynsym, though that is useless.
> >   if (NF > 7 && $7 == ".hidden") next;
> >
> >+  if (version ~ /^GLIBC_ABI_/ && !include_abi_version) next;
> >+
> >   if (version == "GLIBC_PRIVATE" && !include_private) next;
> >
> >   desc = "";
> >diff --git a/scripts/versions.awk b/scripts/versions.awk
> >index 357ad1355e..d70b07bd1a 100644
> >--- a/scripts/versions.awk
> >+++ b/scripts/versions.awk
> >@@ -185,8 +185,13 @@ END {
> >       closeversion(oldver, veryoldver);
> >       veryoldver = oldver;
> >       }
> >-      printf("%s {\n  global:\n", $2) > outfile;
> >       oldver = $2;
> >+      # Skip the placeholder symbol used only for empty version map.
> >+      if ($3 == "__placeholder_only_for_empty_version_map;") {
> >+      printf("%s {\n", $2) > outfile;
> >+      continue;
> >+      }
> >+      printf("%s {\n  global:\n", $2) > outfile;
> >     }
> >     printf("   ") > outfile;
> >     for (n = 3; n <= NF; ++n) {
> >--
> >2.35.1
> >
Fangrui Song March 1, 2022, 10:46 p.m. UTC | #3
On 2022-03-01, H.J. Lu wrote:
>On Tue, Mar 1, 2022 at 11:31 AM Fangrui Song <maskray@google.com> wrote:
>>
>> On 2022-03-01, H.J. Lu wrote:
>> >The EI_ABIVERSION field of the ELF header in executables and shared
>> >libraries can be bumped to indicate the minimum ABI requirement on the
>> >dynamic linker.  However, EI_ABIVERSION in executables isn't checked by
>> >the Linux kernel ELF loader nor the existing dynamic linker.  Executables
>> >will crash mysteriously if the dynamic linker doesn't support the ABI
>> >features required by the EI_ABIVERSION field.  The dynamic linker should
>> >be changed to check EI_ABIVERSION in executables.
>> >
>> >Add a glibc version, GLIBC_ABI_DT_RELR, to indicate DT_RELR support so
>> >that the existing dynamic linkers will issue an error on executables with
>> >GLIBC_ABI_DT_RELR dependency.  Issue an error if there is a DT_RELR entry
>> >without GLIBC_ABI_DT_RELR dependency nor GLIBC_PRIVATE definition.
>> >
>> >Support __placeholder_only_for_empty_version_map as the placeholder symbol
>> >used only for empty version map to generate GLIBC_ABI_DT_RELR without any
>> >symbols.
>> >---
>> > elf/Makefile             | 16 ++++++++++++++--
>> > elf/Versions             |  5 +++++
>> > elf/dl-version.c         | 33 +++++++++++++++++++++++++++++++--
>> > elf/libc-abi-version.exp |  1 +
>> > include/link.h           |  6 ++++++
>> > scripts/abilist.awk      |  2 ++
>> > scripts/versions.awk     |  7 ++++++-
>> > 7 files changed, 65 insertions(+), 5 deletions(-)
>> > create mode 100644 elf/libc-abi-version.exp
>> >
>> >diff --git a/elf/Makefile b/elf/Makefile
>> >index fd462ba315..f533d377fd 100644
>> >--- a/elf/Makefile
>> >+++ b/elf/Makefile
>> >@@ -1113,8 +1113,10 @@ $(eval $(call include_dsosort_tests,dso-sort-tests-1.def))
>> > $(eval $(call include_dsosort_tests,dso-sort-tests-2.def))
>> > endif
>> >
>> >-check-abi: $(objpfx)check-abi-ld.out
>> >-tests-special += $(objpfx)check-abi-ld.out
>> >+check-abi: $(objpfx)check-abi-ld.out \
>> >+         $(objpfx)check-abi-version-libc.out
>> >+tests-special += $(objpfx)check-abi-ld.out \
>> >+         $(objpfx)check-abi-version-libc.out
>> > update-abi: update-abi-ld
>> > update-all-abi: update-all-abi-ld
>> >
>> >@@ -2739,3 +2741,13 @@ $(objpfx)check-tst-relr-pie.out: $(objpfx)tst-relr-pie
>> >               | sed -ne '/required from libc.so/,$$ p' \
>> >               | grep GLIBC_ABI_DT_RELR > $@; \
>> >       $(evaluate-test)
>> >+
>> >+$(objpfx)check-abi-version-libc.out: libc-abi-version.exp \
>> >+  $(objpfx)libc.symlist-abi-version
>> >+      cmp $^ > $@; \
>> >+      $(evaluate-test)
>> >+
>> >+$(objpfx)libc.symlist-abi-version: $(common-objpfx)libc.so
>> >+      LC_ALL=C $(OBJDUMP) --dynamic-syms $< | grep " GLIBC_ABI_" \
>> >+              | sed "s/0\+/00000000/g;s/[ \t]\+/ /g" > $@T
>> >+      mv -f $@T $@
>>
>> As just mentioned on https://sourceware.org/pipermail/libc-alpha/2022-March/136764.html ,
>> perhaps use something like $(READELF) -V $< | grep GLIBC_ABI_DT_RELR
>>
>>
>> % nm -D a/glibc
>>                   U stat@GLIBC_2.33
>> % objdump --dynamic-syms a/glibc
>>
>> a/glibc:     file format elf64-x86-64
>>
>> DYNAMIC SYMBOL TABLE:
>> 0000000000000000      D  *UND*  0000000000000000 (GLIBC_2.33) stat
>
>It is easier to run cmp on "objdump --dynamic-syms" outputs on
>different targets.

How about

$(READELF) -V $< | grep -o GLIBC_ABI_DT_RELR > $@T

It can be compared with a text file containing "GLIBC_ABI_DT_RELR"

This approach does not require the SHN_ABS symbol.

>>
>> % readelf -V a/glibc
>>
>> Version symbols section '.gnu.version' contains 2 entries:
>>   Addr: 0x0000000000000230  Offset: 0x000230  Link: 1 (.dynsym)
>>    000:   0 (*local*)       2 (GLIBC_2.33)
>>
>> Version needs section '.gnu.version_r' contains 1 entry:
>>   Addr: 0x0000000000000234  Offset: 0x000234  Link: 6 (.dynstr)
>>    000000: Version: 1  File: libc.so.6  Cnt: 2
>>    0x0010:   Name: GLIBC_2.33  Flags: none  Version: 2
>>    0x0020:   Name: GLIBC_ABI_DT_RELR  Flags: none  Version: 3
>>
>>
>> Is there a static pie test? A static pie has DT_RELR but does not have verneed.
>
>If linker supports DT_RELR, static PIE programs and tests will have
>DT_RELR.  They will be tested.

OK. I just wanted to mention that a static PIE does not have the
GLIBC_ABI_DT_RELR version dependency.

Therefore, a static PIE built with older glibc links but will segfault at run-time.
Perhaps this still serves as the purpose: users will not port it to an
older glibc system which will fail as well.

>> >diff --git a/elf/Versions b/elf/Versions
>> >index 8bed855d8c..a9ff278de7 100644
>> >--- a/elf/Versions
>> >+++ b/elf/Versions
>> >@@ -23,6 +23,11 @@ libc {
>> >   GLIBC_2.35 {
>> >     _dl_find_object;
>> >   }
>> >+  GLIBC_ABI_DT_RELR {
>> >+    # This symbol is used only for empty version map and will be removed
>> >+    # by scripts/versions.awk.
>> >+    __placeholder_only_for_empty_version_map;
>> >+  }
>> >   GLIBC_PRIVATE {
>> >     # functions used in other libraries
>> >     __libc_early_init;
>> >diff --git a/elf/dl-version.c b/elf/dl-version.c
>> >index b47bd91727..720ec596a5 100644
>> >--- a/elf/dl-version.c
>> >+++ b/elf/dl-version.c
>> >@@ -214,12 +214,20 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
>> >             while (1)
>> >               {
>> >                 /* Match the symbol.  */
>> >+                const char *string = strtab + aux->vna_name;
>> >                 result |= match_symbol (DSO_FILENAME (map->l_name),
>> >                                         map->l_ns, aux->vna_hash,
>> >-                                        strtab + aux->vna_name,
>> >-                                        needed->l_real, verbose,
>> >+                                        string, needed->l_real, verbose,
>> >                                         aux->vna_flags & VER_FLG_WEAK);
>> >
>> >+                if (map->l_abi_version == lav_none
>> >+                    /* 0xfd0e42: _dl_elf_hash ("GLIBC_ABI_DT_RELR").  */
>> >+                    && aux->vna_hash == 0xfd0e42
>> >+                    && __glibc_likely (strcmp (string,
>> >+                                               "GLIBC_ABI_DT_RELR")
>> >+                                       == 0))
>> >+                  map->l_abi_version = lav_dt_relr_ref;
>> >+
>> >                 /* Compare the version index.  */
>> >                 if ((unsigned int) (aux->vna_other & 0x7fff) > ndx_high)
>> >                   ndx_high = aux->vna_other & 0x7fff;
>> >@@ -253,6 +261,16 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
>> >       ent = (ElfW(Verdef) *) (map->l_addr + def->d_un.d_ptr);
>> >       while (1)
>> >       {
>> >+        /* 0x0963cf85: _dl_elf_hash ("GLIBC_PRIVATE").  */
>> >+        if (ent->vd_hash == 0x0963cf85)
>> >+          {
>> >+            ElfW(Verdaux) *aux = (ElfW(Verdaux) *) ((char *) ent
>> >+                                                    + ent->vd_aux);
>> >+            if (__glibc_likely (strcmp ("GLIBC_PRIVATE",
>> >+                                        strtab + aux->vda_name) == 0))
>> >+              map->l_abi_version = lav_private_def;
>> >+          }
>> >+
>> >         if ((unsigned int) (ent->vd_ndx & 0x7fff) > ndx_high)
>> >           ndx_high = ent->vd_ndx & 0x7fff;
>> >
>> >@@ -352,6 +370,17 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
>> >       }
>> >     }
>> >
>> >+  /* Issue an error if there is a DT_RELR entry without GLIBC_ABI_DT_RELR
>> >+     dependency nor GLIBC_PRIVATE definition.  */
>> >+  if (map->l_info[DT_RELR] != NULL
>> >+      && __glibc_unlikely (map->l_abi_version == lav_none))
>> >+    {
>> >+      _dl_exception_create
>> >+      (&exception, DSO_FILENAME (map->l_name),
>> >+       N_("DT_RELR without GLIBC_ABI_DT_RELR dependency"));
>> >+      goto call_error;
>> >+    }
>> >+
>> >   return result;
>> > }
>> >
>> >diff --git a/elf/libc-abi-version.exp b/elf/libc-abi-version.exp
>> >new file mode 100644
>> >index 0000000000..ff8506b3ba
>> >--- /dev/null
>> >+++ b/elf/libc-abi-version.exp
>> >@@ -0,0 +1 @@
>> >+00000000 g DO *ABS* 00000000 GLIBC_ABI_DT_RELR GLIBC_ABI_DT_RELR
>> >diff --git a/include/link.h b/include/link.h
>> >index 03db14c7b0..8ec5e35cf2 100644
>> >--- a/include/link.h
>> >+++ b/include/link.h
>> >@@ -177,6 +177,12 @@ struct link_map
>> >       lt_library,             /* Library needed by main executable.  */
>> >       lt_loaded               /* Extra run-time loaded shared object.  */
>> >       } l_type:2;
>> >+    enum                      /* ABI dependency of this object.  */
>> >+      {
>> >+      lav_none,               /* No ABI dependency.  */
>> >+      lav_dt_relr_ref,        /* Need GLIBC_ABI_DT_RELR.  */
>> >+      lav_private_def         /* Define GLIBC_PRIVATE.  */
>> >+      } l_abi_version:2;
>> >     unsigned int l_relocated:1;       /* Nonzero if object's relocations done.  */
>> >     unsigned int l_init_called:1; /* Nonzero if DT_INIT function called.  */
>> >     unsigned int l_global:1;  /* Nonzero if object in _dl_global_scope.  */
>> >diff --git a/scripts/abilist.awk b/scripts/abilist.awk
>> >index 24a34ccbed..6cc7af6ac8 100644
>> >--- a/scripts/abilist.awk
>> >+++ b/scripts/abilist.awk
>> >@@ -55,6 +55,8 @@ $2 == "g" || $2 == "w" && (NF == 7 || NF == 8) {
>> >   # caused STV_HIDDEN symbols to appear in .dynsym, though that is useless.
>> >   if (NF > 7 && $7 == ".hidden") next;
>> >
>> >+  if (version ~ /^GLIBC_ABI_/ && !include_abi_version) next;
>> >+
>> >   if (version == "GLIBC_PRIVATE" && !include_private) next;
>> >
>> >   desc = "";
>> >diff --git a/scripts/versions.awk b/scripts/versions.awk
>> >index 357ad1355e..d70b07bd1a 100644
>> >--- a/scripts/versions.awk
>> >+++ b/scripts/versions.awk
>> >@@ -185,8 +185,13 @@ END {
>> >       closeversion(oldver, veryoldver);
>> >       veryoldver = oldver;
>> >       }
>> >-      printf("%s {\n  global:\n", $2) > outfile;
>> >       oldver = $2;
>> >+      # Skip the placeholder symbol used only for empty version map.
>> >+      if ($3 == "__placeholder_only_for_empty_version_map;") {
>> >+      printf("%s {\n", $2) > outfile;
>> >+      continue;
>> >+      }
>> >+      printf("%s {\n  global:\n", $2) > outfile;
>> >     }
>> >     printf("   ") > outfile;
>> >     for (n = 3; n <= NF; ++n) {
>> >--
>> >2.35.1
>> >
>
>
>
>-- 
>H.J.
H.J. Lu March 1, 2022, 10:56 p.m. UTC | #4
On Tue, Mar 1, 2022 at 2:46 PM Fangrui Song <maskray@google.com> wrote:
>
> On 2022-03-01, H.J. Lu wrote:
> >On Tue, Mar 1, 2022 at 11:31 AM Fangrui Song <maskray@google.com> wrote:
> >>
> >> On 2022-03-01, H.J. Lu wrote:
> >> >The EI_ABIVERSION field of the ELF header in executables and shared
> >> >libraries can be bumped to indicate the minimum ABI requirement on the
> >> >dynamic linker.  However, EI_ABIVERSION in executables isn't checked by
> >> >the Linux kernel ELF loader nor the existing dynamic linker.  Executables
> >> >will crash mysteriously if the dynamic linker doesn't support the ABI
> >> >features required by the EI_ABIVERSION field.  The dynamic linker should
> >> >be changed to check EI_ABIVERSION in executables.
> >> >
> >> >Add a glibc version, GLIBC_ABI_DT_RELR, to indicate DT_RELR support so
> >> >that the existing dynamic linkers will issue an error on executables with
> >> >GLIBC_ABI_DT_RELR dependency.  Issue an error if there is a DT_RELR entry
> >> >without GLIBC_ABI_DT_RELR dependency nor GLIBC_PRIVATE definition.
> >> >
> >> >Support __placeholder_only_for_empty_version_map as the placeholder symbol
> >> >used only for empty version map to generate GLIBC_ABI_DT_RELR without any
> >> >symbols.
> >> >---
> >> > elf/Makefile             | 16 ++++++++++++++--
> >> > elf/Versions             |  5 +++++
> >> > elf/dl-version.c         | 33 +++++++++++++++++++++++++++++++--
> >> > elf/libc-abi-version.exp |  1 +
> >> > include/link.h           |  6 ++++++
> >> > scripts/abilist.awk      |  2 ++
> >> > scripts/versions.awk     |  7 ++++++-
> >> > 7 files changed, 65 insertions(+), 5 deletions(-)
> >> > create mode 100644 elf/libc-abi-version.exp
> >> >
> >> >diff --git a/elf/Makefile b/elf/Makefile
> >> >index fd462ba315..f533d377fd 100644
> >> >--- a/elf/Makefile
> >> >+++ b/elf/Makefile
> >> >@@ -1113,8 +1113,10 @@ $(eval $(call include_dsosort_tests,dso-sort-tests-1.def))
> >> > $(eval $(call include_dsosort_tests,dso-sort-tests-2.def))
> >> > endif
> >> >
> >> >-check-abi: $(objpfx)check-abi-ld.out
> >> >-tests-special += $(objpfx)check-abi-ld.out
> >> >+check-abi: $(objpfx)check-abi-ld.out \
> >> >+         $(objpfx)check-abi-version-libc.out
> >> >+tests-special += $(objpfx)check-abi-ld.out \
> >> >+         $(objpfx)check-abi-version-libc.out
> >> > update-abi: update-abi-ld
> >> > update-all-abi: update-all-abi-ld
> >> >
> >> >@@ -2739,3 +2741,13 @@ $(objpfx)check-tst-relr-pie.out: $(objpfx)tst-relr-pie
> >> >               | sed -ne '/required from libc.so/,$$ p' \
> >> >               | grep GLIBC_ABI_DT_RELR > $@; \
> >> >       $(evaluate-test)
> >> >+
> >> >+$(objpfx)check-abi-version-libc.out: libc-abi-version.exp \
> >> >+  $(objpfx)libc.symlist-abi-version
> >> >+      cmp $^ > $@; \
> >> >+      $(evaluate-test)
> >> >+
> >> >+$(objpfx)libc.symlist-abi-version: $(common-objpfx)libc.so
> >> >+      LC_ALL=C $(OBJDUMP) --dynamic-syms $< | grep " GLIBC_ABI_" \
> >> >+              | sed "s/0\+/00000000/g;s/[ \t]\+/ /g" > $@T
> >> >+      mv -f $@T $@
> >>
> >> As just mentioned on https://sourceware.org/pipermail/libc-alpha/2022-March/136764.html ,
> >> perhaps use something like $(READELF) -V $< | grep GLIBC_ABI_DT_RELR
> >>
> >>
> >> % nm -D a/glibc
> >>                   U stat@GLIBC_2.33
> >> % objdump --dynamic-syms a/glibc
> >>
> >> a/glibc:     file format elf64-x86-64
> >>
> >> DYNAMIC SYMBOL TABLE:
> >> 0000000000000000      D  *UND*  0000000000000000 (GLIBC_2.33) stat
> >
> >It is easier to run cmp on "objdump --dynamic-syms" outputs on
> >different targets.
>
> How about
>
> $(READELF) -V $< | grep -o GLIBC_ABI_DT_RELR > $@T
>
> It can be compared with a text file containing "GLIBC_ABI_DT_RELR"

We need GLIBC_ABI_DT_RELR definition.  Reference doesn't count.

> This approach does not require the SHN_ABS symbol.
>
> >>
> >> % readelf -V a/glibc
> >>
> >> Version symbols section '.gnu.version' contains 2 entries:
> >>   Addr: 0x0000000000000230  Offset: 0x000230  Link: 1 (.dynsym)
> >>    000:   0 (*local*)       2 (GLIBC_2.33)
> >>
> >> Version needs section '.gnu.version_r' contains 1 entry:
> >>   Addr: 0x0000000000000234  Offset: 0x000234  Link: 6 (.dynstr)
> >>    000000: Version: 1  File: libc.so.6  Cnt: 2
> >>    0x0010:   Name: GLIBC_2.33  Flags: none  Version: 2
> >>    0x0020:   Name: GLIBC_ABI_DT_RELR  Flags: none  Version: 3
> >>
> >>
> >> Is there a static pie test? A static pie has DT_RELR but does not have verneed.
> >
> >If linker supports DT_RELR, static PIE programs and tests will have
> >DT_RELR.  They will be tested.
>
> OK. I just wanted to mention that a static PIE does not have the
> GLIBC_ABI_DT_RELR version dependency.
>
> Therefore, a static PIE built with older glibc links but will segfault at run-time.
> Perhaps this still serves as the purpose: users will not port it to an
> older glibc system which will fail as well.

True.

> >> >diff --git a/elf/Versions b/elf/Versions
> >> >index 8bed855d8c..a9ff278de7 100644
> >> >--- a/elf/Versions
> >> >+++ b/elf/Versions
> >> >@@ -23,6 +23,11 @@ libc {
> >> >   GLIBC_2.35 {
> >> >     _dl_find_object;
> >> >   }
> >> >+  GLIBC_ABI_DT_RELR {
> >> >+    # This symbol is used only for empty version map and will be removed
> >> >+    # by scripts/versions.awk.
> >> >+    __placeholder_only_for_empty_version_map;
> >> >+  }
> >> >   GLIBC_PRIVATE {
> >> >     # functions used in other libraries
> >> >     __libc_early_init;
> >> >diff --git a/elf/dl-version.c b/elf/dl-version.c
> >> >index b47bd91727..720ec596a5 100644
> >> >--- a/elf/dl-version.c
> >> >+++ b/elf/dl-version.c
> >> >@@ -214,12 +214,20 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
> >> >             while (1)
> >> >               {
> >> >                 /* Match the symbol.  */
> >> >+                const char *string = strtab + aux->vna_name;
> >> >                 result |= match_symbol (DSO_FILENAME (map->l_name),
> >> >                                         map->l_ns, aux->vna_hash,
> >> >-                                        strtab + aux->vna_name,
> >> >-                                        needed->l_real, verbose,
> >> >+                                        string, needed->l_real, verbose,
> >> >                                         aux->vna_flags & VER_FLG_WEAK);
> >> >
> >> >+                if (map->l_abi_version == lav_none
> >> >+                    /* 0xfd0e42: _dl_elf_hash ("GLIBC_ABI_DT_RELR").  */
> >> >+                    && aux->vna_hash == 0xfd0e42
> >> >+                    && __glibc_likely (strcmp (string,
> >> >+                                               "GLIBC_ABI_DT_RELR")
> >> >+                                       == 0))
> >> >+                  map->l_abi_version = lav_dt_relr_ref;
> >> >+
> >> >                 /* Compare the version index.  */
> >> >                 if ((unsigned int) (aux->vna_other & 0x7fff) > ndx_high)
> >> >                   ndx_high = aux->vna_other & 0x7fff;
> >> >@@ -253,6 +261,16 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
> >> >       ent = (ElfW(Verdef) *) (map->l_addr + def->d_un.d_ptr);
> >> >       while (1)
> >> >       {
> >> >+        /* 0x0963cf85: _dl_elf_hash ("GLIBC_PRIVATE").  */
> >> >+        if (ent->vd_hash == 0x0963cf85)
> >> >+          {
> >> >+            ElfW(Verdaux) *aux = (ElfW(Verdaux) *) ((char *) ent
> >> >+                                                    + ent->vd_aux);
> >> >+            if (__glibc_likely (strcmp ("GLIBC_PRIVATE",
> >> >+                                        strtab + aux->vda_name) == 0))
> >> >+              map->l_abi_version = lav_private_def;
> >> >+          }
> >> >+
> >> >         if ((unsigned int) (ent->vd_ndx & 0x7fff) > ndx_high)
> >> >           ndx_high = ent->vd_ndx & 0x7fff;
> >> >
> >> >@@ -352,6 +370,17 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
> >> >       }
> >> >     }
> >> >
> >> >+  /* Issue an error if there is a DT_RELR entry without GLIBC_ABI_DT_RELR
> >> >+     dependency nor GLIBC_PRIVATE definition.  */
> >> >+  if (map->l_info[DT_RELR] != NULL
> >> >+      && __glibc_unlikely (map->l_abi_version == lav_none))
> >> >+    {
> >> >+      _dl_exception_create
> >> >+      (&exception, DSO_FILENAME (map->l_name),
> >> >+       N_("DT_RELR without GLIBC_ABI_DT_RELR dependency"));
> >> >+      goto call_error;
> >> >+    }
> >> >+
> >> >   return result;
> >> > }
> >> >
> >> >diff --git a/elf/libc-abi-version.exp b/elf/libc-abi-version.exp
> >> >new file mode 100644
> >> >index 0000000000..ff8506b3ba
> >> >--- /dev/null
> >> >+++ b/elf/libc-abi-version.exp
> >> >@@ -0,0 +1 @@
> >> >+00000000 g DO *ABS* 00000000 GLIBC_ABI_DT_RELR GLIBC_ABI_DT_RELR
> >> >diff --git a/include/link.h b/include/link.h
> >> >index 03db14c7b0..8ec5e35cf2 100644
> >> >--- a/include/link.h
> >> >+++ b/include/link.h
> >> >@@ -177,6 +177,12 @@ struct link_map
> >> >       lt_library,             /* Library needed by main executable.  */
> >> >       lt_loaded               /* Extra run-time loaded shared object.  */
> >> >       } l_type:2;
> >> >+    enum                      /* ABI dependency of this object.  */
> >> >+      {
> >> >+      lav_none,               /* No ABI dependency.  */
> >> >+      lav_dt_relr_ref,        /* Need GLIBC_ABI_DT_RELR.  */
> >> >+      lav_private_def         /* Define GLIBC_PRIVATE.  */
> >> >+      } l_abi_version:2;
> >> >     unsigned int l_relocated:1;       /* Nonzero if object's relocations done.  */
> >> >     unsigned int l_init_called:1; /* Nonzero if DT_INIT function called.  */
> >> >     unsigned int l_global:1;  /* Nonzero if object in _dl_global_scope.  */
> >> >diff --git a/scripts/abilist.awk b/scripts/abilist.awk
> >> >index 24a34ccbed..6cc7af6ac8 100644
> >> >--- a/scripts/abilist.awk
> >> >+++ b/scripts/abilist.awk
> >> >@@ -55,6 +55,8 @@ $2 == "g" || $2 == "w" && (NF == 7 || NF == 8) {
> >> >   # caused STV_HIDDEN symbols to appear in .dynsym, though that is useless.
> >> >   if (NF > 7 && $7 == ".hidden") next;
> >> >
> >> >+  if (version ~ /^GLIBC_ABI_/ && !include_abi_version) next;
> >> >+
> >> >   if (version == "GLIBC_PRIVATE" && !include_private) next;
> >> >
> >> >   desc = "";
> >> >diff --git a/scripts/versions.awk b/scripts/versions.awk
> >> >index 357ad1355e..d70b07bd1a 100644
> >> >--- a/scripts/versions.awk
> >> >+++ b/scripts/versions.awk
> >> >@@ -185,8 +185,13 @@ END {
> >> >       closeversion(oldver, veryoldver);
> >> >       veryoldver = oldver;
> >> >       }
> >> >-      printf("%s {\n  global:\n", $2) > outfile;
> >> >       oldver = $2;
> >> >+      # Skip the placeholder symbol used only for empty version map.
> >> >+      if ($3 == "__placeholder_only_for_empty_version_map;") {
> >> >+      printf("%s {\n", $2) > outfile;
> >> >+      continue;
> >> >+      }
> >> >+      printf("%s {\n  global:\n", $2) > outfile;
> >> >     }
> >> >     printf("   ") > outfile;
> >> >     for (n = 3; n <= NF; ++n) {
> >> >--
> >> >2.35.1
> >> >
> >
> >
> >
> >--
> >H.J.
Peter O'Connor March 1, 2022, 11:42 p.m. UTC | #5
I've been testing out and using patches (1,2,4) for about 2-3 weeks and
have now made it the default for all packages. The reason for excluding patch 3
(and reverted the relevant binutils patch) was due to the enforcement of
GLIBC_ABI_DT_RELR as it was not supported by the LLD linker (with clang I use
LLD).

Other than the size benefits I was able to measure a 1.7s time reduction (about
1.5% of total) in the configure/build of gettext with clang (shared lib build),
just from enabling RELR in the full LLVM stack (and not in any other package).
Seems fairly significant given that loading clang would only be a portion of
the total build time.

On Wed, Mar 2, 2022 at 3:19 AM H.J. Lu via Libc-alpha
<libc-alpha@sourceware.org> wrote:
> @@ -352,6 +370,17 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
>         }
>      }
>
> +  /* Issue an error if there is a DT_RELR entry without GLIBC_ABI_DT_RELR
> +     dependency nor GLIBC_PRIVATE definition.  */
> +  if (map->l_info[DT_RELR] != NULL
> +      && __glibc_unlikely (map->l_abi_version == lav_none))
> +    {
> +      _dl_exception_create
> +       (&exception, DSO_FILENAME (map->l_name),
> +        N_("DT_RELR without GLIBC_ABI_DT_RELR dependency"));
> +      goto call_error;
> +    }
> +
>    return result;
>  }
>

I have now retested this patch series with most of patch 3, but excluding this
section (which would immediately break the container). As expected, no
problems encountered. But it does make one wonder the value in making glibc
refuse to load a library it is perfectly able to load, just because it lacks
the GLIBC_ABI_DT_RELR dependency. There are four scenarios:

Glibc without this series (<2.36):
(1) RELR file without symbol - Fails to load (or crashes) without good error
    message
(2) RELR file with symbol - Fails to load due to missing symbol (much clearer)

Glibc with this series (2.36+):
(3) RELR file without symbol - Refuses to load with "DT_RELR without
    GLIBC_ABI_DT_RELR dependency" message
(4) RELR file with symbol - Loads and works fine

The main driver of GLIBC_ABI_DT_RELR seemed to be to avoid 'time-travel'
compatibility, essentially to avoid situation (1) where you have no idea why the
file won't load.

Once you have a glibc that includes the "DT_RELR without GLIBC_ABI_DT_RELR
dependency" error, you also have support for RELR and the ability to load it.
There's no time-travel issue in this case, but merely enforcing the requirement
for all linkers wanting to support RELR to add the symbol for glibc.

Peter
diff mbox series

Patch

diff --git a/elf/Makefile b/elf/Makefile
index fd462ba315..f533d377fd 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -1113,8 +1113,10 @@  $(eval $(call include_dsosort_tests,dso-sort-tests-1.def))
 $(eval $(call include_dsosort_tests,dso-sort-tests-2.def))
 endif
 
-check-abi: $(objpfx)check-abi-ld.out
-tests-special += $(objpfx)check-abi-ld.out
+check-abi: $(objpfx)check-abi-ld.out \
+	   $(objpfx)check-abi-version-libc.out
+tests-special += $(objpfx)check-abi-ld.out \
+	   $(objpfx)check-abi-version-libc.out
 update-abi: update-abi-ld
 update-all-abi: update-all-abi-ld
 
@@ -2739,3 +2741,13 @@  $(objpfx)check-tst-relr-pie.out: $(objpfx)tst-relr-pie
 		| sed -ne '/required from libc.so/,$$ p' \
 		| grep GLIBC_ABI_DT_RELR > $@; \
 	$(evaluate-test)
+
+$(objpfx)check-abi-version-libc.out: libc-abi-version.exp \
+  $(objpfx)libc.symlist-abi-version
+	cmp $^ > $@; \
+	$(evaluate-test)
+
+$(objpfx)libc.symlist-abi-version: $(common-objpfx)libc.so
+	LC_ALL=C $(OBJDUMP) --dynamic-syms $< | grep " GLIBC_ABI_" \
+		| sed "s/0\+/00000000/g;s/[ \t]\+/ /g" > $@T
+	mv -f $@T $@
diff --git a/elf/Versions b/elf/Versions
index 8bed855d8c..a9ff278de7 100644
--- a/elf/Versions
+++ b/elf/Versions
@@ -23,6 +23,11 @@  libc {
   GLIBC_2.35 {
     _dl_find_object;
   }
+  GLIBC_ABI_DT_RELR {
+    # This symbol is used only for empty version map and will be removed
+    # by scripts/versions.awk.
+    __placeholder_only_for_empty_version_map;
+  }
   GLIBC_PRIVATE {
     # functions used in other libraries
     __libc_early_init;
diff --git a/elf/dl-version.c b/elf/dl-version.c
index b47bd91727..720ec596a5 100644
--- a/elf/dl-version.c
+++ b/elf/dl-version.c
@@ -214,12 +214,20 @@  _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
 	      while (1)
 		{
 		  /* Match the symbol.  */
+		  const char *string = strtab + aux->vna_name;
 		  result |= match_symbol (DSO_FILENAME (map->l_name),
 					  map->l_ns, aux->vna_hash,
-					  strtab + aux->vna_name,
-					  needed->l_real, verbose,
+					  string, needed->l_real, verbose,
 					  aux->vna_flags & VER_FLG_WEAK);
 
+		  if (map->l_abi_version == lav_none
+		      /* 0xfd0e42: _dl_elf_hash ("GLIBC_ABI_DT_RELR").  */
+		      && aux->vna_hash == 0xfd0e42
+		      && __glibc_likely (strcmp (string,
+						 "GLIBC_ABI_DT_RELR")
+					 == 0))
+		    map->l_abi_version = lav_dt_relr_ref;
+
 		  /* Compare the version index.  */
 		  if ((unsigned int) (aux->vna_other & 0x7fff) > ndx_high)
 		    ndx_high = aux->vna_other & 0x7fff;
@@ -253,6 +261,16 @@  _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
       ent = (ElfW(Verdef) *) (map->l_addr + def->d_un.d_ptr);
       while (1)
 	{
+	  /* 0x0963cf85: _dl_elf_hash ("GLIBC_PRIVATE").  */
+	  if (ent->vd_hash == 0x0963cf85)
+	    {
+	      ElfW(Verdaux) *aux = (ElfW(Verdaux) *) ((char *) ent
+						      + ent->vd_aux);
+	      if (__glibc_likely (strcmp ("GLIBC_PRIVATE",
+					  strtab + aux->vda_name) == 0))
+		map->l_abi_version = lav_private_def;
+	    }
+
 	  if ((unsigned int) (ent->vd_ndx & 0x7fff) > ndx_high)
 	    ndx_high = ent->vd_ndx & 0x7fff;
 
@@ -352,6 +370,17 @@  _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
 	}
     }
 
+  /* Issue an error if there is a DT_RELR entry without GLIBC_ABI_DT_RELR
+     dependency nor GLIBC_PRIVATE definition.  */
+  if (map->l_info[DT_RELR] != NULL
+      && __glibc_unlikely (map->l_abi_version == lav_none))
+    {
+      _dl_exception_create
+	(&exception, DSO_FILENAME (map->l_name),
+	 N_("DT_RELR without GLIBC_ABI_DT_RELR dependency"));
+      goto call_error;
+    }
+
   return result;
 }
 
diff --git a/elf/libc-abi-version.exp b/elf/libc-abi-version.exp
new file mode 100644
index 0000000000..ff8506b3ba
--- /dev/null
+++ b/elf/libc-abi-version.exp
@@ -0,0 +1 @@ 
+00000000 g DO *ABS* 00000000 GLIBC_ABI_DT_RELR GLIBC_ABI_DT_RELR
diff --git a/include/link.h b/include/link.h
index 03db14c7b0..8ec5e35cf2 100644
--- a/include/link.h
+++ b/include/link.h
@@ -177,6 +177,12 @@  struct link_map
 	lt_library,		/* Library needed by main executable.  */
 	lt_loaded		/* Extra run-time loaded shared object.  */
       } l_type:2;
+    enum			/* ABI dependency of this object.  */
+      {
+	lav_none,		/* No ABI dependency.  */
+	lav_dt_relr_ref,	/* Need GLIBC_ABI_DT_RELR.  */
+	lav_private_def		/* Define GLIBC_PRIVATE.  */
+      } l_abi_version:2;
     unsigned int l_relocated:1;	/* Nonzero if object's relocations done.  */
     unsigned int l_init_called:1; /* Nonzero if DT_INIT function called.  */
     unsigned int l_global:1;	/* Nonzero if object in _dl_global_scope.  */
diff --git a/scripts/abilist.awk b/scripts/abilist.awk
index 24a34ccbed..6cc7af6ac8 100644
--- a/scripts/abilist.awk
+++ b/scripts/abilist.awk
@@ -55,6 +55,8 @@  $2 == "g" || $2 == "w" && (NF == 7 || NF == 8) {
   # caused STV_HIDDEN symbols to appear in .dynsym, though that is useless.
   if (NF > 7 && $7 == ".hidden") next;
 
+  if (version ~ /^GLIBC_ABI_/ && !include_abi_version) next;
+
   if (version == "GLIBC_PRIVATE" && !include_private) next;
 
   desc = "";
diff --git a/scripts/versions.awk b/scripts/versions.awk
index 357ad1355e..d70b07bd1a 100644
--- a/scripts/versions.awk
+++ b/scripts/versions.awk
@@ -185,8 +185,13 @@  END {
 	closeversion(oldver, veryoldver);
 	veryoldver = oldver;
       }
-      printf("%s {\n  global:\n", $2) > outfile;
       oldver = $2;
+      # Skip the placeholder symbol used only for empty version map.
+      if ($3 == "__placeholder_only_for_empty_version_map;") {
+	printf("%s {\n", $2) > outfile;
+	continue;
+      }
+      printf("%s {\n  global:\n", $2) > outfile;
     }
     printf("   ") > outfile;
     for (n = 3; n <= NF; ++n) {