Skip to content

Commit

Permalink
Add support for LLVM objdump.
Browse files Browse the repository at this point in the history
  • Loading branch information
kalyanac committed May 20, 2020
1 parent 5987a5e commit c72b0b5
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 12 deletions.
105 changes: 100 additions & 5 deletions 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 All @@ -51,6 +52,7 @@ type binrep struct {
nmFound bool
objdump string
objdumpFound bool
llvmObjdumpFound bool

// if fast, perform symbolization using nm (symbol names only),
// instead of file-line detail from the slower addr2line.
Expand Down Expand Up @@ -140,7 +142,88 @@ 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...))
// MacOS binary is named objdump, Linux binary is named llvm-objdump-version
b.objdump, b.llvmObjdumpFound, b.objdumpFound = findObjdump(append(paths["llvm-objdump"], append(paths["objdump"], defaultPath...)...))
}

// findObjdump finds all binaries named objdump, llvm-objdump*. If multiple LLVM objdump are found,
// it picks objdump with the latest major version. If no LLVM objdump is found, returns the path to
// any binary named objdump, if found.
func findObjdump(paths []string) (string, bool, bool) {
allObjdump := findAllObjdump("llvm-objdump*", paths)

if objdump, objdumpFound := findExe("llvm-objdump", paths); objdumpFound {
allObjdump = append(allObjdump, objdump)
}

if objdump, objdumpFound := findExe("llvm-objdump-9", paths); objdumpFound {
allObjdump = append(allObjdump, objdump)
}

buObjdumpFound := false
var buObjdump string
if objdump, objdumpFound := findExe("objdump", paths); objdumpFound {
cmdOut, _ := exec.Command(objdump, "--version").Output()
if strings.Contains(string(cmdOut), "GNU") && strings.Contains(string(cmdOut), "Binutils") {
buObjdump = objdump
buObjdumpFound = true
} else {
allObjdump = append(allObjdump, objdump)
}
}
if !buObjdumpFound {
if objdump, objdumpFound := findExe("gobjdump", paths); objdumpFound {
buObjdump = objdump
buObjdumpFound = true
}
}

// Determine the versions of LLVM objdump only, ignore all errors.
var finalObjdump string
var finalVersion int
llvmObjdumpFound := false
for _, objdump := range allObjdump {
cmdOut, err := exec.Command(objdump, "--version").Output()
if err != nil {
continue
}

re := regexp.MustCompile(`LLVM\sversion (([0-9]*)\.([0-9]*)\.([0-9]*)).*\s`)
fields := re.FindStringSubmatch(string(cmdOut))
if len(fields) != 5 {
continue
}

// Select llvm-objdump based on the major version.
if ver, _ := strconv.Atoi(fields[2]); ver > finalVersion {
finalVersion = ver
llvmObjdumpFound = true
finalObjdump = objdump
}
}

if llvmObjdumpFound {
if runtime.GOOS == "darwin" && finalVersion >= 11 {
// Ensure objdump is at least version 11.0 on MacOS.
return finalObjdump, llvmObjdumpFound, false
}
if runtime.GOOS != "darwin" && finalVersion >= 9 {
// Ensure LLVM objdump is at least version 9.0 on Linux.
return finalObjdump, llvmObjdumpFound, false
}
}
return buObjdump, false, buObjdumpFound
}

func findAllObjdump(cmd string, paths []string) []string {
objdumpFound := []string{}
for _, p := range paths {
cp := filepath.Join(p, cmd)
if c, err := filepath.Glob(cp); err == nil {
objdumpFound = append(objdumpFound, c...)
}
}
return objdumpFound
}

// findExe looks for an executable command on a set of paths.
Expand All @@ -159,12 +242,24 @@ 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()
args := []string{"-d", "-C", "--no-show-raw-insn", "-l",
fmt.Sprintf("--start-address=%#x", start),
fmt.Sprintf("--stop-address=%#x", end)}
if !b.objdumpFound && !b.llvmObjdumpFound {
return nil, fmt.Errorf("couldn't find objdump")
}
var args []string

if b.llvmObjdumpFound {
args = []string{"--disassemble-all", "--no-show-raw-insn",
"--line-numbers", fmt.Sprintf("--start-address=%#x", start),
fmt.Sprintf("--stop-address=%#x", end)}
} else {
args = []string{"-d", "-C", "--no-show-raw-insn", "-l",
fmt.Sprintf("--start-address=%#x", start),
fmt.Sprintf("--stop-address=%#x", end)}

}

if intelSyntax {
if runtime.GOOS == "darwin" {
if b.llvmObjdumpFound {
args = append(args, "-x86-asm-syntax=intel")
} else {
args = append(args, "-M", "intel")
Expand Down
13 changes: 10 additions & 3 deletions internal/binutils/binutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,18 @@ func skipUnlessDarwinAmd64(t *testing.T) {

func testDisasm(t *testing.T, intelSyntax bool) {
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 +211,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
14 changes: 10 additions & 4 deletions internal/binutils/disasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ import (
)

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.*)\(\):`)
objdumpOutputFunctionLLVM = regexp.MustCompile(`^([[:xdigit:]]+)\s(.*):`)
)

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

0 comments on commit c72b0b5

Please sign in to comment.