Move ELF build-id reader into a separate library.

Bug: 328702178
Change-Id: I188a8d20d22e67e4f0c7e3441e3781fff369c828
diff --git a/cmd/symbols_map/Android.bp b/cmd/symbols_map/Android.bp
index 0ba3b07..e3ae6ed 100644
--- a/cmd/symbols_map/Android.bp
+++ b/cmd/symbols_map/Android.bp
@@ -5,17 +5,16 @@
 blueprint_go_binary {
     name: "symbols_map",
     srcs: [
-        "elf.go",
         "r8.go",
         "symbols_map.go",
     ],
     testSrcs: [
-        "elf_test.go",
         "r8_test.go",
     ],
     deps: [
         "blueprint-pathtools",
         "golang-protobuf-encoding-prototext",
+        "soong-elf",
         "soong-response",
         "symbols_map_proto",
     ],
diff --git a/cmd/symbols_map/elf.go b/cmd/symbols_map/elf.go
deleted file mode 100644
index 950e3b2..0000000
--- a/cmd/symbols_map/elf.go
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2022 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
-	"debug/elf"
-	"encoding/binary"
-	"encoding/hex"
-	"errors"
-	"fmt"
-	"io"
-	"os"
-)
-
-const gnuBuildID = "GNU\x00"
-
-// elfIdentifier extracts the elf build ID from an elf file.  If allowMissing is true it returns
-// an empty identifier if the file exists but the build ID note does not.
-func elfIdentifier(filename string, allowMissing bool) (string, error) {
-	f, err := os.Open(filename)
-	if err != nil {
-		return "", fmt.Errorf("failed to open %s: %w", filename, err)
-	}
-	defer f.Close()
-
-	return elfIdentifierFromReaderAt(f, filename, allowMissing)
-}
-
-// elfIdentifierFromReaderAt extracts the elf build ID from a ReaderAt.  If allowMissing is true it
-// returns an empty identifier if the file exists but the build ID note does not.
-func elfIdentifierFromReaderAt(r io.ReaderAt, filename string, allowMissing bool) (string, error) {
-	f, err := elf.NewFile(r)
-	if err != nil {
-		if allowMissing {
-			if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
-				return "", nil
-			}
-			if _, ok := err.(*elf.FormatError); ok {
-				// The file was not an elf file.
-				return "", nil
-			}
-		}
-		return "", fmt.Errorf("failed to parse elf file %s: %w", filename, err)
-	}
-	defer f.Close()
-
-	buildIDNote := f.Section(".note.gnu.build-id")
-	if buildIDNote == nil {
-		if allowMissing {
-			return "", nil
-		}
-		return "", fmt.Errorf("failed to find .note.gnu.build-id in  %s", filename)
-	}
-
-	buildIDs, err := readNote(buildIDNote.Open(), f.ByteOrder)
-	if err != nil {
-		return "", fmt.Errorf("failed to read .note.gnu.build-id: %w", err)
-	}
-
-	for name, desc := range buildIDs {
-		if name == gnuBuildID {
-			return hex.EncodeToString(desc), nil
-		}
-	}
-
-	return "", nil
-}
-
-// readNote reads the contents of a note section, returning it as a map from name to descriptor.
-func readNote(note io.Reader, byteOrder binary.ByteOrder) (map[string][]byte, error) {
-	var noteHeader struct {
-		Namesz uint32
-		Descsz uint32
-		Type   uint32
-	}
-
-	notes := make(map[string][]byte)
-	for {
-		err := binary.Read(note, byteOrder, &noteHeader)
-		if err != nil {
-			if err == io.EOF {
-				return notes, nil
-			}
-			return nil, fmt.Errorf("failed to read note header: %w", err)
-		}
-
-		nameBuf := make([]byte, align4(noteHeader.Namesz))
-		err = binary.Read(note, byteOrder, &nameBuf)
-		if err != nil {
-			return nil, fmt.Errorf("failed to read note name: %w", err)
-		}
-		name := string(nameBuf[:noteHeader.Namesz])
-
-		descBuf := make([]byte, align4(noteHeader.Descsz))
-		err = binary.Read(note, byteOrder, &descBuf)
-		if err != nil {
-			return nil, fmt.Errorf("failed to read note desc: %w", err)
-		}
-		notes[name] = descBuf[:noteHeader.Descsz]
-	}
-}
-
-// align4 rounds the input up to the next multiple of 4.
-func align4(i uint32) uint32 {
-	return (i + 3) &^ 3
-}
diff --git a/cmd/symbols_map/elf_test.go b/cmd/symbols_map/elf_test.go
deleted file mode 100644
index a94c87f..0000000
--- a/cmd/symbols_map/elf_test.go
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2022 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
-	"bytes"
-	"debug/elf"
-	"encoding/binary"
-	"reflect"
-	"testing"
-)
-
-func Test_elfIdentifierFromReaderAt_BadElfFile(t *testing.T) {
-	tests := []struct {
-		name     string
-		contents string
-	}{
-		{
-			name:     "empty",
-			contents: "",
-		},
-		{
-			name:     "text",
-			contents: "#!/bin/bash\necho foobar",
-		},
-		{
-			name:     "empty elf",
-			contents: emptyElfFile(),
-		},
-		{
-			name:     "short section header",
-			contents: shortSectionHeaderElfFile(),
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			buf := bytes.NewReader([]byte(tt.contents))
-			_, err := elfIdentifierFromReaderAt(buf, "<>", false)
-			if err == nil {
-				t.Errorf("expected error reading bad elf file without allowMissing")
-			}
-			_, err = elfIdentifierFromReaderAt(buf, "<>", true)
-			if err != nil {
-				t.Errorf("expected no error reading bad elf file with allowMissing, got %q", err.Error())
-			}
-		})
-	}
-}
-
-func Test_readNote(t *testing.T) {
-	note := []byte{
-		0x04, 0x00, 0x00, 0x00,
-		0x10, 0x00, 0x00, 0x00,
-		0x03, 0x00, 0x00, 0x00,
-		0x47, 0x4e, 0x55, 0x00,
-		0xca, 0xaf, 0x44, 0xd2, 0x82, 0x78, 0x68, 0xfe, 0xc0, 0x90, 0xa3, 0x43, 0x85, 0x36, 0x6c, 0xc7,
-	}
-
-	descs, err := readNote(bytes.NewBuffer(note), binary.LittleEndian)
-	if err != nil {
-		t.Fatalf("unexpected error in readNote: %s", err)
-	}
-
-	expectedDescs := map[string][]byte{
-		"GNU\x00": []byte{0xca, 0xaf, 0x44, 0xd2, 0x82, 0x78, 0x68, 0xfe, 0xc0, 0x90, 0xa3, 0x43, 0x85, 0x36, 0x6c, 0xc7},
-	}
-
-	if !reflect.DeepEqual(descs, expectedDescs) {
-		t.Errorf("incorrect return, want %#v got %#v", expectedDescs, descs)
-	}
-}
-
-// emptyElfFile returns an elf file header with no program headers or sections.
-func emptyElfFile() string {
-	ident := [elf.EI_NIDENT]byte{}
-	identBuf := bytes.NewBuffer(ident[0:0:elf.EI_NIDENT])
-	binary.Write(identBuf, binary.LittleEndian, []byte("\x7fELF"))
-	binary.Write(identBuf, binary.LittleEndian, elf.ELFCLASS64)
-	binary.Write(identBuf, binary.LittleEndian, elf.ELFDATA2LSB)
-	binary.Write(identBuf, binary.LittleEndian, elf.EV_CURRENT)
-	binary.Write(identBuf, binary.LittleEndian, elf.ELFOSABI_LINUX)
-	binary.Write(identBuf, binary.LittleEndian, make([]byte, 8))
-
-	header := elf.Header64{
-		Ident:     ident,
-		Type:      uint16(elf.ET_EXEC),
-		Machine:   uint16(elf.EM_X86_64),
-		Version:   uint32(elf.EV_CURRENT),
-		Entry:     0,
-		Phoff:     uint64(binary.Size(elf.Header64{})),
-		Shoff:     uint64(binary.Size(elf.Header64{})),
-		Flags:     0,
-		Ehsize:    uint16(binary.Size(elf.Header64{})),
-		Phentsize: 0x38,
-		Phnum:     0,
-		Shentsize: 0x40,
-		Shnum:     0,
-		Shstrndx:  0,
-	}
-
-	buf := &bytes.Buffer{}
-	binary.Write(buf, binary.LittleEndian, header)
-	return buf.String()
-}
-
-// shortSectionHeader returns an elf file header with a section header that extends past the end of
-// the file.
-func shortSectionHeaderElfFile() string {
-	ident := [elf.EI_NIDENT]byte{}
-	identBuf := bytes.NewBuffer(ident[0:0:elf.EI_NIDENT])
-	binary.Write(identBuf, binary.LittleEndian, []byte("\x7fELF"))
-	binary.Write(identBuf, binary.LittleEndian, elf.ELFCLASS64)
-	binary.Write(identBuf, binary.LittleEndian, elf.ELFDATA2LSB)
-	binary.Write(identBuf, binary.LittleEndian, elf.EV_CURRENT)
-	binary.Write(identBuf, binary.LittleEndian, elf.ELFOSABI_LINUX)
-	binary.Write(identBuf, binary.LittleEndian, make([]byte, 8))
-
-	header := elf.Header64{
-		Ident:     ident,
-		Type:      uint16(elf.ET_EXEC),
-		Machine:   uint16(elf.EM_X86_64),
-		Version:   uint32(elf.EV_CURRENT),
-		Entry:     0,
-		Phoff:     uint64(binary.Size(elf.Header64{})),
-		Shoff:     uint64(binary.Size(elf.Header64{})),
-		Flags:     0,
-		Ehsize:    uint16(binary.Size(elf.Header64{})),
-		Phentsize: 0x38,
-		Phnum:     0,
-		Shentsize: 0x40,
-		Shnum:     1,
-		Shstrndx:  0,
-	}
-
-	buf := &bytes.Buffer{}
-	binary.Write(buf, binary.LittleEndian, header)
-	binary.Write(buf, binary.LittleEndian, []byte{0})
-	return buf.String()
-}
diff --git a/cmd/symbols_map/symbols_map.go b/cmd/symbols_map/symbols_map.go
index 938446d..c56cf93 100644
--- a/cmd/symbols_map/symbols_map.go
+++ b/cmd/symbols_map/symbols_map.go
@@ -22,6 +22,7 @@
 	"strings"
 
 	"android/soong/cmd/symbols_map/symbols_map_proto"
+	"android/soong/elf"
 	"android/soong/response"
 
 	"github.com/google/blueprint/pathtools"
@@ -116,7 +117,7 @@
 	if *elfFile != "" {
 		typ = symbols_map_proto.Mapping_ELF
 		location = *elfFile
-		identifier, err = elfIdentifier(*elfFile, true)
+		identifier, err = elf.Identifier(*elfFile, true)
 		if err != nil {
 			fmt.Fprintf(os.Stderr, "error reading elf identifier: %s\n", err)
 			os.Exit(1)