Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 465a131a authored by Colin Cross's avatar Colin Cross Committed by Gerrit Code Review
Browse files

Merge "Add a symbols_map tool for extracting identifiers from elf and r8 files"

parents 90b6e70c 36f55aab
Loading
Loading
Loading
Loading
+34 −0
Original line number Diff line number Diff line
package {
    default_applicable_licenses: ["Android-Apache-2.0"],
}

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-response",
        "symbols_map_proto",
    ],
}

bootstrap_go_package {
    name: "symbols_map_proto",
    pkgPath: "android/soong/cmd/symbols_map/symbols_map_proto",
    deps: [
        "golang-protobuf-reflect-protoreflect",
        "golang-protobuf-runtime-protoimpl",
    ],
    srcs: [
        "symbols_map_proto/symbols_map.pb.go",
    ],
}

cmd/symbols_map/elf.go

0 → 100644
+95 −0
Original line number Diff line number Diff line
// 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"
	"fmt"
	"io"
)

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 := elf.Open(filename)
	if err != nil {
		return "", fmt.Errorf("failed to open %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
}
+45 −0
Original line number Diff line number Diff line
// 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"
	"encoding/binary"
	"reflect"
	"testing"
)

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)
	}
}

cmd/symbols_map/r8.go

0 → 100644
+56 −0
Original line number Diff line number Diff line
// 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 (
	"bufio"
	"fmt"
	"io"
	"os"
	"strings"
)

const hashPrefix = "# pg_map_hash: "
const hashTypePrefix = "SHA-256 "
const commentPrefix = "#"

// r8Identifier extracts the hash from the comments of a dictionary produced by R8. It returns
// an empty identifier if no matching comment was found before the first non-comment line.
func r8Identifier(filename string) (string, error) {
	f, err := os.Open(filename)
	if err != nil {
		return "", fmt.Errorf("failed to open %s: %w", filename, err)
	}
	defer f.Close()

	return extractR8CompilerHash(f)
}

func extractR8CompilerHash(r io.Reader) (string, error) {
	s := bufio.NewScanner(r)
	for s.Scan() {
		line := s.Text()
		if strings.HasPrefix(line, hashPrefix) {
			hash := strings.TrimPrefix(line, hashPrefix)
			if !strings.HasPrefix(hash, hashTypePrefix) {
				return "", fmt.Errorf("invalid hash type found in %q", line)
			}
			return strings.TrimPrefix(hash, hashTypePrefix), nil
		} else if !strings.HasPrefix(line, commentPrefix) {
			break
		}
	}
	return "", nil
}
+91 −0
Original line number Diff line number Diff line
// 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"
	"strings"
	"testing"
)

func Test_extractR8CompilerHash(t *testing.T) {
	testCases := []struct {
		name string
		data string

		hash string
		err  string
	}{
		{
			name: "simple",
			data: `# compiler: R8
# compiler_version: 3.3.18-dev
# min_api: 10000
# compiler_hash: bab44c1a04a2201b55fe10394f477994205c34e0
# common_typos_disable
# {"id":"com.android.tools.r8.mapping","version":"2.0"}
# pg_map_id: 7fe8b95
# pg_map_hash: SHA-256 7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da
android.car.userlib.UserHelper -> android.car.userlib.UserHelper:
`,
			hash: "7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da",
		},
		{
			name: "empty",
			data: ``,
			hash: "",
		},
		{
			name: "non comment line",
			data: `# compiler: R8
# compiler_version: 3.3.18-dev
# min_api: 10000
# compiler_hash: bab44c1a04a2201b55fe10394f477994205c34e0
# common_typos_disable
# {"id":"com.android.tools.r8.mapping","version":"2.0"}
# pg_map_id: 7fe8b95
android.car.userlib.UserHelper -> android.car.userlib.UserHelper:
# pg_map_hash: SHA-256 7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da
`,
			hash: "",
		},
		{
			name: "invalid hash",
			data: `# pg_map_hash: foobar 7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da`,
			err:  "invalid hash type",
		},
	}

	for _, tt := range testCases {
		t.Run(tt.name, func(t *testing.T) {
			hash, err := extractR8CompilerHash(bytes.NewBufferString(tt.data))
			if err != nil {
				if tt.err != "" {
					if !strings.Contains(err.Error(), tt.err) {
						t.Fatalf("incorrect error in extractR8CompilerHash, want %s got %s", tt.err, err)
					}
				} else {
					t.Fatalf("unexpected error in extractR8CompilerHash: %s", err)
				}
			} else if tt.err != "" {
				t.Fatalf("missing error in extractR8CompilerHash, want %s", tt.err)
			}

			if g, w := hash, tt.hash; g != w {
				t.Errorf("incorrect hash, want %q got %q", w, g)
			}
		})
	}
}
Loading