diff mbox series

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

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

Commit Message

H.J. Lu Feb. 7, 2022, 11:51 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             | 18 ++++++++++++++++--
 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, 67 insertions(+), 5 deletions(-)
 create mode 100644 elf/libc-abi-version.exp

Comments

Fangrui Song March 1, 2022, 4:22 a.m. UTC | #1
On 2022-02-07, 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             | 18 ++++++++++++++++--
> 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, 67 insertions(+), 5 deletions(-)
> create mode 100644 elf/libc-abi-version.exp
>
>diff --git a/elf/Makefile b/elf/Makefile
>index 71b08c75dd..a6515e8a21 100644
>--- a/elf/Makefile
>+++ b/elf/Makefile
>@@ -48,6 +48,10 @@ routines = \
>   rtld_static_init \
>   # routines
>
>+ifeq ($(have-dt-relr),yes)
>+check-abi-version-libc = $(objpfx)check-abi-version-libc.out
>+endif
>+
> # The core dynamic linking functions are in libc for the static and
> # profiled libraries.
> dl-routines = \
>@@ -1106,8 +1110,8 @@ $(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 $(check-abi-version-libc)
>+tests-special += $(objpfx)check-abi-ld.out $(check-abi-version-libc)
> update-abi: update-abi-ld
> update-all-abi: update-all-abi-ld
>
>@@ -2747,3 +2751,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 $(NM) -D $< | grep " GLIBC_ABI_" \
>+		| sed "s/^0\+/00000000/" > $@T
>+	mv -f $@T $@

This checks the SHN_ABS symbol.
ld.lld does not add the SHN_ABS symbol and does not plan to add it as,
Perhaps just check the placeholder symbol.

GNU nm before binutils 2.35 does not display @ or @@ in -D mode.

>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..455088dc6b
>--- /dev/null
>+++ b/elf/libc-abi-version.exp
>@@ -0,0 +1 @@
>+00000000 A GLIBC_ABI_DT_RELR
>diff --git a/include/link.h b/include/link.h
>index bef2820b40..ce9e3d5214 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.34.1
>

Looks like a Serpent OS contributor has noticed this patch series
https://github.com/llvm/llvm-project/issues/53775#issuecomment-1046361237 .
I will try implementing -z pack-relative-relocs for ld.lld
H.J. Lu March 1, 2022, 2:42 p.m. UTC | #2
On Mon, Feb 28, 2022 at 8:22 PM Fangrui Song <maskray@google.com> wrote:
>
> On 2022-02-07, 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             | 18 ++++++++++++++++--
> > 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, 67 insertions(+), 5 deletions(-)
> > create mode 100644 elf/libc-abi-version.exp
> >
> >diff --git a/elf/Makefile b/elf/Makefile
> >index 71b08c75dd..a6515e8a21 100644
> >--- a/elf/Makefile
> >+++ b/elf/Makefile
> >@@ -48,6 +48,10 @@ routines = \
> >   rtld_static_init \
> >   # routines
> >
> >+ifeq ($(have-dt-relr),yes)
> >+check-abi-version-libc = $(objpfx)check-abi-version-libc.out
> >+endif
> >+
> > # The core dynamic linking functions are in libc for the static and
> > # profiled libraries.
> > dl-routines = \
> >@@ -1106,8 +1110,8 @@ $(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 $(check-abi-version-libc)
> >+tests-special += $(objpfx)check-abi-ld.out $(check-abi-version-libc)
> > update-abi: update-abi-ld
> > update-all-abi: update-all-abi-ld
> >
> >@@ -2747,3 +2751,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 $(NM) -D $< | grep " GLIBC_ABI_" \
> >+              | sed "s/^0\+/00000000/" > $@T
> >+      mv -f $@T $@
>
> This checks the SHN_ABS symbol.
> ld.lld does not add the SHN_ABS symbol and does not plan to add it as,
> Perhaps just check the placeholder symbol.

Does lld support version dependency without a symbol?
I want to avoid adding the unused symbols to libc.so.

> GNU nm before binutils 2.35 does not display @ or @@ in -D mode.

I will check it.

> >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..455088dc6b
> >--- /dev/null
> >+++ b/elf/libc-abi-version.exp
> >@@ -0,0 +1 @@
> >+00000000 A GLIBC_ABI_DT_RELR
> >diff --git a/include/link.h b/include/link.h
> >index bef2820b40..ce9e3d5214 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.34.1
> >
>
> Looks like a Serpent OS contributor has noticed this patch series
> https://github.com/llvm/llvm-project/issues/53775#issuecomment-1046361237 .
> I will try implementing -z pack-relative-relocs for ld.lld
H.J. Lu March 1, 2022, 3:10 p.m. UTC | #3
On Tue, Mar 1, 2022 at 6:42 AM H.J. Lu <hjl.tools@gmail.com> wrote:
>
> On Mon, Feb 28, 2022 at 8:22 PM Fangrui Song <maskray@google.com> wrote:
> >
> > On 2022-02-07, 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             | 18 ++++++++++++++++--
> > > 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, 67 insertions(+), 5 deletions(-)
> > > create mode 100644 elf/libc-abi-version.exp
> > >
> > >diff --git a/elf/Makefile b/elf/Makefile
> > >index 71b08c75dd..a6515e8a21 100644
> > >--- a/elf/Makefile
> > >+++ b/elf/Makefile
> > >@@ -48,6 +48,10 @@ routines = \
> > >   rtld_static_init \
> > >   # routines
> > >
> > >+ifeq ($(have-dt-relr),yes)
> > >+check-abi-version-libc = $(objpfx)check-abi-version-libc.out
> > >+endif
> > >+
> > > # The core dynamic linking functions are in libc for the static and
> > > # profiled libraries.
> > > dl-routines = \
> > >@@ -1106,8 +1110,8 @@ $(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 $(check-abi-version-libc)
> > >+tests-special += $(objpfx)check-abi-ld.out $(check-abi-version-libc)
> > > update-abi: update-abi-ld
> > > update-all-abi: update-all-abi-ld
> > >
> > >@@ -2747,3 +2751,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 $(NM) -D $< | grep " GLIBC_ABI_" \
> > >+              | sed "s/^0\+/00000000/" > $@T
> > >+      mv -f $@T $@
> >
> > This checks the SHN_ABS symbol.
> > ld.lld does not add the SHN_ABS symbol and does not plan to add it as,
> > Perhaps just check the placeholder symbol.
>
> Does lld support version dependency without a symbol?
> I want to avoid adding the unused symbols to libc.so.
>
> > GNU nm before binutils 2.35 does not display @ or @@ in -D mode.
>
> I will check it.

check-abi-version-libc.out doesn't depend on DT_RELR support in
binutils.  I will update it.

> > >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..455088dc6b
> > >--- /dev/null
> > >+++ b/elf/libc-abi-version.exp
> > >@@ -0,0 +1 @@
> > >+00000000 A GLIBC_ABI_DT_RELR
> > >diff --git a/include/link.h b/include/link.h
> > >index bef2820b40..ce9e3d5214 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.34.1
> > >
> >
> > Looks like a Serpent OS contributor has noticed this patch series
> > https://github.com/llvm/llvm-project/issues/53775#issuecomment-1046361237 .
> > I will try implementing -z pack-relative-relocs for ld.lld
>
>
>
> --
> H.J.
Fangrui Song March 1, 2022, 7:13 p.m. UTC | #4
On 2022-03-01, H.J. Lu wrote:
>On Mon, Feb 28, 2022 at 8:22 PM Fangrui Song <maskray@google.com> wrote:
>>
>> On 2022-02-07, 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             | 18 ++++++++++++++++--
>> > 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, 67 insertions(+), 5 deletions(-)
>> > create mode 100644 elf/libc-abi-version.exp
>> >
>> >diff --git a/elf/Makefile b/elf/Makefile
>> >index 71b08c75dd..a6515e8a21 100644
>> >--- a/elf/Makefile
>> >+++ b/elf/Makefile
>> >@@ -48,6 +48,10 @@ routines = \
>> >   rtld_static_init \
>> >   # routines
>> >
>> >+ifeq ($(have-dt-relr),yes)
>> >+check-abi-version-libc = $(objpfx)check-abi-version-libc.out
>> >+endif
>> >+
>> > # The core dynamic linking functions are in libc for the static and
>> > # profiled libraries.
>> > dl-routines = \
>> >@@ -1106,8 +1110,8 @@ $(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 $(check-abi-version-libc)
>> >+tests-special += $(objpfx)check-abi-ld.out $(check-abi-version-libc)
>> > update-abi: update-abi-ld
>> > update-all-abi: update-all-abi-ld
>> >
>> >@@ -2747,3 +2751,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 $(NM) -D $< | grep " GLIBC_ABI_" \
>> >+              | sed "s/^0\+/00000000/" > $@T
>> >+      mv -f $@T $@
>>
>> This checks the SHN_ABS symbol.
>> ld.lld does not add the SHN_ABS symbol and does not plan to add it as,
>> Perhaps just check the placeholder symbol.
>
>Does lld support version dependency without a symbol?
>I want to avoid adding the unused symbols to libc.so.

Yes. My https://reviews.llvm.org/D120701 adds GLIBC_ABI_DT_RELR verneed
without a symbol.

It just doesn't synthesize the SHN_ABS symbol (name is the version name) which seems not useful.

>> GNU nm before binutils 2.35 does not display @ or @@ in -D mode.
>
>I will check it.

Thanks!
Perhaps   $(READELF) -V $< | grep GLIBC_ABI_DT_RELR
will be quite good.

>> >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..455088dc6b
>> >--- /dev/null
>> >+++ b/elf/libc-abi-version.exp
>> >@@ -0,0 +1 @@
>> >+00000000 A GLIBC_ABI_DT_RELR
>> >diff --git a/include/link.h b/include/link.h
>> >index bef2820b40..ce9e3d5214 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.34.1
>> >
>>
>> Looks like a Serpent OS contributor has noticed this patch series
>> https://github.com/llvm/llvm-project/issues/53775#issuecomment-1046361237 .
>> I will try implementing -z pack-relative-relocs for ld.lld
>
>
>
>-- 
>H.J.
diff mbox series

Patch

diff --git a/elf/Makefile b/elf/Makefile
index 71b08c75dd..a6515e8a21 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -48,6 +48,10 @@  routines = \
   rtld_static_init \
   # routines
 
+ifeq ($(have-dt-relr),yes)
+check-abi-version-libc = $(objpfx)check-abi-version-libc.out
+endif
+
 # The core dynamic linking functions are in libc for the static and
 # profiled libraries.
 dl-routines = \
@@ -1106,8 +1110,8 @@  $(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 $(check-abi-version-libc)
+tests-special += $(objpfx)check-abi-ld.out $(check-abi-version-libc)
 update-abi: update-abi-ld
 update-all-abi: update-all-abi-ld
 
@@ -2747,3 +2751,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 $(NM) -D $< | grep " GLIBC_ABI_" \
+		| sed "s/^0\+/00000000/" > $@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..455088dc6b
--- /dev/null
+++ b/elf/libc-abi-version.exp
@@ -0,0 +1 @@ 
+00000000 A GLIBC_ABI_DT_RELR
diff --git a/include/link.h b/include/link.h
index bef2820b40..ce9e3d5214 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) {