blob: 049eee883cfc4eb36616bedad59577751b1cbb2d [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 (
"context"
"encoding/json"
"fmt"
"path/filepath"
"reflect"
"testing"
)
type mockModule struct {
name string
addr2line map[uint64][]SourceLocation
}
type mockSymbolizer struct {
modules map[string]mockModule
}
func newMockSymbolizer(modules []mockModule) Symbolizer {
var out mockSymbolizer
out.modules = make(map[string]mockModule)
for _, mod := range modules {
out.modules[mod.name] = mod
}
return &out
}
func (s *mockSymbolizer) FindSrcLoc(file, build string, modRelAddr uint64) <-chan LLVMSymbolizeResult {
out := make(chan LLVMSymbolizeResult, 1)
if mod, ok := s.modules[file]; ok {
if locs, ok := mod.addr2line[modRelAddr]; ok {
out <- LLVMSymbolizeResult{locs, nil}
} else {
out <- LLVMSymbolizeResult{nil, fmt.Errorf("0x%x was not a valid address in %s", modRelAddr, file)}
}
} else {
out <- LLVMSymbolizeResult{nil, fmt.Errorf("%s could not be found", file)}
}
return out
}
type symbolizerRepo struct {
builds map[string]string
}
func TestBasic(t *testing.T) {
// mock the input and outputs of llvm-symbolizer
symbo := newMockSymbolizer([]mockModule{
{
filepath.Join(*testDataDir, "libc.elf"),
map[uint64][]SourceLocation{
0x429c0: {{NewOptStr("atan2.c"), 49, NewOptStr("atan2")}, {NewOptStr("math.h"), 51, NewOptStr("__DOUBLE_FLOAT")}},
0x43680: {{NewOptStr("pow.c"), 23, NewOptStr("pow")}},
0x44987: {{NewOptStr("memcpy.c"), 76, NewOptStr("memcpy")}},
},
},
{
filepath.Join(*testDataDir, "libcrypto.elf"),
map[uint64][]SourceLocation{
0x81000: {{NewOptStr("rsa.c"), 101, NewOptStr("mod_exp")}},
0x82000: {{NewOptStr("aes.c"), 17, NewOptStr("gf256_mul")}},
0x83000: {{NewOptStr("aes.c"), 560, NewOptStr("gf256_div")}},
},
},
})
// Get a line parser
parseLine := GetLineParser()
// make an actual filter using those two mock objects
filter := NewFilter(getTestBinaries(), symbo)
// parse some example lines
err := filter.addModule(Module{"libc.elf", "4fcb712aa6387724a9f465a32cd8c14b", 1})
if err != nil {
t.Fatal(err)
}
err = filter.addModule(Module{"libcrypto.elf", "12ef5c50b3ed3599c07c02d4509311be", 2})
if err != nil {
t.Fatal(err)
}
filter.addSegment(Segment{1, 0x12345000, 849596, "rx", 0x0})
filter.addSegment(Segment{2, 0x23456000, 539776, "rx", 0x80000})
line := parseLine("\033[1m Error at {{{pc:0x123879c0}}}")
// print out a more precise form
for _, token := range line {
token.Accept(&filterVisitor{filter, 1, context.Background(), DummySource{}})
}
json, err := GetLineJson(line)
if err != nil {
t.Fatalf("json did not parse correctly: %v", err)
}
expectedJson := []byte(`[
{"type": "color", "color": 1},
{"type": "text", "text": " Error at "},
{"type": "pc", "vaddr": 305691072, "file": "atan2.c",
"line": 49, "function": "atan2"}
]`)
if !EqualJson(json, expectedJson) {
t.Error("expected", expectedJson, "got", json)
}
}
func TestMalformed(t *testing.T) {
parseLine := GetLineParser()
// Parse a bad line
line := parseLine("\033[1m Error at {{{pc:0x123879c0")
// Malformed lines should still parse
if line == nil {
t.Error("expected", "not nil", "got", line)
}
}
func EqualJson(a, b []byte) bool {
var j1, j2 interface{}
err := json.Unmarshal(a, &j1)
if err != nil {
panic(err.Error())
}
err = json.Unmarshal(b, &j2)
if err != nil {
panic(err.Error())
}
return reflect.DeepEqual(j1, j2)
}
func TestBacktrace(t *testing.T) {
parseLine := GetLineParser()
line := parseLine("Error at {{{bt:0:0x12389988}}}")
if line == nil {
t.Error("got", nil, "expected", "not nil")
return
}
// mock the input and outputs of llvm-symbolizer
symbo := newMockSymbolizer([]mockModule{
{
filepath.Join(*testDataDir, "libc.elf"),
map[uint64][]SourceLocation{
0x44988: {{NewOptStr("duff.h"), 64, NewOptStr("duffcopy")}, {NewOptStr("memcpy.c"), 76, NewOptStr("memcpy")}},
},
},
})
// make an actual filter using those two mock objects
filter := NewFilter(getTestBinaries(), symbo)
// add some context
err := filter.addModule(Module{"libc.so", "4fcb712aa6387724a9f465a32cd8c14b", 1})
if err != nil {
t.Fatal(err)
}
filter.addSegment(Segment{1, 0x12345000, 849596, "rx", 0x0})
for _, token := range line {
token.Accept(&filterVisitor{filter, 1, context.Background(), DummySource{}})
}
json, err := GetLineJson(line)
if err != nil {
t.Error("json did not parse correctly", err)
}
expectedJson := []byte(`[
{"type": "text", "text": "Error at "},
{"type": "bt", "vaddr": 305699208, "num": 0, "locs":[
{"line": 64, "function": "duffcopy", "file": "duff.h"},
{"line": 76, "function": "memcpy", "file": "memcpy.c"}
]}
]`)
if !EqualJson(json, expectedJson) {
t.Error("unexpected json output", "got", string(json), "expected", string(expectedJson))
}
}
func TestReset(t *testing.T) {
parseLine := GetLineParser()
line := parseLine("{{{reset}}}")
json, err := GetLineJson(line)
if err != nil {
t.Error("json did not parse correctly", err)
}
expectedJson := []byte(`[{"type":"reset"}]`)
if !EqualJson(json, expectedJson) {
t.Error("unexpected json output", "got", string(json), "expected", string(expectedJson))
}
// mock the input and outputs of llvm-symbolizer
symbo := newMockSymbolizer([]mockModule{
{
filepath.Join(*testDataDir, "libc.elf"),
map[uint64][]SourceLocation{
0x44987: {{NewOptStr("memcpy.c"), 76, NewOptStr("memcpy")}},
},
},
})
// make an actual filter using those two mock objects
filter := NewFilter(getTestBinaries(), symbo)
// add some context
mod := Module{"libc.so", "4fcb712aa6387724a9f465a32cd8c14b", 1}
err = filter.addModule(mod)
if err != nil {
t.Fatal(err)
}
seg := Segment{1, 0x12345000, 849596, "rx", 0x0}
filter.addSegment(seg)
addr := uint64(0x12389987)
if info, err := filter.findInfoForAddress(addr); err != nil {
t.Error("expected", nil, "got", err)
if len(info.locs) != 1 {
t.Error("expected", 1, "source location but got", len(info.locs))
} else {
loc := SourceLocation{NewOptStr("memcpy.c"), 76, NewOptStr("memcpy")}
if info.locs[0] != loc {
t.Error("expected", loc, "got", info.locs[0])
}
}
if info.mod != mod {
t.Error("expected", mod, "got", info.mod)
}
if info.seg != seg {
t.Error("expected", seg, "got", info.seg)
}
if info.addr != addr {
t.Error("expected", addr, "got", info.addr)
}
}
// now forget the context
for _, token := range line {
token.Accept(&filterVisitor{filter, 1, context.Background(), DummySource{}})
}
if _, err := filter.findInfoForAddress(addr); err == nil {
t.Error("expected non-nil error but got", err)
}
}