[elflib] Support reading build ID from sections

To handle split debug binaries we should first read the build ID from the
section and then only if the section is missing fallback to the program
header. I've added tests here for the only section case and the only
program header case but the symbolizer has more tricky tests using
both.

Change-Id: I593e2f4f9d8e3f9ab40e431ebc198ba4e8860a84
diff --git a/elflib/elflib.go b/elflib/elflib.go
index bea4142..7d5cc25 100644
--- a/elflib/elflib.go
+++ b/elflib/elflib.go
@@ -112,13 +112,29 @@
 	}
 }
 
+func getBuildIDs(filename string, endian binary.ByteOrder, data io.ReaderAt, size uint64) ([][]byte, error) {
+	noteBytes := make([]byte, size)
+	_, err := data.ReadAt(noteBytes, 0)
+	if err != nil {
+		return nil, fmt.Errorf("error parsing section header in %s: %v", filename, err)
+	}
+	out := [][]byte{}
+	err = forEachNote(noteBytes, endian, func(entry noteEntry) {
+		if entry.noteType != NT_GNU_BUILD_ID || entry.name != "GNU\000" {
+			return
+		}
+		out = append(out, entry.desc)
+	})
+	return out, err
+}
+
 func GetBuildIDs(filename string, file io.ReaderAt) ([][]byte, error) {
 	elfFile, err := elf.NewFile(file)
 	if err != nil {
 		return nil, fmt.Errorf("could not parse ELF file %s: %v", filename, err)
 	}
-	if len(elfFile.Progs) == 0 {
-		return nil, fmt.Errorf("no program headers in %s", filename)
+	if len(elfFile.Progs) == 0 && len(elfFile.Sections) == 0 {
+		return nil, fmt.Errorf("no program headers or sections in %s", filename)
 	}
 	var endian binary.ByteOrder
 	if elfFile.Data == elf.ELFDATA2LSB {
@@ -127,26 +143,32 @@
 		endian = binary.BigEndian
 	}
 	out := [][]byte{}
+	// Check every SHT_NOTE section.
+	for _, section := range elfFile.Sections {
+		if section == nil || section.Type != elf.SHT_NOTE {
+			continue
+		}
+		buildIDs, err := getBuildIDs(filename, endian, section, section.Size)
+		if err != nil {
+			return out, err
+		}
+		out = append(out, buildIDs...)
+	}
+	// If we found what we were looking for with sections, don't reparse the program
+	// headers.
+	if len(out) > 0 {
+		return out, nil
+	}
 	// Check every PT_NOTE segment.
 	for _, prog := range elfFile.Progs {
 		if prog == nil || prog.Type != elf.PT_NOTE {
 			continue
 		}
-		noteBytes := make([]byte, prog.Filesz)
-		_, err := prog.ReadAt(noteBytes, 0)
-		if err != nil {
-			return nil, fmt.Errorf("error parsing program header in %s: %v", filename, err)
-		}
-		// While the part of the note segment we're looking at doesn't have more valid data.
-		err = forEachNote(noteBytes, endian, func(entry noteEntry) {
-			if entry.noteType != NT_GNU_BUILD_ID || entry.name != "GNU\000" {
-				return
-			}
-			out = append(out, entry.desc)
-		})
+		buildIDs, err := getBuildIDs(filename, endian, prog, prog.Filesz)
 		if err != nil {
 			return out, err
 		}
+		out = append(out, buildIDs...)
 	}
 	return out, nil
 }
diff --git a/elflib/elflib_test.go b/elflib/elflib_test.go
new file mode 100644
index 0000000..8c01747
--- /dev/null
+++ b/elflib/elflib_test.go
@@ -0,0 +1,47 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package elflib
+
+import (
+	"encoding/hex"
+	"os"
+	"testing"
+)
+
+func TestBuildIDs(t *testing.T) {
+	f, err := os.Open("testdata/libc.elf.section-only")
+	if err != nil {
+		t.Fatal("from os.Open: ", err)
+	}
+	buildIDs, err := GetBuildIDs("testdata/libc.elf.section-only", f)
+	if err != nil {
+		t.Fatal("from GetBuildIDs: ", err)
+	}
+	if len(buildIDs) != 1 {
+		t.Fatal("expected one build ID but got ", buildIDs)
+	}
+	expected := "4fcb712aa6387724a9f465a32cd8c14b"
+	if hex.EncodeToString(buildIDs[0]) != expected {
+		t.Fatal("expected ", expected, " but got ", buildIDs[0])
+	}
+}
+
+func TestStrippedBuildIDs(t *testing.T) {
+	f, err := os.Open("testdata/libc.elf.stripped")
+	if err != nil {
+		t.Fatal("from os.Open: ", err)
+	}
+	buildIDs, err := GetBuildIDs("testdata/libc.elf.stripped", f)
+	if err != nil {
+		t.Fatal("from GetBuildIDs: ", err)
+	}
+	if len(buildIDs) != 1 {
+		t.Fatal("expected one build ID but got ", buildIDs)
+	}
+	expected := "4fcb712aa6387724a9f465a32cd8c14b"
+	if hex.EncodeToString(buildIDs[0]) != expected {
+		t.Fatal("expected ", expected, " but got ", buildIDs[0])
+	}
+}
diff --git a/elflib/elflib_test.go.no-test b/elflib/elflib_test.go.no-test
deleted file mode 100644
index b392758..0000000
--- a/elflib/elflib_test.go.no-test
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2018 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// This test requires a real binary to test and I don't want that binary
-// to be uploaded into the repo at this time. I still want to show how
-// to test such things.
-
-package elflib
-
-import (
-	"os"
-	"testing"
-)
-
-func TestGetSoName(t *testing.T) {
-	f, err := os.Open("testdata/libc.so.debug")
-	if err != nil {
-		t.Fatal(err)
-	}
-	soname, err := GetSoName("testdata/libc.so.debug", f)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if soname != "libc.so" {
-		t.Fatal("expected soname to be \"libc.so\" but got ", soname)
-	}
-}
diff --git a/elflib/testdata/libc.elf.section-only b/elflib/testdata/libc.elf.section-only
new file mode 100644
index 0000000..cbced7c
--- /dev/null
+++ b/elflib/testdata/libc.elf.section-only
Binary files differ
diff --git a/elflib/testdata/libc.elf.stripped b/elflib/testdata/libc.elf.stripped
new file mode 100755
index 0000000..0071813
--- /dev/null
+++ b/elflib/testdata/libc.elf.stripped
Binary files differ
diff --git a/elflib/testdata/libc.yaml b/elflib/testdata/libc.yaml
new file mode 100644
index 0000000..e33cfd0
--- /dev/null
+++ b/elflib/testdata/libc.yaml
@@ -0,0 +1,45 @@
+--- !ELF
+FileHeader:
+  Class:           ELFCLASS64
+  Data:            ELFDATA2LSB
+  Type:            ET_EXEC
+  Machine:         EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC ]
+  - Name:            .dynsym
+    Type:            SHT_DYNSYM
+  - Name:            .dynstr
+    Type:            SHT_STRTAB
+  - Name:            .note.gnu.build-id
+    Type:            SHT_NOTE
+    Flags:           [ SHF_ALLOC ]
+    AddressAlign:    0x0000000000000004
+    Content:         040000001000000003000000474E55004FCB712AA6387724A9F465A32CD8C14B
+DynamicSymbols:
+  Global:
+    - Name: atan2
+      Type: STT_FUNC
+      Section: .text
+    - Name: pow
+      Type: STT_FUNC
+      Section: .text
+    - Name: memcpy
+      Type: STT_FUNC
+      Section: .text
+ProgramHeaders:
+  - Type: PT_LOAD
+    Flags: [ PF_X, PF_R ]
+    Sections:
+      - Section: .text
+  - Type: PT_LOAD
+    Flags: [ PF_R ]
+    Sections:
+      - Section: .dynsym
+      - Section: .dynstr
+      - Section: .note.gnu.build-id
+  - Type: PT_NOTE
+    Flags: [ PF_R ]
+    Sections:
+      - Section: .note.gnu.build-id
diff --git a/elflib/testdata/libc.yaml.section-only b/elflib/testdata/libc.yaml.section-only
new file mode 100644
index 0000000..5bcd6ca
--- /dev/null
+++ b/elflib/testdata/libc.yaml.section-only
@@ -0,0 +1,12 @@
+--- !ELF
+FileHeader:
+  Class:           ELFCLASS64
+  Data:            ELFDATA2LSB
+  Type:            ET_EXEC
+  Machine:         EM_X86_64
+Sections:
+  - Name:            .note.gnu.build-id
+    Type:            SHT_NOTE
+    Flags:           [ SHF_ALLOC ]
+    AddressAlign:    0x0000000000000004
+    Content:         040000001000000003000000474E55004FCB712AA6387724A9F465A32CD8C14B