blob: 3bcff1116241a39479a7ea79f4683f046c370daf [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 (
// Symbolizer is an interface to an object that maps addresses in a bianry to source locations
type Symbolizer interface {
FindSrcLoc(file, build string, modRelAddr uint64) <-chan LLVMSymbolizeResult
type llvmSymbolizeArgsKey struct {
build string
modRelAddr uint64
type llvmSymboArgs struct {
file string
build string
modRelAddr uint64
output chan LLVMSymbolizeResult
const maxCacheSize = 128
type LLVMSymbolizeResult struct {
Locs []SourceLocation
Err error
type LLVMSymbolizer struct {
path string
stdin io.WriteCloser
stdout io.ReadCloser
symbolizer *exec.Cmd
input chan llvmSymboArgs
cache cache.Cache
func NewLLVMSymbolizer(llvmSymboPath string) *LLVMSymbolizer {
var out LLVMSymbolizer
out.path = llvmSymboPath
out.symbolizer = exec.Command(llvmSymboPath)
out.input = make(chan llvmSymboArgs)
out.cache = &cache.LRUCache{Size: maxCacheSize}
return &out
func unknownStr(str string) OptStr {
if str == "??" || str == "" {
return EmptyOptStr()
return NewOptStr(str)
func (s *LLVMSymbolizer) handle(ctx context.Context) {
for {
select {
case <-ctx.Done():
case args, ok := <-s.input:
if !ok {
// See if we've seen this before and send off the result
key := llvmSymbolizeArgsKey{, args.modRelAddr}
if res, ok := s.cache.Get(key); ok {
args.output <- res.(LLVMSymbolizeResult)
if len(strings.TrimSpace(args.file)) == 0 {
args.output <- LLVMSymbolizeResult{
nil, fmt.Errorf("Attempt to request code location of unnamed file with build ID %x",}
// Before sending a binary off to llvm-symbolizer, verify the binary
if err := elflib.NewBinaryFileRef(args.file,; err != nil {
args.output <- LLVMSymbolizeResult{nil, err}
// From //zircon/docs/
// In frames after frame zero, this code location identifies a call site.
// Some emitters may subtract one byte or one instruction length from the
// actual return address for the call site, with the intent that the address
// logged can be translated directly to a source location for the call site
// and not for the apparent return site thereafter (which can be confusing).
// It‘s recommended that emitters not do this, so that each frame’s code
// location is the exact return address given to its callee and e.g. could be
// highlighted in instruction-level disassembly. The symbolizing filter can do
// the adjustment to the address it translates into a source location. Assuming
// that a call instruction is longer than one byte on all supported machines,
// applying the "subtract one byte" adjustment a second time still results in an
// address somewhere in the call instruction, so a little sloppiness here does
// no harm.
fmt.Fprintf(s.stdin, "%s 0x%x\n", args.file, args.modRelAddr-1)
out := []SourceLocation{}
scanner := bufio.NewScanner(s.stdout)
for scanner.Scan() {
function := scanner.Text()
if len(function) == 0 {
good := scanner.Scan()
if !good {
panic(fmt.Sprintf("%s output ended too soon", s.path))
location := scanner.Text()
parts := strings.SplitN(location, ":", 3)
if len(parts) < 2 {
panic(fmt.Sprintf("%s output unrecgonized format", s.path))
line, _ := strconv.Atoi(parts[1])
out = append(out, SourceLocation{unknownStr(parts[0]), line, unknownStr(function)})
outputRes := LLVMSymbolizeResult{out, nil}
s.cache.Add(key, outputRes)
args.output <- outputRes
func (s *LLVMSymbolizer) Start(ctx context.Context) error {
var err error
if s.stdin, err = s.symbolizer.StdinPipe(); err != nil {
return err
if s.stdout, err = s.symbolizer.StdoutPipe(); err != nil {
return err
if err = s.symbolizer.Start(); err != nil {
return err
go s.handle(ctx)
return nil
func (s *LLVMSymbolizer) FindSrcLoc(file, build string, modRelAddr uint64) <-chan LLVMSymbolizeResult {
// Buffer the return chanel so we don't block handle().
out := make(chan LLVMSymbolizeResult, 1)
args := llvmSymboArgs{file: file, build: build, modRelAddr: modRelAddr, output: out}
s.input <- args
return out