Skip to content

Commit

Permalink
Add disassembly support on Mac OS X.
Browse files Browse the repository at this point in the history
  • Loading branch information
kalyanac committed May 19, 2020
1 parent 5987a5e commit cda73ae
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 8 deletions.
35 changes: 34 additions & 1 deletion internal/binutils/binutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"sync"

Expand Down Expand Up @@ -120,6 +121,29 @@ func (bu *Binutils) SetTools(config string) {
bu.update(func(r *binrep) { initTools(r, config) })
}

func ensureObjdumpVersion(objdump string, objdumpFound bool) (string, bool) {
if !objdumpFound || runtime.GOOS != "darwin" {
return objdump, objdumpFound
}

// Ensure objdump is at least version 9.0 on Mac OS
cmdOut, err := exec.Command(objdump, "-version").Output()
if err != nil {
return objdump, false
}

re := regexp.MustCompile(`.*version (([0-9]*)\.([0-9]*)\.([0-9]*)).*$`)
fields := re.FindStringSubmatch(strings.Split(string(cmdOut), "\n")[0])
if len(fields) != 5 {
return objdump, false
}

if ver, _ := strconv.Atoi(fields[2]); ver < 9 {
return objdump, false
}
return objdump, objdumpFound
}

func initTools(b *binrep, config string) {
// paths collect paths per tool; Key "" contains the default.
paths := make(map[string][]string)
Expand All @@ -140,7 +164,7 @@ func initTools(b *binrep, config string) {
b.addr2line, b.addr2lineFound = findExe("gaddr2line", append(paths["addr2line"], defaultPath...))
}
b.nm, b.nmFound = findExe("nm", append(paths["nm"], defaultPath...))
b.objdump, b.objdumpFound = findExe("objdump", append(paths["objdump"], defaultPath...))
b.objdump, b.objdumpFound = ensureObjdumpVersion(findExe("objdump", append(paths["objdump"], defaultPath...)))
}

// findExe looks for an executable command on a set of paths.
Expand All @@ -159,10 +183,19 @@ func findExe(cmd string, paths []string) (string, bool) {
// of a binary.
func (bu *Binutils) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) {
b := bu.get()
if !b.objdumpFound {
return nil, fmt.Errorf("couldn't find objdump")
}
args := []string{"-d", "-C", "--no-show-raw-insn", "-l",
fmt.Sprintf("--start-address=%#x", start),
fmt.Sprintf("--stop-address=%#x", end)}

if runtime.GOOS == "darwin" {
args = []string{"-disassemble-all", "-no-show-raw-insn",
"-line-numbers", fmt.Sprintf("-start-address=%#x", start),
fmt.Sprintf("-stop-address=%#x", end)}
}

if intelSyntax {
if runtime.GOOS == "darwin" {
args = append(args, "-x86-asm-syntax=intel")
Expand Down
35 changes: 32 additions & 3 deletions internal/binutils/binutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import (
"bytes"
"fmt"
"math"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strconv"
"strings"
"testing"

Expand Down Expand Up @@ -189,14 +191,39 @@ func skipUnlessDarwinAmd64(t *testing.T) {
}

func testDisasm(t *testing.T, intelSyntax bool) {
if runtime.GOOS == "darwin" {
// get objdump version and skip test if < 9.0
// binutils does not export get(). Try to get objdump version on our own.
// only works if objdump is in path.
cmdOut, err := exec.Command("objdump", "-version").Output()
if err != nil {
t.Skip("cannot determine objdump version.", err)
}

re := regexp.MustCompile(`.*version (([0-9]*)\.([0-9]*)\.([0-9]*)).*$`)
fields := re.FindStringSubmatch(strings.Split(string(cmdOut), "\n")[0])
if len(fields) != 5 {
t.Skip("cannot determine objdump version.", fields)
}

if ver, _ := strconv.Atoi(fields[2]); ver < 9 {
t.Skip("objdump version too old. ", fields[0])
}
}

bu := &Binutils{}
insts, err := bu.Disasm(filepath.Join("testdata", "exe_linux_64"), 0, math.MaxUint64, intelSyntax)
testexe := "exe_linux_64"
if runtime.GOOS == "darwin" {
testexe = "exe_mac_64"
}

insts, err := bu.Disasm(filepath.Join("testdata", testexe), 0, math.MaxUint64, intelSyntax)
if err != nil {
t.Fatalf("Disasm: unexpected error %v", err)
}
mainCount := 0
for _, x := range insts {
if x.Function == "main" {
if x.Function == "main" || x.Function == "_main" {
mainCount++
}
}
Expand All @@ -206,7 +233,9 @@ func testDisasm(t *testing.T, intelSyntax bool) {
}

func TestDisasm(t *testing.T) {
skipUnlessLinuxAmd64(t)
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
t.Skip("This test only works on Linux or Mac")
}
testDisasm(t, true)
testDisasm(t, false)
}
Expand Down
16 changes: 12 additions & 4 deletions internal/binutils/disasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@ import (
"bytes"
"io"
"regexp"
"runtime"
"strconv"

"github.com/google/pprof/internal/plugin"
"github.com/ianlancetaylor/demangle"
)

var (
nmOutputRE = regexp.MustCompile(`^\s*([[:xdigit:]]+)\s+(.)\s+(.*)`)
objdumpAsmOutputRE = regexp.MustCompile(`^\s*([[:xdigit:]]+):\s+(.*)`)
objdumpOutputFileLine = regexp.MustCompile(`^(.*):([0-9]+)`)
objdumpOutputFunction = regexp.MustCompile(`^(\S.*)\(\):`)
nmOutputRE = regexp.MustCompile(`^\s*([[:xdigit:]]+)\s+(.)\s+(.*)`)
objdumpAsmOutputRE = regexp.MustCompile(`^\s*([[:xdigit:]]+):\s+(.*)`)
objdumpOutputFileLine = regexp.MustCompile(`^(.*):([0-9]+)`)
objdumpOutputFunction = regexp.MustCompile(`^(\S.*)\(\):`)
objdumpOutputFunctionDarwin = regexp.MustCompile(`^(\S.*):`)
)

func findSymbols(syms []byte, file string, r *regexp.Regexp, address uint64) ([]*plugin.Sym, error) {
Expand Down Expand Up @@ -144,6 +146,12 @@ func disassemble(asm []byte) ([]plugin.Inst, error) {
function = fields[1]
continue
}
if runtime.GOOS == "darwin" {
if fields := objdumpOutputFunctionDarwin.FindStringSubmatch(input); len(fields) == 2 {
function = fields[1]
continue
}
}
// Reset on unrecognized lines.
function, file, line = "", "", 0
}
Expand Down

0 comments on commit cda73ae

Please sign in to comment.