From 3e6334e2e059505aa3ebd16185b03f7a9cc88d23 Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Wed, 23 Sep 2015 15:46:00 +1200 Subject: [PATCH] cmd/link: set the ELF headers of ARM executables that use cgo correctly It is generally expected that the ELF flags of a dynamically linked executable and the libraries it links against match. Go's linker currently always produces executables with flags that do not declare a float abi (hard, soft) at all, but when cgo is involved it is unlikely that this matches the system libraries being linked against -- really the decision about ABI is made by the C compiler during the invocation of cgo. This change is basically a port of the code from binutils that parses the ".ARM.attributes" section to check for the tag that declares that the code is built for the hard-float ABI. Fixes #7094 Change-Id: I737c8f3b5ed4af545cfc3e86722d03eb83083402 Reviewed-on: https://go-review.googlesource.com/14860 Run-TryBot: Michael Hudson-Doyle TryBot-Result: Gobot Gobot Reviewed-by: Minux Ma --- src/cmd/link/internal/ld/elf.go | 11 ++- src/cmd/link/internal/ld/ldelf.go | 136 ++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go index 8287d2714e..19865a15bf 100644 --- a/src/cmd/link/internal/ld/elf.go +++ b/src/cmd/link/internal/ld/elf.go @@ -206,6 +206,7 @@ const ( SHT_GNU_VERNEED = 0x6ffffffe SHT_GNU_VERSYM = 0x6fffffff SHT_LOPROC = 0x70000000 + SHT_ARM_ATTRIBUTES = 0x70000003 SHT_HIPROC = 0x7fffffff SHT_LOUSER = 0x80000000 SHT_HIUSER = 0xffffffff @@ -776,11 +777,19 @@ func Elfinit() { ehdr.phentsize = ELF64PHDRSIZE /* Must be ELF64PHDRSIZE */ ehdr.shentsize = ELF64SHDRSIZE /* Must be ELF64SHDRSIZE */ - // we use EABI on both linux/arm and freebsd/arm. + // we use EABI on both linux/arm and freebsd/arm. // 32-bit architectures case '5': // we use EABI on both linux/arm and freebsd/arm. if HEADTYPE == obj.Hlinux || HEADTYPE == obj.Hfreebsd { + // We set a value here that makes no indication of which + // float ABI the object uses, because this is information + // used by the dynamic linker to compare executables and + // shared libraries -- so it only matters for cgo calls, and + // the information properly comes from the object files + // produced by the host C compiler. parseArmAttributes in + // ldelf.go reads that information and updates this field as + // appropriate. ehdr.flags = 0x5000002 // has entry point, Version5 EABI } fallthrough diff --git a/src/cmd/link/internal/ld/ldelf.go b/src/cmd/link/internal/ld/ldelf.go index 3efdb75b89..20e23117ad 100644 --- a/src/cmd/link/internal/ld/ldelf.go +++ b/src/cmd/link/internal/ld/ldelf.go @@ -5,6 +5,7 @@ import ( "cmd/internal/obj" "encoding/binary" "fmt" + "io" "log" "sort" "strings" @@ -315,6 +316,135 @@ func valuecmp(a *LSym, b *LSym) int { return 0 } +const ( + Tag_file = 1 + Tag_CPU_name = 4 + Tag_CPU_raw_name = 5 + Tag_compatibility = 32 + Tag_nodefaults = 64 + Tag_also_compatible_with = 65 + Tag_ABI_VFP_args = 28 +) + +type elfAttribute struct { + tag uint64 + sval string + ival uint64 +} + +type elfAttributeList struct { + data []byte + err error +} + +func (a *elfAttributeList) string() string { + if a.err != nil { + return "" + } + nul := bytes.IndexByte(a.data, 0) + if nul < 0 { + a.err = io.EOF + return "" + } + s := string(a.data[:nul]) + a.data = a.data[nul+1:] + return s +} + +func (a *elfAttributeList) uleb128() uint64 { + if a.err != nil { + return 0 + } + v, size := binary.Uvarint(a.data) + a.data = a.data[size:] + return v +} + +// Read an elfAttribute from the list following the rules used on ARM systems. +func (a *elfAttributeList) armAttr() elfAttribute { + attr := elfAttribute{tag: a.uleb128()} + switch { + case attr.tag == Tag_compatibility: + attr.ival = a.uleb128() + attr.sval = a.string() + + case attr.tag == 64: // Tag_nodefaults has no argument + + case attr.tag == 65: // Tag_also_compatible_with + // Not really, but we don't actually care about this tag. + attr.sval = a.string() + + // Tag with string argument + case attr.tag == Tag_CPU_name || attr.tag == Tag_CPU_raw_name || (attr.tag >= 32 && attr.tag&1 != 0): + attr.sval = a.string() + + default: // Tag with integer argument + attr.ival = a.uleb128() + } + return attr +} + +func (a *elfAttributeList) done() bool { + if a.err != nil || len(a.data) == 0 { + return true + } + return false +} + +// Look for the attribute that indicates the object uses the hard-float ABI (a +// file-level attribute with tag Tag_VFP_arch and value 1). Unfortunately the +// format used means that we have to parse all of the file-level attributes to +// find the one we are looking for. This format is slightly documented in "ELF +// for the ARM Architecture" but mostly this is derived from reading the source +// to gold and readelf. +func parseArmAttributes(e binary.ByteOrder, data []byte) { + // We assume the soft-float ABI unless we see a tag indicating otherwise. + if ehdr.flags == 0x5000002 { + ehdr.flags = 0x5000202 + } + if data[0] != 'A' { + fmt.Fprintf(&Bso, ".ARM.attributes has unexpected format %c\n", data[0]) + return + } + data = data[1:] + for len(data) != 0 { + sectionlength := e.Uint32(data) + sectiondata := data[4:sectionlength] + data = data[sectionlength:] + + nulIndex := bytes.IndexByte(sectiondata, 0) + if nulIndex < 0 { + fmt.Fprintf(&Bso, "corrupt .ARM.attributes (section name not NUL-terminated)\n") + return + } + name := string(sectiondata[:nulIndex]) + sectiondata = sectiondata[nulIndex+1:] + + if name != "aeabi" { + continue + } + for len(sectiondata) != 0 { + subsectiontag, sz := binary.Uvarint(sectiondata) + subsectionsize := e.Uint32(sectiondata[sz:]) + subsectiondata := sectiondata[sz+4 : subsectionsize] + sectiondata = sectiondata[subsectionsize:] + + if subsectiontag == Tag_file { + attrList := elfAttributeList{data: subsectiondata} + for !attrList.done() { + attr := attrList.armAttr() + if attr.tag == Tag_ABI_VFP_args && attr.ival == 1 { + ehdr.flags = 0x5000402 // has entry point, Version5 EABI, hard-float ABI + } + } + if attrList.err != nil { + fmt.Fprintf(&Bso, "could not parse .ARM.attributes\n") + } + } + } + } +} + func ldelf(f *obj.Biobuf, pkg string, length int64, pn string) { if Debug['v'] != 0 { fmt.Fprintf(&Bso, "%5.2f ldelf %s\n", obj.Cputime(), pn) @@ -549,6 +679,12 @@ func ldelf(f *obj.Biobuf, pkg string, length int64, pn string) { // create symbols for elfmapped sections for i := 0; uint(i) < elfobj.nsect; i++ { sect = &elfobj.sect[i] + if sect.type_ == SHT_ARM_ATTRIBUTES && sect.name == ".ARM.attributes" { + if err = elfmap(elfobj, sect); err != nil { + goto bad + } + parseArmAttributes(e, sect.base[:sect.size]) + } if (sect.type_ != ElfSectProgbits && sect.type_ != ElfSectNobits) || sect.flags&ElfSectFlagAlloc == 0 { continue }