blob: 963b93a3e59436796bcdb428d56b3f9f6529e094 [file] [log] [blame]
// 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 symbolize
import (
"bufio"
"bytes"
"encoding/hex"
"fmt"
"os"
"strings"
"sync"
"fuchsia.googlesource.com/tools/elflib"
)
type Binary struct {
BuildID string
Name string
}
type Source interface {
GetBinaries() ([]Binary, error)
}
type buildIDError struct {
err error
filename string
}
func newBuildIDError(err error, filename string) *buildIDError {
return &buildIDError{err: err, filename: filename}
}
func (b buildIDError) Error() string {
return fmt.Sprintf("error reading %s: %v", b.filename, b.err)
}
func VerifyBinary(filename, build string) error {
file, err := os.Open(filename)
if err != nil {
return newBuildIDError(err, filename)
}
buildIDs, err := elflib.GetBuildIDs(filename, file)
if err != nil {
return newBuildIDError(err, filename)
}
binBuild, err := hex.DecodeString(build)
if err != nil {
return newBuildIDError(fmt.Errorf("build id `%s` is not a hex string: %v", build, err), filename)
}
for _, buildID := range buildIDs {
if bytes.Equal(buildID, binBuild) {
return nil
}
}
return newBuildIDError(fmt.Errorf("build id `%s` could not be found", build), filename)
}
type IDsSource struct {
pathToIDs string
}
func NewIDsSource(pathToIDs string) Source {
return &IDsSource{pathToIDs}
}
func (i *IDsSource) GetBinaries() ([]Binary, error) {
file, err := os.Open(i.pathToIDs)
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(file)
out := []Binary{}
for line := 0; scanner.Scan(); line++ {
parts := strings.SplitN(scanner.Text(), " ", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("error parsing %s at line %d", i.pathToIDs, line)
}
build := parts[0]
filename := parts[1]
if err := VerifyBinary(filename, build); err != nil {
return nil, err
}
out = append(out, Binary{Name: filename, BuildID: build})
}
return out, nil
}
type buildInfo struct {
filepath string
buildID string
}
// SymbolizerRepo keeps track of build objects and source files used in those build objects.
type SymbolizerRepo struct {
lock sync.RWMutex
sources []Source
builds map[string]*buildInfo
}
func (s *SymbolizerRepo) AddObject(build, filename string) {
s.lock.Lock()
defer s.lock.Unlock()
s.builds[build] = &buildInfo{
filepath: filename,
buildID: build,
}
}
func NewRepo() *SymbolizerRepo {
return &SymbolizerRepo{
builds: make(map[string]*buildInfo),
}
}
func (s *SymbolizerRepo) loadSource(source Source) error {
bins, err := source.GetBinaries()
if err != nil {
return err
}
// TODO: Do this in parallel
for _, bin := range bins {
s.AddObject(bin.BuildID, bin.Name)
}
return nil
}
// AddSource adds a source of binaries and all contained binaries.
func (s *SymbolizerRepo) AddSource(source Source) error {
s.sources = append(s.sources, source)
return s.loadSource(source)
}
func (s *SymbolizerRepo) reloadSources() error {
for _, source := range s.sources {
if err := s.loadSource(source); err != nil {
return err
}
}
return nil
}
func (s *SymbolizerRepo) readInfo(buildid string) (*buildInfo, bool) {
s.lock.RLock()
info, ok := s.builds[buildid]
s.lock.RUnlock()
return info, ok
}
// GetBuildObject lets you get the build object associated with a build id.
func (s *SymbolizerRepo) GetBuildObject(buildid string) (string, error) {
// No defer is used here because we don't want to hold the read lock
// for very long.
info, ok := s.readInfo(buildid)
if !ok {
// If we don't recognize that build id, reload all sources.
s.reloadSources()
info, ok = s.readInfo(buildid)
if !ok {
// If we still don't know about that build, return an error.
return "", fmt.Errorf("unrecognized buildid %s", buildid)
}
}
return info.filepath, nil
}