[v4,3/7] bfd: Improve nm and objdump without section header

Message ID 20230606175846.399377-4-hjl.tools@gmail.com
State Unresolved
Headers
Series ELF: Strip section header in ELF objects |

Checks

Context Check Description
snail/binutils-gdb-check warning Git am fail log

Commit Message

H.J. Lu June 6, 2023, 5:58 p.m. UTC
  When there is no section header in an executable or shared library, we
reconstruct dynamic symbol table from the PT_DYNAMIC segment, which
contains DT_HASH/DT_GNU_HASH/DT_MIPS_XHASH, DT_STRTAB, DT_SYMTAB,
DT_STRSZ, and DT_SYMENT entries, to improve nm and objdump.  For DT_HASH,
the number of dynamic symbol table entries equals the number of chains.
For DT_GNU_HASH/DT_MIPS_XHASH, only defined symbols with non-STB_LOCAL
indings are in hash table.  Since DT_GNU_HASH/DT_MIPS_XHASH place all
symbols with STB_LOCAL binding before symbols with other bindings and
all undefined symbols defined ones in dynamic symbol table, the highest
symbol index in DT_GNU_HASH/DT_MIPS_XHASH is the highest dynamic symbol
table index.  We can also get symbol version from DT_VERSYM, DT_VERDEF
and DT_VERNEED entries.

dt_symtab, dt_versym, dt_verdef, dt_verneed, dt_symtab_count,
dt_verdef_count, dt_verneed_count and dt_strtab are added to
elf_obj_tdata to store dynamic symbol table information.

	PR ld/25617
	* elf-bfd.h (elf_obj_tdata): Add dt_symtab, dt_verdef, dt_verneed,
	dt_symtab_count, dt_verdef_count, dt_verneed_count and dt_strtab.
	(elf_use_dt_symtab_p): New.
	(_bfd_elf_get_dynamic_symbols): Likewise.
	(_bfd_elf_get_section_from_dynamic_symbol): Likewise.
	* elf.c (bfd_elf_get_elf_syms): Use dynamic symbol table if
	neeeded.
	(_bfd_elf_get_dynamic_symtab_upper_bound): Likewise.
	(_bfd_elf_slurp_version_tables): Likewise.
	(offset_from_vma): New function.
	(get_hash_table_data): Likewise.
	(_bfd_elf_get_dynamic_symbols): Likewise.
	(_bfd_elf_get_section_from_dynamic_symbol): Likewise.
	(_bfd_elf_get_symbol_version_name): Likewise.
	* elfcode.h (elf_object_p): Call _bfd_elf_get_dynamic_symbols
	to reconstruct dynamic symbol table from PT_DYNAMIC segment if
	there is no section header.
	(elf_slurp_symbol_table): Use dynamic symbol table if neeeded.
	Don't free isymbuf when dynamic symbol table is used.
	* elflink.c (elf_link_is_defined_archive_symbol): Return wrong
	format error when dynamic symbol table is used.
	(elf_link_add_object_symbols): Likewise.
---
 bfd/elf-bfd.h |  15 ++
 bfd/elf.c     | 729 ++++++++++++++++++++++++++++++++++++++++++++++----
 bfd/elfcode.h |  55 +++-
 bfd/elflink.c |  12 +
 4 files changed, 746 insertions(+), 65 deletions(-)
  

Comments

Simon Marchi July 1, 2023, 2:12 a.m. UTC | #1
On 6/6/23 13:58, H.J. Lu wrote:
> When there is no section header in an executable or shared library, we
> reconstruct dynamic symbol table from the PT_DYNAMIC segment, which
> contains DT_HASH/DT_GNU_HASH/DT_MIPS_XHASH, DT_STRTAB, DT_SYMTAB,
> DT_STRSZ, and DT_SYMENT entries, to improve nm and objdump.  For DT_HASH,
> the number of dynamic symbol table entries equals the number of chains.
> For DT_GNU_HASH/DT_MIPS_XHASH, only defined symbols with non-STB_LOCAL
> indings are in hash table.  Since DT_GNU_HASH/DT_MIPS_XHASH place all
> symbols with STB_LOCAL binding before symbols with other bindings and
> all undefined symbols defined ones in dynamic symbol table, the highest
> symbol index in DT_GNU_HASH/DT_MIPS_XHASH is the highest dynamic symbol
> table index.  We can also get symbol version from DT_VERSYM, DT_VERDEF
> and DT_VERNEED entries.
> 
> dt_symtab, dt_versym, dt_verdef, dt_verneed, dt_symtab_count,
> dt_verdef_count, dt_verneed_count and dt_strtab are added to
> elf_obj_tdata to store dynamic symbol table information.

Hi,

This broke a GDB test:

    gdb/ $ make check TESTS="gdb.base/eu-strip-infcall.exp"
    FAIL: gdb.base/eu-strip-infcall.exp: gdb_breakpoint: set breakpoint at main

Looking at gdb/testsuite/gdb.log, before:

    (gdb) file /home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall

    Reading symbols from /home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall...

    Reading symbols from /home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall.debug...


and after:

(gdb) file /home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall

Reading symbols from /home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall...

warning: `/home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall.debug': can't read symbols: file format not recognized.

(No debugging symbols found in /home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall)


Can you please check if it's a problem with your patch, or if it's really a malformed file?

Thanks,

Simon
  
H.J. Lu July 7, 2023, 3:26 p.m. UTC | #2
On Fri, Jun 30, 2023 at 7:12 PM Simon Marchi <simon.marchi@polymtl.ca> wrote:
>
> On 6/6/23 13:58, H.J. Lu wrote:
> > When there is no section header in an executable or shared library, we
> > reconstruct dynamic symbol table from the PT_DYNAMIC segment, which
> > contains DT_HASH/DT_GNU_HASH/DT_MIPS_XHASH, DT_STRTAB, DT_SYMTAB,
> > DT_STRSZ, and DT_SYMENT entries, to improve nm and objdump.  For DT_HASH,
> > the number of dynamic symbol table entries equals the number of chains.
> > For DT_GNU_HASH/DT_MIPS_XHASH, only defined symbols with non-STB_LOCAL
> > indings are in hash table.  Since DT_GNU_HASH/DT_MIPS_XHASH place all
> > symbols with STB_LOCAL binding before symbols with other bindings and
> > all undefined symbols defined ones in dynamic symbol table, the highest
> > symbol index in DT_GNU_HASH/DT_MIPS_XHASH is the highest dynamic symbol
> > table index.  We can also get symbol version from DT_VERSYM, DT_VERDEF
> > and DT_VERNEED entries.
> >
> > dt_symtab, dt_versym, dt_verdef, dt_verneed, dt_symtab_count,
> > dt_verdef_count, dt_verneed_count and dt_strtab are added to
> > elf_obj_tdata to store dynamic symbol table information.
>
> Hi,
>
> This broke a GDB test:
>
>     gdb/ $ make check TESTS="gdb.base/eu-strip-infcall.exp"
>     FAIL: gdb.base/eu-strip-infcall.exp: gdb_breakpoint: set breakpoint at main
>
> Looking at gdb/testsuite/gdb.log, before:
>
>     (gdb) file /home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall
>
>     Reading symbols from /home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall...
>
>     Reading symbols from /home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall.debug...
>
>
> and after:
>
> (gdb) file /home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall
>
> Reading symbols from /home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall...
>
> warning: `/home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall.debug': can't read symbols: file format not recognized.
>
> (No debugging symbols found in /home/smarchi/build/binutils-gdb/gdb/testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall)
>
>
> Can you please check if it's a problem with your patch, or if it's really a malformed file?
>
> Thanks,
>
> Simon

It works for me:

$ make check TESTS="gdb.base/eu-strip-infcall.exp"
....
=== gdb Summary ===

# of expected passes 1

My change only impacts files without section header. eu-strip-infcall.exp does
"eu-strip -f ${binfile}.debug $binfile", which doesn't remove section header.
  
Simon Marchi July 10, 2023, 3:30 a.m. UTC | #3
> It works for me:
> 
> $ make check TESTS="gdb.base/eu-strip-infcall.exp"
> ....
> === gdb Summary ===
> 
> # of expected passes 1
> 
> My change only impacts files without section header. eu-strip-infcall.exp does
> "eu-strip -f ${binfile}.debug $binfile", which doesn't remove section header.
> 

I can reliably reproduce the problem on two separate machine, one Ubuntu
22.04 and one failrly up to date Arch Linux.  elfutils version 0.186 and
0.189, respectively.

It goes wrong when GDB does a bfd_check_format call on
testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall.debug.
Before you commit it works, and after your commit it returns false.  It
happens in this new statement added to elf_object_p, added by the commit:

	      if ((i_phdr->p_offset + i_phdr->p_filesz) > filesize)
		goto got_no_match;

(top-gdb) p i_phdr->p_offset
$1 = 8192
(top-gdb) p i_phdr->p_filesz
$2 = 196
(top-gdb) p filesize
$3 = 5104
(top-gdb) p i
$4 = 4

It would be this program header causing the condition to fail:

  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  ...
  LOAD           0x002000 0x0000000000002000 0x0000000000002000 0x0000c4 0x0000c4 R   0x1000

So, the program header of the .debug file describes the segments of the
main binary, not sure if that's expected.

Simon
  
Alan Modra July 13, 2023, 5:02 a.m. UTC | #4
On Sun, Jul 09, 2023 at 11:30:01PM -0400, Simon Marchi wrote:
> 
> > It works for me:
> > 
> > $ make check TESTS="gdb.base/eu-strip-infcall.exp"
> > ....
> > === gdb Summary ===
> > 
> > # of expected passes 1
> > 
> > My change only impacts files without section header. eu-strip-infcall.exp does
> > "eu-strip -f ${binfile}.debug $binfile", which doesn't remove section header.
> > 
> 
> I can reliably reproduce the problem on two separate machine, one Ubuntu
> 22.04 and one failrly up to date Arch Linux.  elfutils version 0.186 and
> 0.189, respectively.
> 
> It goes wrong when GDB does a bfd_check_format call on
> testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall.debug.
> Before you commit it works, and after your commit it returns false.  It
> happens in this new statement added to elf_object_p, added by the commit:
> 
> 	      if ((i_phdr->p_offset + i_phdr->p_filesz) > filesize)
> 		goto got_no_match;
> 
> (top-gdb) p i_phdr->p_offset
> $1 = 8192
> (top-gdb) p i_phdr->p_filesz
> $2 = 196
> (top-gdb) p filesize
> $3 = 5104
> (top-gdb) p i
> $4 = 4
> 
> It would be this program header causing the condition to fail:
> 
>   Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
>   ...
>   LOAD           0x002000 0x0000000000002000 0x0000000000002000 0x0000c4 0x0000c4 R   0x1000
> 
> So, the program header of the .debug file describes the segments of the
> main binary, not sure if that's expected.

No, that's not expected.  Program headers in a .debug file ought to
describe the contents of the debug file.  You'll typically see many
with p_filesz zero.  eu-strip appears to be broken in this respect.

There is another problem with the code added to elf_object_p:
_bfd_elf_get_dynamic_symbols is told that it can access up to e_phnum
program headers, but they very likely haven't all been swapped in.

I'm going to apply the following patch.
----

elf_object_p load of dynamic symbols

This fixes an uninitialised memory access on a fuzzed file:
0 0xf22e9b in offset_from_vma /src/binutils-gdb/bfd/elf.c:1899:2
1 0xf1e90f in _bfd_elf_get_dynamic_symbols /src/binutils-gdb/bfd/elf.c:2099:13
2 0x10e6a54 in bfd_elf32_object_p /src/binutils-gdb/bfd/elfcode.h:851:9

Hopefully it will also stop any attempt to load dynamic symbols from
eu-strip debug files.

	* elfcode.h (elf_object_p): Do not attempt to load dynamic
	symbols for a file with no section headers until all the
	program headers are swapped in.  Do not fail on eu-strip debug
	files.

diff --git a/bfd/elfcode.h b/bfd/elfcode.h
index aae66bcebf8..b2277921680 100644
--- a/bfd/elfcode.h
+++ b/bfd/elfcode.h
@@ -819,6 +819,7 @@ elf_object_p (bfd *abfd)
 	goto got_no_match;
       if (bfd_seek (abfd, (file_ptr) i_ehdrp->e_phoff, SEEK_SET) != 0)
 	goto got_no_match;
+      bool eu_strip_broken_phdrs = false;
       i_phdr = elf_tdata (abfd)->phdr;
       for (i = 0; i < i_ehdrp->e_phnum; i++, i_phdr++)
 	{
@@ -839,21 +840,31 @@ elf_object_p (bfd *abfd)
 		  abfd->read_only = 1;
 		}
 	    }
-	  if (i_phdr->p_filesz != 0)
-	    {
-	      if ((i_phdr->p_offset + i_phdr->p_filesz) > filesize)
-		goto got_no_match;
-	      /* Try to reconstruct dynamic symbol table from PT_DYNAMIC
-		 segment if there is no section header.  */
-	      if (i_phdr->p_type == PT_DYNAMIC
-		  && i_ehdrp->e_shstrndx == 0
-		  && i_ehdrp->e_shoff == 0
-		  && !_bfd_elf_get_dynamic_symbols (abfd, i_phdr,
-						    elf_tdata (abfd)->phdr,
-						    i_ehdrp->e_phnum,
-						    filesize))
-		goto got_no_match;
-	    }
+	  /* Detect eu-strip -f debug files, which have program
+	     headers that describe the original file.  */
+	  if (i_phdr->p_filesz != 0
+	      && (i_phdr->p_filesz > filesize
+		  || i_phdr->p_offset > filesize - i_phdr->p_filesz))
+	    eu_strip_broken_phdrs = true;
+	}
+      if (!eu_strip_broken_phdrs
+	  && i_ehdrp->e_shoff == 0
+	  && i_ehdrp->e_shstrndx == 0)
+	{
+	  /* Try to reconstruct dynamic symbol table from PT_DYNAMIC
+	     segment if there is no section header.  */
+	  i_phdr = elf_tdata (abfd)->phdr;
+	  for (i = 0; i < i_ehdrp->e_phnum; i++, i_phdr++)
+	    if (i_phdr->p_type == PT_DYNAMIC)
+	      {
+		if (i_phdr->p_filesz != 0
+		    && !_bfd_elf_get_dynamic_symbols (abfd, i_phdr,
+						      elf_tdata (abfd)->phdr,
+						      i_ehdrp->e_phnum,
+						      filesize))
+		  goto got_no_match;
+		break;
+	      }
 	}
     }
  
Fangrui Song July 13, 2023, 5:34 a.m. UTC | #5
On Wed, Jul 12, 2023 at 10:02 PM Alan Modra via Binutils
<binutils@sourceware.org> wrote:
>
> On Sun, Jul 09, 2023 at 11:30:01PM -0400, Simon Marchi wrote:
> >
> > > It works for me:
> > >
> > > $ make check TESTS="gdb.base/eu-strip-infcall.exp"
> > > ....
> > > === gdb Summary ===
> > >
> > > # of expected passes 1
> > >
> > > My change only impacts files without section header. eu-strip-infcall.exp does
> > > "eu-strip -f ${binfile}.debug $binfile", which doesn't remove section header.
> > >
> >
> > I can reliably reproduce the problem on two separate machine, one Ubuntu
> > 22.04 and one failrly up to date Arch Linux.  elfutils version 0.186 and
> > 0.189, respectively.
> >
> > It goes wrong when GDB does a bfd_check_format call on
> > testsuite/outputs/gdb.base/eu-strip-infcall/eu-strip-infcall.debug.
> > Before you commit it works, and after your commit it returns false.  It
> > happens in this new statement added to elf_object_p, added by the commit:
> >
> >             if ((i_phdr->p_offset + i_phdr->p_filesz) > filesize)
> >               goto got_no_match;
> >
> > (top-gdb) p i_phdr->p_offset
> > $1 = 8192
> > (top-gdb) p i_phdr->p_filesz
> > $2 = 196
> > (top-gdb) p filesize
> > $3 = 5104
> > (top-gdb) p i
> > $4 = 4
> >
> > It would be this program header causing the condition to fail:
> >
> >   Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
> >   ...
> >   LOAD           0x002000 0x0000000000002000 0x0000000000002000 0x0000c4 0x0000c4 R   0x1000
> >
> > So, the program header of the .debug file describes the segments of the
> > main binary, not sure if that's expected.
>
> No, that's not expected.  Program headers in a .debug file ought to
> describe the contents of the debug file.  You'll typically see many
> with p_filesz zero.  eu-strip appears to be broken in this respect.

Do you have a bug number for eu-strip ? It seems good to keep a
reference of the bug
so that we can track it.

> There is another problem with the code added to elf_object_p:
> _bfd_elf_get_dynamic_symbols is told that it can access up to e_phnum
> program headers, but they very likely haven't all been swapped in.
>
> I'm going to apply the following patch.
> ----
>
> elf_object_p load of dynamic symbols
>
> This fixes an uninitialised memory access on a fuzzed file:
> 0 0xf22e9b in offset_from_vma /src/binutils-gdb/bfd/elf.c:1899:2
> 1 0xf1e90f in _bfd_elf_get_dynamic_symbols /src/binutils-gdb/bfd/elf.c:2099:13
> 2 0x10e6a54 in bfd_elf32_object_p /src/binutils-gdb/bfd/elfcode.h:851:9
>
> Hopefully it will also stop any attempt to load dynamic symbols from
> eu-strip debug files.
>
>         * elfcode.h (elf_object_p): Do not attempt to load dynamic
>         symbols for a file with no section headers until all the
>         program headers are swapped in.  Do not fail on eu-strip debug
>         files.
>
> diff --git a/bfd/elfcode.h b/bfd/elfcode.h
> index aae66bcebf8..b2277921680 100644
> --- a/bfd/elfcode.h
> +++ b/bfd/elfcode.h
> @@ -819,6 +819,7 @@ elf_object_p (bfd *abfd)
>         goto got_no_match;
>        if (bfd_seek (abfd, (file_ptr) i_ehdrp->e_phoff, SEEK_SET) != 0)
>         goto got_no_match;
> +      bool eu_strip_broken_phdrs = false;
>        i_phdr = elf_tdata (abfd)->phdr;
>        for (i = 0; i < i_ehdrp->e_phnum; i++, i_phdr++)
>         {
> @@ -839,21 +840,31 @@ elf_object_p (bfd *abfd)
>                   abfd->read_only = 1;
>                 }
>             }
> -         if (i_phdr->p_filesz != 0)
> -           {
> -             if ((i_phdr->p_offset + i_phdr->p_filesz) > filesize)
> -               goto got_no_match;
> -             /* Try to reconstruct dynamic symbol table from PT_DYNAMIC
> -                segment if there is no section header.  */
> -             if (i_phdr->p_type == PT_DYNAMIC
> -                 && i_ehdrp->e_shstrndx == 0
> -                 && i_ehdrp->e_shoff == 0
> -                 && !_bfd_elf_get_dynamic_symbols (abfd, i_phdr,
> -                                                   elf_tdata (abfd)->phdr,
> -                                                   i_ehdrp->e_phnum,
> -                                                   filesize))
> -               goto got_no_match;
> -           }
> +         /* Detect eu-strip -f debug files, which have program
> +            headers that describe the original file.  */
> +         if (i_phdr->p_filesz != 0
> +             && (i_phdr->p_filesz > filesize
> +                 || i_phdr->p_offset > filesize - i_phdr->p_filesz))
> +           eu_strip_broken_phdrs = true;
> +       }
> +      if (!eu_strip_broken_phdrs
> +         && i_ehdrp->e_shoff == 0
> +         && i_ehdrp->e_shstrndx == 0)
> +       {
> +         /* Try to reconstruct dynamic symbol table from PT_DYNAMIC
> +            segment if there is no section header.  */
> +         i_phdr = elf_tdata (abfd)->phdr;
> +         for (i = 0; i < i_ehdrp->e_phnum; i++, i_phdr++)
> +           if (i_phdr->p_type == PT_DYNAMIC)
> +             {
> +               if (i_phdr->p_filesz != 0
> +                   && !_bfd_elf_get_dynamic_symbols (abfd, i_phdr,
> +                                                     elf_tdata (abfd)->phdr,
> +                                                     i_ehdrp->e_phnum,
> +                                                     filesize))
> +                 goto got_no_match;
> +               break;
> +             }
>         }
>      }
>
>
> --
> Alan Modra
> Australia Development Lab, IBM
  
Mark Wielaard July 13, 2023, 9:58 p.m. UTC | #6
Hi,

On Thu, Jul 13, 2023 at 02:32:25PM +0930, Alan Modra via Binutils wrote:
> > So, the program header of the .debug file describes the segments of the
> > main binary, not sure if that's expected.
> 
> No, that's not expected.  Program headers in a .debug file ought to
> describe the contents of the debug file.  You'll typically see many
> with p_filesz zero.  eu-strip appears to be broken in this respect.

It is by design that eu-strip -f copies over the program headers of
the main file into the .debug file. It would be nice to tag .debug
files as such, to prevent issues like this. There is a binutils bug
about it: https://sourceware.org/bugzilla/show_bug.cgi?id=22136

> There is another problem with the code added to elf_object_p:
> _bfd_elf_get_dynamic_symbols is told that it can access up to e_phnum
> program headers, but they very likely haven't all been swapped in.
> 
> I'm going to apply the following patch.
>
> ----
> 
> elf_object_p load of dynamic symbols
> 
> This fixes an uninitialised memory access on a fuzzed file:
> 0 0xf22e9b in offset_from_vma /src/binutils-gdb/bfd/elf.c:1899:2
> 1 0xf1e90f in _bfd_elf_get_dynamic_symbols /src/binutils-gdb/bfd/elf.c:2099:13
> 2 0x10e6a54 in bfd_elf32_object_p /src/binutils-gdb/bfd/elfcode.h:851:9
> 
> Hopefully it will also stop any attempt to load dynamic symbols from
> eu-strip debug files.
> 
> 	* elfcode.h (elf_object_p): Do not attempt to load dynamic
> 	symbols for a file with no section headers until all the
> 	program headers are swapped in.  Do not fail on eu-strip debug
> 	files.

Thanks! This does resolves an elfutils/debuginfod issue Ryan and I
were tracking down on debian-testing with using binutils objcopy
extracting sections from a .debug file. Debian testing ships with
binutils 2.40.90.20230705.

If possible could this go into 2.41 (and in an update for Debian
testing)?

Thanks,

Mark
  
Alan Modra July 19, 2023, 6:21 a.m. UTC | #7
On Thu, Jul 13, 2023 at 11:58:08PM +0200, Mark Wielaard wrote:
> If possible could this go into 2.41

Done.
  

Patch

diff --git a/bfd/elf-bfd.h b/bfd/elf-bfd.h
index 2a64a1e6a03..dd8d6efbbcc 100644
--- a/bfd/elf-bfd.h
+++ b/bfd/elf-bfd.h
@@ -2031,6 +2031,14 @@  struct elf_obj_tdata
   Elf_Internal_Shdr dynversym_hdr;
   Elf_Internal_Shdr dynverref_hdr;
   Elf_Internal_Shdr dynverdef_hdr;
+  Elf_Internal_Sym *dt_symtab;
+  bfd_byte *dt_versym;
+  bfd_byte *dt_verdef;
+  bfd_byte *dt_verneed;
+  size_t dt_symtab_count;
+  size_t dt_verdef_count;
+  size_t dt_verneed_count;
+  char *dt_strtab;
   elf_section_list * symtab_shndx_list;
   bfd_vma gp;				/* The gp value */
   unsigned int gp_size;			/* The gp size */
@@ -2194,6 +2202,7 @@  struct elf_obj_tdata
 #define elf_dyn_lib_class(bfd)	(elf_tdata(bfd) -> dyn_lib_class)
 #define elf_bad_symtab(bfd)	(elf_tdata(bfd) -> bad_symtab)
 #define elf_flags_init(bfd)	(elf_tdata(bfd) -> o->flags_init)
+#define elf_use_dt_symtab_p(bfd) (elf_tdata(bfd) -> dt_symtab_count != 0)
 #define elf_known_obj_attributes(bfd) (elf_tdata (bfd) -> known_obj_attributes)
 #define elf_other_obj_attributes(bfd) (elf_tdata (bfd) -> other_obj_attributes)
 #define elf_known_obj_attributes_proc(bfd) \
@@ -2587,6 +2596,12 @@  extern bfd_reloc_status_type bfd_elf_perform_complex_relocation
 extern bool _bfd_elf_setup_sections
   (bfd *);
 
+extern bool _bfd_elf_get_dynamic_symbols
+  (bfd *, Elf_Internal_Phdr *, Elf_Internal_Phdr *, size_t,
+   bfd_size_type);
+extern asection *_bfd_elf_get_section_from_dynamic_symbol
+  (bfd *, Elf_Internal_Sym *);
+
 extern struct bfd_link_hash_entry *bfd_elf_define_start_stop
   (struct bfd_link_info *, const char *, asection *);
 
diff --git a/bfd/elf.c b/bfd/elf.c
index 07ad15ddb2f..29d05e47786 100644
--- a/bfd/elf.c
+++ b/bfd/elf.c
@@ -397,6 +397,17 @@  bfd_elf_get_elf_syms (bfd *ibfd,
   if (symcount == 0)
     return intsym_buf;
 
+  if (elf_use_dt_symtab_p (ibfd))
+    {
+      /* Use dynamic symbol table.  */
+      if (elf_tdata (ibfd)->dt_symtab_count != symcount + symoffset)
+	{
+	  bfd_set_error (bfd_error_invalid_operation);
+	  return NULL;
+	}
+      return elf_tdata (ibfd)->dt_symtab + symoffset;
+    }
+
   /* Normal syms might have section extension entries.  */
   shndx_hdr = NULL;
   if (elf_symtab_shndx_list (ibfd) != NULL)
@@ -1873,6 +1884,551 @@  _bfd_elf_print_private_bfd_data (bfd *abfd, void *farg)
   return false;
 }
 
+/* Find the file offset corresponding to VMA by using the program
+   headers.  */
+
+static file_ptr
+offset_from_vma (Elf_Internal_Phdr *phdrs, size_t phnum, bfd_vma vma,
+		 size_t size, size_t *max_size_p)
+{
+  Elf_Internal_Phdr *seg;
+  size_t i;
+
+  for (seg = phdrs, i = 0; i < phnum; ++seg, ++i)
+    if (seg->p_type == PT_LOAD
+	&& vma >= (seg->p_vaddr & -seg->p_align)
+	&& vma + size <= seg->p_vaddr + seg->p_filesz)
+      {
+	if (max_size_p)
+	  *max_size_p = seg->p_vaddr + seg->p_filesz - vma;
+	return vma - seg->p_vaddr + seg->p_offset;
+      }
+
+  bfd_set_error (bfd_error_invalid_operation);
+  return (file_ptr) -1;
+}
+
+/* Convert hash table to internal form.  */
+
+static bfd_vma *
+get_hash_table_data (bfd *abfd, bfd_size_type number,
+		     unsigned int ent_size, bfd_size_type filesize)
+{
+  unsigned char *e_data = NULL;
+  bfd_vma *i_data = NULL;
+  bfd_size_type size;
+
+  if (ent_size != 4 && ent_size != 8)
+    return NULL;
+
+  if ((size_t) number != number)
+    {
+      bfd_set_error (bfd_error_file_too_big);
+      return NULL;
+    }
+
+  size = ent_size * number;
+  /* Be kind to memory checkers (eg valgrind, address sanitizer) by not
+     attempting to allocate memory when the read is bound to fail.  */
+  if (size > filesize
+      || number >= ~(size_t) 0 / ent_size
+      || number >= ~(size_t) 0 / sizeof (*i_data))
+    {
+      bfd_set_error (bfd_error_file_too_big);
+      return NULL;
+    }
+
+  e_data = _bfd_malloc_and_read (abfd, size, size);
+  if (e_data == NULL)
+    return NULL;
+
+  i_data = (bfd_vma *) bfd_malloc (number * sizeof (*i_data));
+  if (i_data == NULL)
+    {
+      free (e_data);
+      return NULL;
+    }
+
+  if (ent_size == 4)
+    while (number--)
+      i_data[number] = bfd_get_32 (abfd, e_data + number * ent_size);
+  else
+    while (number--)
+      i_data[number] = bfd_get_64 (abfd, e_data + number * ent_size);
+
+  free (e_data);
+  return i_data;
+}
+
+/* Address of .MIPS.xhash section.  FIXME: What is the best way to
+   support DT_MIPS_XHASH?  */
+#define DT_MIPS_XHASH	       0x70000036
+
+/* Reconstruct dynamic symbol table from PT_DYNAMIC segment.  */
+
+bool
+_bfd_elf_get_dynamic_symbols (bfd *abfd, Elf_Internal_Phdr *phdr,
+			      Elf_Internal_Phdr *phdrs, size_t phnum,
+			      bfd_size_type filesize)
+{
+  bfd_byte *extdyn, *extdynend;
+  size_t extdynsize;
+  void (*swap_dyn_in) (bfd *, const void *, Elf_Internal_Dyn *);
+  bool (*swap_symbol_in) (bfd *, const void *, const void *,
+			  Elf_Internal_Sym *);
+  Elf_Internal_Dyn dyn;
+  bfd_vma dt_hash = 0;
+  bfd_vma dt_gnu_hash = 0;
+  bfd_vma dt_mips_xhash = 0;
+  bfd_vma dt_strtab = 0;
+  bfd_vma dt_symtab = 0;
+  size_t dt_strsz = 0;
+  bfd_vma dt_versym = 0;
+  bfd_vma dt_verdef = 0;
+  bfd_vma dt_verneed = 0;
+  bfd_byte *dynbuf = NULL;
+  char *strbuf = NULL;
+  bfd_vma *gnubuckets = NULL;
+  bfd_vma *gnuchains = NULL;
+  bfd_vma *mipsxlat = NULL;
+  file_ptr saved_filepos, filepos;
+  bool res = false;
+  size_t amt;
+  bfd_byte *esymbuf = NULL, *esym;
+  bfd_size_type symcount;
+  Elf_Internal_Sym *isymbuf = NULL;
+  Elf_Internal_Sym *isym, *isymend;
+  bfd_byte *versym = NULL;
+  bfd_byte *verdef = NULL;
+  bfd_byte *verneed = NULL;
+  size_t verdef_size;
+  size_t verneed_size;
+  size_t extsym_size;
+  const struct elf_backend_data *bed;
+
+  /* Return TRUE if symbol table is bad.  */
+  if (elf_bad_symtab (abfd))
+    return true;
+
+  /* Return TRUE if DT_HASH/DT_GNU_HASH have bee processed before.  */
+  if (elf_tdata (abfd)->dt_strtab != NULL)
+    return true;
+
+  bed = get_elf_backend_data (abfd);
+
+  /* Save file position for elf_object_p.  */
+  saved_filepos = bfd_tell (abfd);
+
+  if (bfd_seek (abfd, phdr->p_offset, SEEK_SET) != 0)
+    goto error_return;
+
+  dynbuf = _bfd_malloc_and_read (abfd, phdr->p_filesz, phdr->p_filesz);
+  if (dynbuf == NULL)
+    goto error_return;
+
+  extsym_size = bed->s->sizeof_sym;
+  extdynsize = bed->s->sizeof_dyn;
+  swap_dyn_in = bed->s->swap_dyn_in;
+
+  extdyn = dynbuf;
+  if (phdr->p_filesz < extdynsize)
+    goto error_return;
+  extdynend = extdyn + phdr->p_filesz;
+  for (; extdyn <= (extdynend - extdynsize); extdyn += extdynsize)
+    {
+      swap_dyn_in (abfd, extdyn, &dyn);
+
+      if (dyn.d_tag == DT_NULL)
+	break;
+
+      switch (dyn.d_tag)
+	{
+	case DT_HASH:
+	  dt_hash = dyn.d_un.d_val;
+	  break;
+	case DT_GNU_HASH:
+	  if (bed->elf_machine_code != EM_MIPS
+	      && bed->elf_machine_code != EM_MIPS_RS3_LE)
+	    dt_gnu_hash = dyn.d_un.d_val;
+	  break;
+	case DT_STRTAB:
+	  dt_strtab = dyn.d_un.d_val;
+	  break;
+	case DT_SYMTAB:
+	  dt_symtab = dyn.d_un.d_val;
+	  break;
+	case DT_STRSZ:
+	  dt_strsz = dyn.d_un.d_val;
+	  break;
+	case DT_SYMENT:
+	  if (dyn.d_un.d_val != extsym_size)
+	    goto error_return;
+	  break;
+	case DT_VERSYM:
+	  dt_versym = dyn.d_un.d_val;
+	  break;
+	case DT_VERDEF:
+	  dt_verdef = dyn.d_un.d_val;
+	  break;
+	case DT_VERNEED:
+	  dt_verneed = dyn.d_un.d_val;
+	  break;
+	default:
+	  if (dyn.d_tag == DT_MIPS_XHASH
+	      && (bed->elf_machine_code == EM_MIPS
+		  || bed->elf_machine_code == EM_MIPS_RS3_LE))
+	    {
+	      dt_gnu_hash = dyn.d_un.d_val;
+	      dt_mips_xhash = dyn.d_un.d_val;
+	    }
+	  break;
+	}
+    }
+
+  /* Check if we can reconstruct dynamic symbol table from PT_DYNAMIC
+     segment.  */
+  if ((!dt_hash && !dt_gnu_hash)
+      || !dt_strtab
+      || !dt_symtab
+      || !dt_strsz)
+    goto error_return;
+
+  /* Get dynamic string table.  */
+  filepos = offset_from_vma (phdrs, phnum, dt_strtab, dt_strsz, NULL);
+  if (filepos == (file_ptr) -1
+      || bfd_seek (abfd, filepos, SEEK_SET) != 0)
+    goto error_return;
+
+  /* Dynamic string table must be valid until ABFD is closed.  */
+  strbuf = (char *) _bfd_alloc_and_read (abfd, dt_strsz, dt_strsz);
+  if (strbuf == NULL)
+    goto error_return;
+
+  /* Get the real symbol count from DT_HASH or DT_GNU_HASH.  Prefer
+     DT_HASH since it is simpler than DT_GNU_HASH.  */
+  if (dt_hash)
+    {
+      unsigned char nb[16];
+      unsigned int hash_ent_size;
+
+      switch (bed->elf_machine_code)
+	{
+	case EM_ALPHA:
+	case EM_S390:
+	case EM_S390_OLD:
+	  if (bed->s->elfclass == ELFCLASS64)
+	    {
+	      hash_ent_size = 8;
+	      break;
+	    }
+	  /* FALLTHROUGH */
+	default:
+	  hash_ent_size = 4;
+	  break;
+	}
+
+      filepos = offset_from_vma (phdrs, phnum, dt_hash, sizeof (nb),
+				 NULL);
+      if (filepos == (file_ptr) -1
+	  || bfd_seek (abfd, filepos, SEEK_SET) != 0
+	  || (bfd_bread (nb, 2 * hash_ent_size, abfd)
+	      != (2 * hash_ent_size)))
+	goto error_return;
+
+      /* The number of dynamic symbol table entries equals the number
+	 of chains.  */
+      if (hash_ent_size == 8)
+	symcount = bfd_get_64 (abfd, nb + hash_ent_size);
+      else
+	symcount = bfd_get_32 (abfd, nb + hash_ent_size);
+    }
+  else
+    {
+      /* For DT_GNU_HASH, only defined symbols with non-STB_LOCAL
+	 bindings are in hash table.  Since in dynamic symbol table,
+	 all symbols with STB_LOCAL binding are placed before symbols
+	 with other bindings and all undefined symbols are placed
+	 before defined ones, the highest symbol index in DT_GNU_HASH
+	 is the highest dynamic symbol table index.  */
+      unsigned char nb[16];
+      bfd_vma ngnubuckets;
+      bfd_vma gnusymidx;
+      size_t i, ngnuchains;
+      bfd_vma maxchain = 0xffffffff, bitmaskwords;
+      bfd_vma buckets_vma;
+
+      filepos = offset_from_vma (phdrs, phnum, dt_gnu_hash,
+				 sizeof (nb), NULL);
+      if (filepos == (file_ptr) -1
+	  || bfd_seek (abfd, filepos, SEEK_SET) != 0
+	  || bfd_bread (nb, sizeof (nb), abfd) != sizeof (nb))
+	goto error_return;
+
+      ngnubuckets = bfd_get_32 (abfd, nb);
+      gnusymidx = bfd_get_32 (abfd, nb + 4);
+      bitmaskwords = bfd_get_32 (abfd, nb + 8);
+      buckets_vma = dt_gnu_hash + 16;
+      if (bed->s->elfclass == ELFCLASS32)
+	buckets_vma += bitmaskwords * 4;
+      else
+	buckets_vma += bitmaskwords * 8;
+      filepos = offset_from_vma (phdrs, phnum, buckets_vma, 4, NULL);
+      if (filepos == (file_ptr) -1
+	  || bfd_seek (abfd, filepos, SEEK_SET) != 0)
+	goto error_return;
+
+      gnubuckets = get_hash_table_data (abfd, ngnubuckets, 4, filesize);
+      if (gnubuckets == NULL)
+	goto error_return;
+
+      for (i = 0; i < ngnubuckets; i++)
+	if (gnubuckets[i] != 0)
+	  {
+	    if (gnubuckets[i] < gnusymidx)
+	      goto error_return;
+
+	    if (maxchain == 0xffffffff || gnubuckets[i] > maxchain)
+	      maxchain = gnubuckets[i];
+	  }
+
+      if (maxchain == 0xffffffff)
+	{
+	  symcount = 0;
+	  goto empty_gnu_hash;
+	}
+
+      maxchain -= gnusymidx;
+      filepos = offset_from_vma (phdrs, phnum,
+				 (buckets_vma +
+				  4 * (ngnubuckets + maxchain)),
+				 4, NULL);
+      if (filepos == (file_ptr) -1
+	  || bfd_seek (abfd, filepos, SEEK_SET) != 0)
+	goto error_return;
+
+      do
+	{
+	  if (bfd_bread (nb, 4, abfd) != 4)
+	    goto error_return;
+	  ++maxchain;
+	  if (maxchain == 0)
+	    goto error_return;
+	}
+      while ((bfd_get_32 (abfd, nb) & 1) == 0);
+
+      filepos = offset_from_vma (phdrs, phnum,
+				 (buckets_vma + 4 * ngnubuckets),
+				 4, NULL);
+      if (filepos == (file_ptr) -1
+	  || bfd_seek (abfd, filepos, SEEK_SET) != 0)
+	goto error_return;
+
+      gnuchains = get_hash_table_data (abfd, maxchain, 4, filesize);
+      if (gnubuckets == NULL)
+	goto error_return;
+      ngnuchains = maxchain;
+
+      if (dt_mips_xhash)
+	{
+	  filepos = offset_from_vma (phdrs, phnum,
+				     (buckets_vma
+				      + 4 * (ngnubuckets + maxchain)),
+				     4, NULL);
+	  if (filepos == (file_ptr) -1
+	      || bfd_seek (abfd, filepos, SEEK_SET) != 0)
+	    goto error_return;
+
+	  mipsxlat = get_hash_table_data (abfd, maxchain, 4, filesize);
+	  if (mipsxlat == NULL)
+	    goto error_return;
+	}
+
+      symcount = 0;
+      for (i = 0; i < ngnubuckets; ++i)
+	if (gnubuckets[i] != 0)
+	  {
+	    bfd_vma si = gnubuckets[i];
+	    bfd_vma off = si - gnusymidx;
+	    do
+	      {
+		if (mipsxlat)
+		  {
+		    if (mipsxlat[off] >= symcount)
+		      symcount = mipsxlat[off] + 1;
+		  }
+		else
+		  {
+		    if (si >= symcount)
+		      symcount = si + 1;
+		  }
+		si++;
+	      }
+	    while (off < ngnuchains && (gnuchains[off++] & 1) == 0);
+	  }
+    }
+
+  /* Swap in dynamic symbol table.  */
+  if (_bfd_mul_overflow (symcount, extsym_size, &amt))
+    {
+      bfd_set_error (bfd_error_file_too_big);
+      goto error_return;
+    }
+
+  filepos = offset_from_vma (phdrs, phnum, dt_symtab, amt, NULL);
+  if (filepos == (file_ptr) -1
+      || bfd_seek (abfd, filepos, SEEK_SET) != 0)
+    goto error_return;
+  esymbuf = _bfd_malloc_and_read (abfd, amt, amt);
+  if (esymbuf == NULL)
+    goto error_return;
+
+  if (_bfd_mul_overflow (symcount, sizeof (Elf_Internal_Sym), &amt))
+    {
+      bfd_set_error (bfd_error_file_too_big);
+      goto error_return;
+    }
+
+  /* Dynamic symbol table must be valid until ABFD is closed.  */
+  isymbuf = (Elf_Internal_Sym *) bfd_alloc (abfd, amt);
+  if (isymbuf == NULL)
+    goto error_return;
+
+  swap_symbol_in = bed->s->swap_symbol_in;
+
+  /* Convert the symbols to internal form.  */
+  isymend = isymbuf + symcount;
+  for (esym = esymbuf, isym = isymbuf;
+       isym < isymend;
+       esym += extsym_size, isym++)
+    if (!swap_symbol_in (abfd, esym, NULL, isym)
+	|| isym->st_name >= dt_strsz)
+      {
+	bfd_set_error (bfd_error_invalid_operation);
+	goto error_return;
+      }
+
+  if (dt_versym)
+    {
+      /* Swap in DT_VERSYM.  */
+      if (_bfd_mul_overflow (symcount, 2, &amt))
+	{
+	  bfd_set_error (bfd_error_file_too_big);
+	  goto error_return;
+	}
+
+      filepos = offset_from_vma (phdrs, phnum, dt_versym, amt, NULL);
+      if (filepos == (file_ptr) -1
+	  || bfd_seek (abfd, filepos, SEEK_SET) != 0)
+	goto error_return;
+
+      /* DT_VERSYM info must be valid until ABFD is closed.  */
+      versym = _bfd_alloc_and_read (abfd, amt, amt);
+
+      if (dt_verdef)
+	{
+	  /* Read in DT_VERDEF.  */
+	  filepos = offset_from_vma (phdrs, phnum, dt_verdef,
+				     0, &verdef_size);
+	  if (filepos == (file_ptr) -1
+	      || bfd_seek (abfd, filepos, SEEK_SET) != 0)
+	    goto error_return;
+
+	  /* DT_VERDEF info must be valid until ABFD is closed.  */
+	  verdef = _bfd_alloc_and_read (abfd, verdef_size,
+					verdef_size);
+	}
+
+      if (dt_verneed)
+	{
+	  /* Read in DT_VERNEED.  */
+	  filepos = offset_from_vma (phdrs, phnum, dt_verneed,
+				     0, &verneed_size);
+	  if (filepos == (file_ptr) -1
+	      || bfd_seek (abfd, filepos, SEEK_SET) != 0)
+	    goto error_return;
+
+	  /* DT_VERNEED info must be valid until ABFD is closed.  */
+	  verneed = _bfd_alloc_and_read (abfd, verneed_size,
+					 verneed_size);
+	}
+    }
+
+ empty_gnu_hash:
+  elf_tdata (abfd)->dt_strtab = strbuf;
+  elf_tdata (abfd)->dt_symtab = isymbuf;
+  elf_tdata (abfd)->dt_symtab_count = symcount;
+  elf_tdata (abfd)->dt_versym = versym;
+  elf_tdata (abfd)->dt_verdef = verdef;
+  elf_tdata (abfd)->dt_verneed = verneed;
+  elf_tdata (abfd)->dt_verdef_count
+    = verdef_size / sizeof (Elf_External_Verdef);
+  elf_tdata (abfd)->dt_verneed_count
+    = verneed_size / sizeof (Elf_External_Verneed);
+
+  res = true;
+
+ error_return:
+  /* Restore file position for elf_object_p.  */
+  if (bfd_seek (abfd, saved_filepos, SEEK_SET) != 0)
+    res = false;
+  free (dynbuf);
+  free (esymbuf);
+  free (gnubuckets);
+  free (gnuchains);
+  free (mipsxlat);
+  return res;
+}
+
+/* Reconstruct section from dynamic symbol.  */
+
+asection *
+_bfd_elf_get_section_from_dynamic_symbol (bfd *abfd,
+					  Elf_Internal_Sym *isym)
+{
+  asection *sec;
+  flagword flags;
+
+  if (!elf_use_dt_symtab_p (abfd))
+    return NULL;
+
+  flags = SEC_ALLOC | SEC_LOAD;
+  switch (ELF_ST_TYPE (isym->st_info))
+    {
+    case STT_FUNC:
+    case STT_GNU_IFUNC:
+      sec = bfd_get_section_by_name (abfd, ".text");
+      if (sec == NULL)
+	sec = bfd_make_section_with_flags (abfd,
+					   ".text",
+					   flags | SEC_CODE);
+      break;
+    case STT_COMMON:
+      sec = bfd_com_section_ptr;
+      break;
+    case STT_OBJECT:
+      sec = bfd_get_section_by_name (abfd, ".data");
+      if (sec == NULL)
+	sec = bfd_make_section_with_flags (abfd,
+					   ".data",
+					   flags | SEC_DATA);
+      break;
+    case STT_TLS:
+      sec = bfd_get_section_by_name (abfd, ".tdata");
+      if (sec == NULL)
+	sec = bfd_make_section_with_flags (abfd,
+					   ".tdata",
+					   (flags
+					    | SEC_DATA
+					    | SEC_THREAD_LOCAL));
+      break;
+    default:
+      sec = bfd_abs_section_ptr;
+      break;
+    }
+
+  return sec;
+}
+
 /* Get version name.  If BASE_P is TRUE, return "Base" for VER_FLG_BASE
    and return symbol version for symbol version itself.   */
 
@@ -1882,8 +2438,11 @@  _bfd_elf_get_symbol_version_string (bfd *abfd, asymbol *symbol,
 				    bool *hidden)
 {
   const char *version_string = NULL;
-  if (elf_dynversym (abfd) != 0
-      && (elf_dynverdef (abfd) != 0 || elf_dynverref (abfd) != 0))
+  if ((elf_dynversym (abfd) != 0
+       && (elf_dynverdef (abfd) != 0 || elf_dynverref (abfd) != 0))
+      || (elf_tdata (abfd)->dt_versym != NULL
+	  && (elf_tdata (abfd)->dt_verdef != NULL
+	      || elf_tdata (abfd)->dt_verneed != NULL)))
     {
       unsigned int vernum = ((elf_symbol_type *) symbol)->version;
 
@@ -8609,6 +9168,11 @@  _bfd_elf_get_dynamic_symtab_upper_bound (bfd *abfd)
 
   if (elf_dynsymtab (abfd) == 0)
     {
+      /* Check if there is dynamic symbol table.  */
+      symcount = elf_tdata (abfd)->dt_symtab_count;
+      if (symcount)
+	goto compute_symtab_size;
+
       bfd_set_error (bfd_error_invalid_operation);
       return -1;
     }
@@ -8619,6 +9183,8 @@  _bfd_elf_get_dynamic_symtab_upper_bound (bfd *abfd)
       bfd_set_error (bfd_error_file_too_big);
       return -1;
     }
+
+ compute_symtab_size:
   symtab_size = symcount * (sizeof (asymbol *));
   if (symcount == 0)
     symtab_size = sizeof (asymbol *);
@@ -8826,35 +9392,51 @@  _bfd_elf_slurp_version_tables (bfd *abfd, bool default_imported_symver)
   unsigned int freeidx = 0;
   size_t amt;
 
-  if (elf_dynverref (abfd) != 0)
+  if (elf_dynverref (abfd) != 0 || elf_tdata (abfd)->dt_verneed != NULL)
     {
       Elf_Internal_Shdr *hdr;
       Elf_External_Verneed *everneed;
       Elf_Internal_Verneed *iverneed;
       unsigned int i;
       bfd_byte *contents_end;
+      size_t verneed_count;
+      size_t verneed_size;
 
-      hdr = &elf_tdata (abfd)->dynverref_hdr;
-
-      if (hdr->sh_info > hdr->sh_size / sizeof (Elf_External_Verneed))
+      if (elf_tdata (abfd)->dt_verneed != NULL)
 	{
-	error_return_bad_verref:
-	  _bfd_error_handler
-	    (_("%pB: .gnu.version_r invalid entry"), abfd);
-	  bfd_set_error (bfd_error_bad_value);
-	error_return_verref:
-	  elf_tdata (abfd)->verref = NULL;
-	  elf_tdata (abfd)->cverrefs = 0;
-	  goto error_return;
+	  hdr = NULL;
+	  contents = elf_tdata (abfd)->dt_verneed;
+	  verneed_count = elf_tdata (abfd)->dt_verneed_count;
+	  verneed_size = verneed_count * sizeof (Elf_External_Verneed);
 	}
+      else
+	{
+	  hdr = &elf_tdata (abfd)->dynverref_hdr;
 
-      if (bfd_seek (abfd, hdr->sh_offset, SEEK_SET) != 0)
-	goto error_return_verref;
-      contents = _bfd_malloc_and_read (abfd, hdr->sh_size, hdr->sh_size);
-      if (contents == NULL)
-	goto error_return_verref;
+	  if (hdr->sh_info > hdr->sh_size / sizeof (Elf_External_Verneed))
+	    {
+	    error_return_bad_verref:
+	      _bfd_error_handler
+		(_("%pB: .gnu.version_r invalid entry"), abfd);
+	      bfd_set_error (bfd_error_bad_value);
+	    error_return_verref:
+	      elf_tdata (abfd)->verref = NULL;
+	      elf_tdata (abfd)->cverrefs = 0;
+	      goto error_return;
+	    }
+
+	  if (bfd_seek (abfd, hdr->sh_offset, SEEK_SET) != 0)
+	    goto error_return_verref;
+	  contents = _bfd_malloc_and_read (abfd, hdr->sh_size, hdr->sh_size);
+	  if (contents == NULL)
+	    goto error_return_verref;
+
+	  verneed_size = hdr->sh_size;
+	  verneed_count = hdr->sh_info;
+	}
 
-      if (_bfd_mul_overflow (hdr->sh_info, sizeof (Elf_Internal_Verneed), &amt))
+      if (_bfd_mul_overflow (verneed_count,
+			     sizeof (Elf_Internal_Verneed), &amt))
 	{
 	  bfd_set_error (bfd_error_file_too_big);
 	  goto error_return_verref;
@@ -8867,10 +9449,11 @@  _bfd_elf_slurp_version_tables (bfd *abfd, bool default_imported_symver)
 
       BFD_ASSERT (sizeof (Elf_External_Verneed)
 		  == sizeof (Elf_External_Vernaux));
-      contents_end = contents + hdr->sh_size - sizeof (Elf_External_Verneed);
+      contents_end = (contents + verneed_size
+		      - sizeof (Elf_External_Verneed));
       everneed = (Elf_External_Verneed *) contents;
       iverneed = elf_tdata (abfd)->verref;
-      for (i = 0; i < hdr->sh_info; i++, iverneed++)
+      for (i = 0; i < verneed_count; i++, iverneed++)
 	{
 	  Elf_External_Vernaux *evernaux;
 	  Elf_Internal_Vernaux *ivernaux;
@@ -8880,9 +9463,13 @@  _bfd_elf_slurp_version_tables (bfd *abfd, bool default_imported_symver)
 
 	  iverneed->vn_bfd = abfd;
 
-	  iverneed->vn_filename =
-	    bfd_elf_string_from_elf_section (abfd, hdr->sh_link,
-					     iverneed->vn_file);
+	  if (elf_use_dt_symtab_p (abfd))
+	    iverneed->vn_filename
+	      = elf_tdata (abfd)->dt_strtab + iverneed->vn_file;
+	  else
+	    iverneed->vn_filename
+	      = bfd_elf_string_from_elf_section (abfd, hdr->sh_link,
+						 iverneed->vn_file);
 	  if (iverneed->vn_filename == NULL)
 	    goto error_return_bad_verref;
 
@@ -8913,9 +9500,13 @@  _bfd_elf_slurp_version_tables (bfd *abfd, bool default_imported_symver)
 	    {
 	      _bfd_elf_swap_vernaux_in (abfd, evernaux, ivernaux);
 
-	      ivernaux->vna_nodename =
-		bfd_elf_string_from_elf_section (abfd, hdr->sh_link,
-						 ivernaux->vna_name);
+	      if (elf_use_dt_symtab_p (abfd))
+		ivernaux->vna_nodename
+		  = elf_tdata (abfd)->dt_strtab + ivernaux->vna_name;
+	      else
+		ivernaux->vna_nodename
+		  = bfd_elf_string_from_elf_section (abfd, hdr->sh_link,
+						     ivernaux->vna_name);
 	      if (ivernaux->vna_nodename == NULL)
 		goto error_return_bad_verref;
 
@@ -8954,11 +9545,12 @@  _bfd_elf_slurp_version_tables (bfd *abfd, bool default_imported_symver)
 	}
       elf_tdata (abfd)->cverrefs = i;
 
-      free (contents);
+      if (elf_tdata (abfd)->dt_verneed == NULL)
+	free (contents);
       contents = NULL;
     }
 
-  if (elf_dynverdef (abfd) != 0)
+  if (elf_dynverdef (abfd) != 0 || elf_tdata (abfd)->dt_verdef != NULL)
     {
       Elf_Internal_Shdr *hdr;
       Elf_External_Verdef *everdef;
@@ -8968,40 +9560,56 @@  _bfd_elf_slurp_version_tables (bfd *abfd, bool default_imported_symver)
       unsigned int i;
       unsigned int maxidx;
       bfd_byte *contents_end_def, *contents_end_aux;
+      size_t verdef_count;
+      size_t verdef_size;
 
-      hdr = &elf_tdata (abfd)->dynverdef_hdr;
-
-      if (hdr->sh_size < sizeof (Elf_External_Verdef))
+      if (elf_tdata (abfd)->dt_verdef != NULL)
 	{
-	error_return_bad_verdef:
-	  _bfd_error_handler
-	    (_("%pB: .gnu.version_d invalid entry"), abfd);
-	  bfd_set_error (bfd_error_bad_value);
-	error_return_verdef:
-	  elf_tdata (abfd)->verdef = NULL;
-	  elf_tdata (abfd)->cverdefs = 0;
-	  goto error_return;
+	  hdr = NULL;
+	  contents = elf_tdata (abfd)->dt_verdef;
+	  verdef_count = elf_tdata (abfd)->dt_verdef_count;
+	  verdef_size = verdef_count * sizeof (Elf_External_Verdef);
 	}
+      else
+	{
+	  hdr = &elf_tdata (abfd)->dynverdef_hdr;
 
-      if (bfd_seek (abfd, hdr->sh_offset, SEEK_SET) != 0)
-	goto error_return_verdef;
-      contents = _bfd_malloc_and_read (abfd, hdr->sh_size, hdr->sh_size);
-      if (contents == NULL)
-	goto error_return_verdef;
+	  if (hdr->sh_size < sizeof (Elf_External_Verdef))
+	    {
+	    error_return_bad_verdef:
+	      _bfd_error_handler
+		(_("%pB: .gnu.version_d invalid entry"), abfd);
+	      bfd_set_error (bfd_error_bad_value);
+	    error_return_verdef:
+	      elf_tdata (abfd)->verdef = NULL;
+	      elf_tdata (abfd)->cverdefs = 0;
+	      goto error_return;
+	    }
+
+	  if (bfd_seek (abfd, hdr->sh_offset, SEEK_SET) != 0)
+	    goto error_return_verdef;
+	  contents = _bfd_malloc_and_read (abfd, hdr->sh_size, hdr->sh_size);
+	  if (contents == NULL)
+	    goto error_return_verdef;
 
-      BFD_ASSERT (sizeof (Elf_External_Verdef)
-		  >= sizeof (Elf_External_Verdaux));
-      contents_end_def = contents + hdr->sh_size
-			 - sizeof (Elf_External_Verdef);
-      contents_end_aux = contents + hdr->sh_size
-			 - sizeof (Elf_External_Verdaux);
+	  BFD_ASSERT (sizeof (Elf_External_Verdef)
+		      >= sizeof (Elf_External_Verdaux));
+
+	  verdef_count = hdr->sh_info;
+	  verdef_size = hdr->sh_size;
+	}
+
+      contents_end_def = (contents + verdef_size
+			  - sizeof (Elf_External_Verdef));
+      contents_end_aux = (contents + verdef_size
+			  - sizeof (Elf_External_Verdaux));
 
       /* We know the number of entries in the section but not the maximum
 	 index.  Therefore we have to run through all entries and find
 	 the maximum.  */
       everdef = (Elf_External_Verdef *) contents;
       maxidx = 0;
-      for (i = 0; i < hdr->sh_info; ++i)
+      for (i = 0; i < verdef_count; ++i)
 	{
 	  _bfd_elf_swap_verdef_in (abfd, everdef, &iverdefmem);
 
@@ -9044,7 +9652,7 @@  _bfd_elf_slurp_version_tables (bfd *abfd, bool default_imported_symver)
 
       everdef = (Elf_External_Verdef *) contents;
       iverdefarr = elf_tdata (abfd)->verdef;
-      for (i = 0; i < hdr->sh_info; i++)
+      for (i = 0; i < verdef_count; ++i)
 	{
 	  Elf_External_Verdaux *everdaux;
 	  Elf_Internal_Verdaux *iverdaux;
@@ -9087,9 +9695,13 @@  _bfd_elf_slurp_version_tables (bfd *abfd, bool default_imported_symver)
 	    {
 	      _bfd_elf_swap_verdaux_in (abfd, everdaux, iverdaux);
 
-	      iverdaux->vda_nodename =
-		bfd_elf_string_from_elf_section (abfd, hdr->sh_link,
-						 iverdaux->vda_name);
+	      if (elf_use_dt_symtab_p (abfd))
+		iverdaux->vda_nodename
+		  = elf_tdata (abfd)->dt_strtab + iverdaux->vda_name;
+	      else
+		iverdaux->vda_nodename
+		  = bfd_elf_string_from_elf_section (abfd, hdr->sh_link,
+						     iverdaux->vda_name);
 	      if (iverdaux->vda_nodename == NULL)
 		goto error_return_bad_verdef;
 
@@ -9124,7 +9736,8 @@  _bfd_elf_slurp_version_tables (bfd *abfd, bool default_imported_symver)
 		     ((bfd_byte *) everdef + iverdef->vd_next));
 	}
 
-      free (contents);
+      if (elf_tdata (abfd)->dt_verdef == NULL)
+	free (contents);
       contents = NULL;
     }
   else if (default_imported_symver)
diff --git a/bfd/elfcode.h b/bfd/elfcode.h
index 495e498838d..aae66bcebf8 100644
--- a/bfd/elfcode.h
+++ b/bfd/elfcode.h
@@ -839,6 +839,21 @@  elf_object_p (bfd *abfd)
 		  abfd->read_only = 1;
 		}
 	    }
+	  if (i_phdr->p_filesz != 0)
+	    {
+	      if ((i_phdr->p_offset + i_phdr->p_filesz) > filesize)
+		goto got_no_match;
+	      /* Try to reconstruct dynamic symbol table from PT_DYNAMIC
+		 segment if there is no section header.  */
+	      if (i_phdr->p_type == PT_DYNAMIC
+		  && i_ehdrp->e_shstrndx == 0
+		  && i_ehdrp->e_shoff == 0
+		  && !_bfd_elf_get_dynamic_symbols (abfd, i_phdr,
+						    elf_tdata (abfd)->phdr,
+						    i_ehdrp->e_phnum,
+						    filesize))
+		goto got_no_match;
+	    }
 	}
     }
 
@@ -1245,7 +1260,9 @@  elf_slurp_symbol_table (bfd *abfd, asymbol **symptrs, bool dynamic)
       if ((elf_dynverdef (abfd) != 0
 	   && elf_tdata (abfd)->verdef == NULL)
 	  || (elf_dynverref (abfd) != 0
-	      && elf_tdata (abfd)->verref == NULL))
+	      && elf_tdata (abfd)->verref == NULL)
+	  || elf_tdata (abfd)->dt_verdef != NULL
+	  || elf_tdata (abfd)->dt_verneed != NULL)
 	{
 	  if (!_bfd_elf_slurp_version_tables (abfd, false))
 	    return -1;
@@ -1253,11 +1270,15 @@  elf_slurp_symbol_table (bfd *abfd, asymbol **symptrs, bool dynamic)
     }
 
   ebd = get_elf_backend_data (abfd);
-  symcount = hdr->sh_size / sizeof (Elf_External_Sym);
+  symcount = elf_tdata (abfd)->dt_symtab_count;
+  if (symcount == 0)
+    symcount = hdr->sh_size / sizeof (Elf_External_Sym);
   if (symcount == 0)
     sym = symbase = NULL;
   else
     {
+      size_t i;
+
       isymbuf = bfd_elf_get_elf_syms (abfd, hdr, symcount, 0,
 				      NULL, NULL, NULL);
       if (isymbuf == NULL)
@@ -1304,12 +1325,18 @@  elf_slurp_symbol_table (bfd *abfd, asymbol **symptrs, bool dynamic)
       if (xver != NULL)
 	++xver;
       isymend = isymbuf + symcount;
-      for (isym = isymbuf + 1, sym = symbase; isym < isymend; isym++, sym++)
+      for (isym = isymbuf + 1, sym = symbase, i = 1;
+	   isym < isymend;
+	   isym++, sym++, i++)
 	{
 	  memcpy (&sym->internal_elf_sym, isym, sizeof (Elf_Internal_Sym));
 
 	  sym->symbol.the_bfd = abfd;
-	  sym->symbol.name = bfd_elf_sym_name (abfd, hdr, isym, NULL);
+	  if (elf_use_dt_symtab_p (abfd))
+	    sym->symbol.name = (elf_tdata (abfd)->dt_strtab
+				+ isym->st_name);
+	  else
+	    sym->symbol.name = bfd_elf_sym_name (abfd, hdr, isym, NULL);
 	  sym->symbol.value = isym->st_value;
 
 	  if (isym->st_shndx == SHN_UNDEF)
@@ -1343,6 +1370,15 @@  elf_slurp_symbol_table (bfd *abfd, asymbol **symptrs, bool dynamic)
 		 moment) about the alignment.  */
 	      sym->symbol.value = isym->st_size;
 	    }
+	  else if (elf_use_dt_symtab_p (abfd))
+	    {
+	      asection *sec;
+	      sec = _bfd_elf_get_section_from_dynamic_symbol (abfd,
+							      isym);
+	      if (sec == NULL)
+		goto error_return;
+	      sym->symbol.section = sec;
+	    }
 	  else
 	    {
 	      sym->symbol.section
@@ -1423,7 +1459,10 @@  elf_slurp_symbol_table (bfd *abfd, asymbol **symptrs, bool dynamic)
 	  if (dynamic)
 	    sym->symbol.flags |= BSF_DYNAMIC;
 
-	  if (xver != NULL)
+	  if (elf_tdata (abfd)->dt_versym)
+	    sym->version = bfd_get_16 (abfd,
+				       elf_tdata (abfd)->dt_versym + 2 * i);
+	  else if (xver != NULL)
 	    {
 	      Elf_Internal_Versym iversym;
 
@@ -1461,13 +1500,15 @@  elf_slurp_symbol_table (bfd *abfd, asymbol **symptrs, bool dynamic)
     }
 
   free (xverbuf);
-  if (hdr->contents != (unsigned char *) isymbuf)
+  if (hdr->contents != (unsigned char *) isymbuf
+      && !elf_use_dt_symtab_p (abfd))
     free (isymbuf);
   return symcount;
 
  error_return:
   free (xverbuf);
-  if (hdr->contents != (unsigned char *) isymbuf)
+  if (hdr->contents != (unsigned char *) isymbuf
+      && !elf_use_dt_symtab_p (abfd))
     free (isymbuf);
   return -1;
 }
diff --git a/bfd/elflink.c b/bfd/elflink.c
index f10faa5f8bd..422c7b25c81 100644
--- a/bfd/elflink.c
+++ b/bfd/elflink.c
@@ -3571,6 +3571,12 @@  elf_link_is_defined_archive_symbol (bfd * abfd, carsym * symdef)
   if (! bfd_check_format (abfd, bfd_object))
     return false;
 
+  if (elf_use_dt_symtab_p (abfd))
+    {
+      bfd_set_error (bfd_error_wrong_format);
+      return false;
+    }
+
   /* Select the appropriate symbol table.  If we don't know if the
      object file is an IR object, give linker LTO plugin a chance to
      get the correct symbol table.  */
@@ -4233,6 +4239,12 @@  elf_link_add_object_symbols (bfd *abfd, struct bfd_link_info *info)
   htab = elf_hash_table (info);
   bed = get_elf_backend_data (abfd);
 
+  if (elf_use_dt_symtab_p (abfd))
+    {
+      bfd_set_error (bfd_error_wrong_format);
+      return false;
+    }
+
   if ((abfd->flags & DYNAMIC) == 0)
     dynamic = false;
   else