Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove binutils wrappers used for testing #1

Merged
merged 1 commit into from
Feb 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 64 additions & 46 deletions internal/binutils/addr2liner.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,43 @@ const (
sentinel = ^uint64(0)
)

// Addr2Liner is a connection to an addr2line command for obtaining
// addr2Liner is a connection to an addr2line command for obtaining
// address and line number information from a binary.
type addr2Liner struct {
filename string
cmd *exec.Cmd
in io.WriteCloser
out *bufio.Reader
err error

rw lineReaderWriter
base uint64
}

// lineReaderWriter is an interface to abstract the I/O to an addr2line
// process. It writes a line of input to the job, and reads its output
// one line at a time.
type lineReaderWriter interface {
write(string) error
readLine() (string, error)
close()
}

type addr2LinerJob struct {
cmd *exec.Cmd
in io.WriteCloser
out *bufio.Reader
}

func (a *addr2LinerJob) write(s string) error {
_, err := fmt.Fprint(a.in, s+"\n")
return err
}

func (a *addr2LinerJob) readLine() (string, error) {
return a.out.ReadString('\n')
}

// close releases any resources used by the addr2liner object.
func (a *addr2LinerJob) close() {
a.in.Close()
a.cmd.Wait()
}

// newAddr2liner starts the given addr2liner command reporting
// information about the given executable file. If file is a shared
// library, base should be the address at which is was mapped in the
Expand All @@ -54,51 +79,49 @@ func newAddr2Liner(cmd, file string, base uint64) (*addr2Liner, error) {
cmd = defaultAddr2line
}

a := &addr2Liner{
filename: file,
base: base,
cmd: exec.Command(cmd, "-aif", "-e", file),
j := &addr2LinerJob{
cmd: exec.Command(cmd, "-aif", "-e", file),
}

var err error
if a.in, err = a.cmd.StdinPipe(); err != nil {
if j.in, err = j.cmd.StdinPipe(); err != nil {
return nil, err
}

outPipe, err := a.cmd.StdoutPipe()
outPipe, err := j.cmd.StdoutPipe()
if err != nil {
return nil, err
}

a.out = bufio.NewReader(outPipe)
if err := a.cmd.Start(); err != nil {
j.out = bufio.NewReader(outPipe)
if err := j.cmd.Start(); err != nil {
return nil, err
}
return a, nil
}

// close releases any resources used by the addr2liner object.
func (d *addr2Liner) close() {
d.in.Close()
d.cmd.Wait()
a := &addr2Liner{
rw: j,
base: base,
}

return a, nil
}

func (d *addr2Liner) readString() (s string) {
if d.err != nil {
return ""
}
if s, d.err = d.out.ReadString('\n'); d.err != nil {
return ""
func (d *addr2Liner) readString() (string, error) {
s, err := d.rw.readLine()
if err != nil {
return "", err
}
return strings.TrimSpace(s)
return strings.TrimSpace(s), nil
}

// readFrame parses the addr2line output for a single address. It
// returns a populated plugin.Frame and whether it has reached the end of the
// data.
func (d *addr2Liner) readFrame() (plugin.Frame, bool) {
funcname := d.readString()

funcname, err := d.readString()
if err != nil {
return plugin.Frame{}, true
}
if strings.HasPrefix(funcname, "0x") {
// If addr2line returns a hex address we can assume it is the
// sentinel. Read and ignore next two lines of output from
Expand All @@ -108,8 +131,8 @@ func (d *addr2Liner) readFrame() (plugin.Frame, bool) {
return plugin.Frame{}, true
}

fileline := d.readString()
if d.err != nil {
fileline, err := d.readString()
if err != nil {
return plugin.Frame{}, true
}

Expand Down Expand Up @@ -142,26 +165,21 @@ func (d *addr2Liner) readFrame() (plugin.Frame, bool) {
// addrInfo returns the stack frame information for a specific program
// address. It returns nil if the address could not be identified.
func (d *addr2Liner) addrInfo(addr uint64) ([]plugin.Frame, error) {
if d.err != nil {
return nil, d.err
}

if _, d.err = fmt.Fprintf(d.in, "%x\n", addr-d.base); d.err != nil {
return nil, d.err
if err := d.rw.write(fmt.Sprintf("%x", addr-d.base)); err != nil {
return nil, err
}

if _, d.err = fmt.Fprintf(d.in, "%x\n", sentinel); d.err != nil {
return nil, d.err
if err := d.rw.write(fmt.Sprintf("%x", sentinel)); err != nil {
return nil, err
}

resp := d.readString()
if d.err != nil {
return nil, d.err
resp, err := d.readString()
if err != nil {
return nil, err
}

if !strings.HasPrefix(resp, "0x") {
d.err = fmt.Errorf("unexpected addr2line output: %s", resp)
return nil, d.err
return nil, fmt.Errorf("unexpected addr2line output: %s", resp)
}

var stack []plugin.Frame
Expand All @@ -175,5 +193,5 @@ func (d *addr2Liner) addrInfo(addr uint64) ([]plugin.Frame, error) {
stack = append(stack, frame)
}
}
return stack, d.err
return stack, nil
}
22 changes: 19 additions & 3 deletions internal/binutils/binutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,16 @@ func (b *Binutils) Disasm(file string, start, end uint64) ([]plugin.Inst, error)
// Update the command invocations if not initialized.
b.SetTools("")
}
return disassemble(b.objdump, file, start, end)
cmd := exec.Command(b.objdump, "-d", "-C", "--no-show-raw-insn", "-l",
fmt.Sprintf("--start-address=%#x", start),
fmt.Sprintf("--stop-address=%#x", end),
file)
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("%v: %v", cmd.Args, err)
}

return disassemble(out)
}

// Open satisfies the plugin.ObjTool interface.
Expand Down Expand Up @@ -191,7 +200,14 @@ func (f *file) Close() error {
}

func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
return findSymbols(f.b.nm, f.name, r, addr)
// Get from nm a list of symbols sorted by address.
cmd := exec.Command(f.b.nm, "-n", f.name)
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("%v: %v", cmd.Args, err)
}

return findSymbols(out, f.name, r, addr)
}

// fileNM implements the binutils.ObjFile interface, using 'nm' to map
Expand Down Expand Up @@ -235,7 +251,7 @@ func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {

func (f *fileAddr2Line) Close() error {
if f.addr2liner != nil {
f.addr2liner.close()
f.addr2liner.rw.close()
f.addr2liner = nil
}
return nil
Expand Down
53 changes: 47 additions & 6 deletions internal/binutils/binutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,7 @@ func functionName(level int) (name string) {
func TestAddr2Liner(t *testing.T) {
const offset = 0x500

a, err := newAddr2Liner("testdata/wrapper/addr2line", "executable", offset)
if err != nil {
t.Fatalf("Addr2Liner Open: %v", err)
}

a := addr2Liner{&mockAddr2liner{}, offset}
for i := 1; i < 8; i++ {
addr := i*0x1000 + offset
s, err := a.addrInfo(uint64(addr))
Expand All @@ -67,7 +63,52 @@ func TestAddr2Liner(t *testing.T) {
if len(s) != 0 {
t.Fatalf("AddrInfo(0xFFFF): got len==%d, want 0", len(s))
}
a.close()
a.rw.close()
}

type mockAddr2liner struct {
output []string
}

func (a *mockAddr2liner) write(s string) error {
var lines []string
switch s {
case "1000":
lines = []string{"_Z3fooid.clone2", "file1000:1000"}
case "2000":
lines = []string{"_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
case "3000":
lines = []string{"_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
case "4000":
lines = []string{"fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
case "5000":
lines = []string{"fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
case "6000":
lines = []string{"fun6000", "file6000:6000", "fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
case "7000":
lines = []string{"fun7000", "file7000:7000", "fun6000", "file6000:6000", "fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
case "8000":
lines = []string{"fun8000", "file8000:8000", "fun7000", "file7000:7000", "fun6000", "file6000:6000", "fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
case "9000":
lines = []string{"fun9000", "file9000:9000", "fun8000", "file8000:8000", "fun7000", "file7000:7000", "fun6000", "file6000:6000", "fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
default:
lines = []string{"??", "??:0"}
}
a.output = append(a.output, "0x"+s)
a.output = append(a.output, lines...)
return nil
}

func (a *mockAddr2liner) readLine() (string, error) {
if len(a.output) == 0 {
return "", fmt.Errorf("end of file")
}
next := a.output[0]
a.output = a.output[1:]
return next, nil
}

func (a *mockAddr2liner) close() {
}

func TestAddr2LinerLookup(t *testing.T) {
Expand Down
30 changes: 6 additions & 24 deletions internal/binutils/disasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ package binutils

import (
"bytes"
"fmt"
"io"
"os/exec"
"regexp"
"strconv"

Expand All @@ -32,19 +30,12 @@ var (
objdumpOutputFileLine = regexp.MustCompile(`^(.*):([0-9]+)`)
)

func findSymbols(nm, file string, r *regexp.Regexp, address uint64) ([]*plugin.Sym, error) {
// Get from nm a list of symbols sorted by address.
cmd := exec.Command(nm, "-n", file)
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("%v: %v", cmd.Args, err)
}

func findSymbols(syms []byte, file string, r *regexp.Regexp, address uint64) ([]*plugin.Sym, error) {
// Collect all symbols from the nm output, grouping names mapped to
// the same address into a single symbol.
var symbols []*plugin.Sym
names, start := []string{}, uint64(0)
buf := bytes.NewBuffer(out)
buf := bytes.NewBuffer(syms)
for symAddr, name, err := nextSymbol(buf); err == nil; symAddr, name, err = nextSymbol(buf) {
if err != nil {
return nil, err
Expand Down Expand Up @@ -88,19 +79,10 @@ func matchSymbol(names []string, start, end uint64, r *regexp.Regexp, address ui
return nil
}

// disassemble returns the assembly instructions in a function from a
// binary file. It uses objdump to obtain the assembly listing.
func disassemble(objdump string, file string, start, stop uint64) ([]plugin.Inst, error) {
cmd := exec.Command(objdump, "-d", "-C", "--no-show-raw-insn", "-l",
fmt.Sprintf("--start-address=%#x", start),
fmt.Sprintf("--stop-address=%#x", stop),
file)
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("%v: %v", cmd.Args, err)
}

buf := bytes.NewBuffer(out)
// disassemble parses the output of the objdump command and returns
// the assembly instructions in a slice.
func disassemble(asm []byte) ([]plugin.Inst, error) {
buf := bytes.NewBuffer(asm)
file, line := "", 0
var assembly []plugin.Inst
for {
Expand Down
Loading