]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: support DT_GNU_HASH in VDSO
authorAustin Clements <austin@google.com>
Tue, 13 Jun 2017 12:52:53 +0000 (08:52 -0400)
committerAustin Clements <austin@google.com>
Sun, 13 Aug 2017 22:08:55 +0000 (22:08 +0000)
Currently we only support finding symbols in the VDSO using the old
DT_HASH. These days everything uses DT_GNU_HASH instead. To keep up
with the times and future-proof against DT_HASH disappearing from the
VDSO in the future, this commit adds support for DT_GNU_HASH and
prefers it over DT_HASH.

Tested by making sure it found a DT_GNU_HASH section and all of the
expected symbols in it, and then disabling the DT_GNU_HASH path and
making sure the old DT_HASH path still found all of the symbols.

Fixes #19649.

Change-Id: I508c8b35a019330d2c32f04f3833b69cb2686f13
Reviewed-on: https://go-review.googlesource.com/45511
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
src/runtime/vdso_linux_amd64.go

index 8a970dfbe6d631f2101af112b2b473cd9213f593..37736b102803635407ea19911797e91a7827e357 100644 (file)
@@ -23,12 +23,13 @@ const (
        _PT_LOAD    = 1 /* Loadable program segment */
        _PT_DYNAMIC = 2 /* Dynamic linking information */
 
-       _DT_NULL   = 0 /* Marks end of dynamic section */
-       _DT_HASH   = 4 /* Dynamic symbol hash table */
-       _DT_STRTAB = 5 /* Address of string table */
-       _DT_SYMTAB = 6 /* Address of symbol table */
-       _DT_VERSYM = 0x6ffffff0
-       _DT_VERDEF = 0x6ffffffc
+       _DT_NULL     = 0          /* Marks end of dynamic section */
+       _DT_HASH     = 4          /* Dynamic symbol hash table */
+       _DT_STRTAB   = 5          /* Address of string table */
+       _DT_SYMTAB   = 6          /* Address of symbol table */
+       _DT_GNU_HASH = 0x6ffffef5 /* GNU-style dynamic symbol hash table */
+       _DT_VERSYM   = 0x6ffffff0
+       _DT_VERDEF   = 0x6ffffffc
 
        _VER_FLG_BASE = 0x1 /* Version definition of file itself */
 
@@ -126,6 +127,7 @@ type elf64Auxv struct {
 type symbol_key struct {
        name     string
        sym_hash uint32
+       gnu_hash uint32
        ptr      *uintptr
 }
 
@@ -146,6 +148,8 @@ type vdso_info struct {
        symstrings *[1 << 32]byte
        chain      []uint32
        bucket     []uint32
+       symOff     uint32
+       isGNUHash  bool
 
        /* Version table */
        versym *[1 << 32]uint16
@@ -155,9 +159,9 @@ type vdso_info struct {
 var linux26 = version_key{"LINUX_2.6", 0x3ae75f6}
 
 var sym_keys = []symbol_key{
-       {"__vdso_time", 0xa33c485, &__vdso_time_sym},
-       {"__vdso_gettimeofday", 0x315ca59, &__vdso_gettimeofday_sym},
-       {"__vdso_clock_gettime", 0xd35ec75, &__vdso_clock_gettime_sym},
+       {"__vdso_time", 0xa33c485, 0x821e8e0d, &__vdso_time_sym},
+       {"__vdso_gettimeofday", 0x315ca59, 0xb01bca00, &__vdso_gettimeofday_sym},
+       {"__vdso_clock_gettime", 0xd35ec75, 0x6e43a318, &__vdso_clock_gettime_sym},
 }
 
 // initialize with vsyscall fallbacks
@@ -197,8 +201,7 @@ func vdso_init_from_sysinfo_ehdr(info *vdso_info, hdr *elf64Ehdr) {
 
        // Fish out the useful bits of the dynamic table.
 
-       var hash *[1 << 30]uint32
-       hash = nil
+       var hash, gnuhash *[1 << 30]uint32
        info.symstrings = nil
        info.symtab = nil
        info.versym = nil
@@ -213,6 +216,8 @@ func vdso_init_from_sysinfo_ehdr(info *vdso_info, hdr *elf64Ehdr) {
                        info.symtab = (*[1 << 32]elf64Sym)(unsafe.Pointer(p))
                case _DT_HASH:
                        hash = (*[1 << 30]uint32)(unsafe.Pointer(p))
+               case _DT_GNU_HASH:
+                       gnuhash = (*[1 << 30]uint32)(unsafe.Pointer(p))
                case _DT_VERSYM:
                        info.versym = (*[1 << 32]uint16)(unsafe.Pointer(p))
                case _DT_VERDEF:
@@ -220,7 +225,7 @@ func vdso_init_from_sysinfo_ehdr(info *vdso_info, hdr *elf64Ehdr) {
                }
        }
 
-       if info.symstrings == nil || info.symtab == nil || hash == nil {
+       if info.symstrings == nil || info.symtab == nil || (hash == nil && gnuhash == nil) {
                return // Failed
        }
 
@@ -228,11 +233,21 @@ func vdso_init_from_sysinfo_ehdr(info *vdso_info, hdr *elf64Ehdr) {
                info.versym = nil
        }
 
-       // Parse the hash table header.
-       nbucket := hash[0]
-       nchain := hash[1]
-       info.bucket = hash[2 : 2+nbucket]
-       info.chain = hash[2+nbucket : 2+nbucket+nchain]
+       if gnuhash != nil {
+               // Parse the GNU hash table header.
+               nbucket := gnuhash[0]
+               info.symOff = gnuhash[1]
+               bloomSize := gnuhash[2]
+               info.bucket = gnuhash[4+bloomSize*2:][:nbucket]
+               info.chain = gnuhash[4+bloomSize*2+nbucket:]
+               info.isGNUHash = true
+       } else {
+               // Parse the hash table header.
+               nbucket := hash[0]
+               nchain := hash[1]
+               info.bucket = hash[2 : 2+nbucket]
+               info.chain = hash[2+nbucket : 2+nbucket+nchain]
+       }
 
        // That's all we need.
        info.valid = true
@@ -266,25 +281,56 @@ func vdso_parse_symbols(info *vdso_info, version int32) {
                return
        }
 
-       for _, k := range sym_keys {
-               for chain := info.bucket[k.sym_hash%uint32(len(info.bucket))]; chain != 0; chain = info.chain[chain] {
-                       sym := &info.symtab[chain]
-                       typ := _ELF64_ST_TYPE(sym.st_info)
-                       bind := _ELF64_ST_BIND(sym.st_info)
-                       if typ != _STT_FUNC || bind != _STB_GLOBAL && bind != _STB_WEAK || sym.st_shndx == _SHN_UNDEF {
-                               continue
-                       }
-                       if k.name != gostringnocopy(&info.symstrings[sym.st_name]) {
-                               continue
-                       }
+       apply := func(symIndex uint32, k symbol_key) bool {
+               sym := &info.symtab[symIndex]
+               typ := _ELF64_ST_TYPE(sym.st_info)
+               bind := _ELF64_ST_BIND(sym.st_info)
+               if typ != _STT_FUNC || bind != _STB_GLOBAL && bind != _STB_WEAK || sym.st_shndx == _SHN_UNDEF {
+                       return false
+               }
+               if k.name != gostringnocopy(&info.symstrings[sym.st_name]) {
+                       return false
+               }
+
+               // Check symbol version.
+               if info.versym != nil && version != 0 && int32(info.versym[symIndex]&0x7fff) != version {
+                       return false
+               }
+
+               *k.ptr = info.load_offset + uintptr(sym.st_value)
+               return true
+       }
 
-                       // Check symbol version.
-                       if info.versym != nil && version != 0 && int32(info.versym[chain]&0x7fff) != version {
-                               continue
+       if !info.isGNUHash {
+               // Old-style DT_HASH table.
+               for _, k := range sym_keys {
+                       for chain := info.bucket[k.sym_hash%uint32(len(info.bucket))]; chain != 0; chain = info.chain[chain] {
+                               if apply(chain, k) {
+                                       break
+                               }
                        }
+               }
+               return
+       }
 
-                       *k.ptr = info.load_offset + uintptr(sym.st_value)
-                       break
+       // New-style DT_GNU_HASH table.
+       for _, k := range sym_keys {
+               symIndex := info.bucket[k.gnu_hash%uint32(len(info.bucket))]
+               if symIndex < info.symOff {
+                       continue
+               }
+               for ; ; symIndex++ {
+                       hash := info.chain[symIndex-info.symOff]
+                       if hash|1 == k.gnu_hash|1 {
+                               // Found a hash match.
+                               if apply(symIndex, k) {
+                                       break
+                               }
+                       }
+                       if hash&1 != 0 {
+                               // End of chain.
+                               break
+                       }
                }
        }
 }