Skip to content

Commit

Permalink
Add support for LLVM objudmp version string with turnk
Browse files Browse the repository at this point in the history
  • Loading branch information
kalyanac committed Jun 3, 2020
2 parents c72b0b5 + 427632f commit ed96624
Show file tree
Hide file tree
Showing 20 changed files with 1,681 additions and 674 deletions.
34 changes: 34 additions & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,37 @@ the symbolization handler.

* **-symbolize=demangle=templates:** Demangle, and trim function parameters, but
not template parameters.

# Web Interface

When the user requests a web interface (by supplying an `-http=[host]:[port]`
argument on the command-line), pprof starts a web server and opens a browser
window pointing at that server. The web interface provided by the server allows
the user to interactively view profile data in multiple formats.

The top of the display is a header that contains some buttons and menus.

## Config

The `Config` menu allows the user to save the current refinement
settings (e.g., the focus and hide list) as a named configuration. A
saved configuration can later be re-applied to reinstitue the saved
refinements. The `Config` menu contains:

**Save as ...**: shows a dialog where the user can type in a
configuration name. The current refinement settings are saved under
the specified name.

**Default**: switches back to the default view by removing all refinements.

The `Config` menu also contains an entry per named
configuration. Selecting such an entry applies that configuration. The
currently selected entry is marked with a ✓. Clicking on the 🗙 on the
right-hand side of such an entry deletes the configuration (after
prompting the user to confirm).

## TODO: cover the following issues:

* Overall layout
* Menu entries
* Explanation of all the views
151 changes: 66 additions & 85 deletions internal/binutils/binutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type Binutils struct {
rep *binrep
}

var objdumpLLVMVerRE = regexp.MustCompile(`LLVM version (?:(\d*)\.(\d*)\.(\d*)|.*(trunk).*)`)

// binrep is an immutable representation for Binutils. It is atomically
// replaced on every mutation to provide thread-safe access.
type binrep struct {
Expand All @@ -52,7 +54,7 @@ type binrep struct {
nmFound bool
objdump string
objdumpFound bool
llvmObjdumpFound bool
isLLVMObjdump bool

// if fast, perform symbolization using nm (symbol names only),
// instead of file-line detail from the slower addr2line.
Expand Down Expand Up @@ -142,88 +144,76 @@ 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...))
// 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...)...))
b.objdump, b.isLLVMObjdump, b.objdumpFound = findObjdump(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.
// findObjdump finds and returns path to preferred objdump binary.
// Order of preference is: llvm-objdump, objdump.
// On MacOS only, also looks for gobjdump with least preference.
// Accepts a list of paths and returns:
// a string with path to the preferred objdump binary if found,
// or an empty string if not found;
// a boolen indicating if it is an LLVM objdump;
// a boolean if any acceptable objdump was 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)
objdumpNames := []string{"llvm-objdump", "objdump"}
if runtime.GOOS == "darwin" {
objdumpNames = append(objdumpNames, "gobjdump")
}

if objdump, objdumpFound := findExe("llvm-objdump-9", paths); objdumpFound {
allObjdump = append(allObjdump, objdump)
for _, objdumpName := range objdumpNames {
if objdump, objdumpFound := findExe(objdumpName, paths); objdumpFound {
cmdOut, err := exec.Command(objdump, "--version").Output()
if err != nil {
continue
}
if llvmObjdumpFound := isLLVMObjdump(string(cmdOut)); llvmObjdumpFound {
return objdump, llvmObjdumpFound, llvmObjdumpFound
}
if buObjdumpFound := isBuObjdump(string(cmdOut)); buObjdumpFound {
return objdump, false, buObjdumpFound
}
}
}
return "", false, false
}

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)
}
// isLLVMObjdump accepts a string with path to an objdump binary,
// and returns a boolean indicating if the given binary is an LLVM
// objdump binary of an acceptable version.
func isLLVMObjdump(output string) bool {
fields := objdumpLLVMVerRE.FindStringSubmatch(output)
if len(fields) != 5 {
return false
}
if !buObjdumpFound {
if objdump, objdumpFound := findExe("gobjdump", paths); objdumpFound {
buObjdump = objdump
buObjdumpFound = true
}
if fields[4] == "trunk" {
return 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
}
verMajor, err := strconv.Atoi(fields[1])
if err != nil {
return false
}

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
}
verPatch, err := strconv.Atoi(fields[3])
if err != nil {
return false
}
if runtime.GOOS == "linux" && verMajor >= 8 {
// Ensure LLVM objdump is at least version 8.0 on Linux.
// Some flags are not supported by previous versions.
return true
}
if runtime.GOOS == "darwin" {
// Ensure LLVM objdump is at least version 10.0.1 on MacOS.
return verMajor > 10 || (verMajor == 10 && verPatch >= 1)
}
return buObjdump, false, buObjdumpFound
return false
}

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
// isBuObjdump accepts a string with path to an objdump binary,
// and returns a boolean indicating if the given binary is a GNU
// binutils objdump binary. No version check is performed.
func isBuObjdump(output string) bool {
return strings.Contains(output, "GNU objdump") && strings.Contains(output, "Binutils")
}

// findExe looks for an executable command on a set of paths.
Expand All @@ -242,25 +232,16 @@ 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 && !b.llvmObjdumpFound {
if !b.objdumpFound {
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)}

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

if intelSyntax {
if b.llvmObjdumpFound {
args = append(args, "-x86-asm-syntax=intel")
if b.isLLVMObjdump {
args = append(args, "--x86-asm-syntax=intel")
} else {
args = append(args, "-M", "intel")
}
Expand Down
108 changes: 108 additions & 0 deletions internal/binutils/binutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ func skipUnlessDarwinAmd64(t *testing.T) {
}

func testDisasm(t *testing.T, intelSyntax bool) {
_, llvmObjdump, buObjdump := findObjdump([]string{""})
if !(llvmObjdump || buObjdump) {
t.Skip("Cannot find usable objdump")
}

bu := &Binutils{}
testexe := "exe_linux_64"
if runtime.GOOS == "darwin" {
Expand All @@ -202,6 +207,7 @@ func testDisasm(t *testing.T, intelSyntax bool) {
mainCount := 0
for _, x := range insts {
if x.Function == "main" || x.Function == "_main" {
// Mac symbols have a leading underscore.
mainCount++
}
}
Expand Down Expand Up @@ -408,3 +414,105 @@ func TestOpenMalformedMachO(t *testing.T) {
t.Errorf("Open: got %v, want error containing 'Mach-O'", err)
}
}

func TestObjdumpVersionChecks(t *testing.T) {
// Test that the objdump version strings are parsed properly.

var version string
if runtime.GOOS == "darwin" {
// Valid Apple LLVM version string with usable version.
version = `Apple LLVM version 11.0.3 (clang-1103.0.32.62)
Optimized build.
Default target: x86_64-apple-darwin19.4.0
Host CPU: skylake`
if runtime.GOOS == "darwin" && !isLLVMObjdump(version) {
t.Errorf("Valid Apple LLVM version string for isLLVMObjdump: got false, want true")
}

// Valid Apple LLVM version string with unusable version.
version = `Apple LLVM version 10.0.0 (clang-1000.11.45.5)
Optimized build.
Default target: x86_64-apple-darwin19.4.0
Host CPU: skylake`
if runtime.GOOS == "darwin" && isLLVMObjdump(version) {
t.Errorf("Unusable Apple LLVM version for isLLVMObjdump: got true, want false")
}

// Invalid Apple LLVM version string with usable version.
version = `Apple LLVM versions 11.0.3 (clang-1103.0.32.62)
Optimized build.
Default target: x86_64-apple-darwin19.4.0
Host CPU: skylake`
if runtime.GOOS == "darwin" && isLLVMObjdump(version) {
t.Errorf("Invalid Apple LLVM version string for isLLVMObjdump: got true, want false")
}
}

if runtime.GOOS == "linux" {
// Valid LLVM version string with usable version.
version = `LLVM (http://llvm.org/):
LLVM version 9.0.1
Optimized build.`
if runtime.GOOS == "linux" && !isLLVMObjdump(version) {
t.Errorf("Valid LLVM version string for isLLVMObjdump: got false, want true")
}

// Valid LLVM version string with unusable version.
version = `LLVM (http://llvm.org/):
LLVM version 6.0.1
Optimized build.`
if runtime.GOOS == "linux" && isLLVMObjdump(version) {
t.Errorf("Unusable LLVM version for isLLVMObjdump: got true, want false")
}

// Invalid LLVM version string with usable version.
version = `LLVM (http://llvm.org/):
LLVM versions 9.0.1
Optimized build.`
if runtime.GOOS == "linux" && isLLVMObjdump(version) {
t.Errorf("Invalid LLVM version string for isLLVMObjdump: got true, want false")
}
}

// Valid GNU objdump version string.
version = `GNU objdump (GNU Binutils) 2.34
Copyright (C) 2020 Free Software Foundation, Inc.`
if !isBuObjdump(version) {
t.Errorf("Valid GNU objdump version string for isBuObjdump: got false, want true")
}

// Invalid GNU objdump version string.
version = `GNU objdump (GNU Banutils) 2.34
Copyright (C) 2020 Free Software Foundation, Inc.`
if isBuObjdump(version) {
t.Errorf("Invalid GNU objdump version string for isBuObjdump: got true, want false")
}

// LLVM objdump version string contains trunk
version = `LLVM (http://llvm.org/):
LLVM version custom-trunk 124ffeb592a00bfe
Optimized build.`
if !isLLVMObjdump(version) {
t.Errorf("Valid LLVM objdump version string with trunk for isBuObjdump: got false, want true")
}

// Invalid LLVM objdump version string
version = `LLVM (http://llvm.org/):
LLVM version custom-trank 124ffeb592a00bfe
Optimized build.`
if isLLVMObjdump(version) {
t.Errorf("Invalid LLVM objdump version string with trunk for isBuObjdump: got true, want false")
}

// Invalid LLVM objdump version string
version = `LLVM (http://llvm.org/):
llvm version custom-trunk 124ffeb592a00bfe
Optimized build.`
if isLLVMObjdump(version) {
t.Errorf("Invalid LLVM objdump version string with trunk for isBuObjdump: got true, want false")
}

}
6 changes: 3 additions & 3 deletions internal/binutils/disasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import (
var (
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(.*):`)
objdumpOutputFileLine = regexp.MustCompile(`^;?\s?(.*):([0-9]+)`)
objdumpOutputFunction = regexp.MustCompile(`^;?\s?(\S.*)\(\):`)
objdumpOutputFunctionLLVM = regexp.MustCompile(`^([[:xdigit:]]+)?\s?(.*):`)
)

func findSymbols(syms []byte, file string, r *regexp.Regexp, address uint64) ([]*plugin.Sym, error) {
Expand Down
19 changes: 19 additions & 0 deletions internal/binutils/disasm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,25 @@ func TestFunctionAssembly(t *testing.T) {
{Addr: 0x2001, Text: "instruction two"},
},
},
{
plugin.Sym{Name: []string{"_main"}, Start: 0x30000, End: 0x3FFF},
`_main:
; /tmp/hello.c:3
30001: push %rbp`,
[]plugin.Inst{
{Addr: 0x30001, Text: "push %rbp", Function: "_main", File: "/tmp/hello.c", Line: 3},
},
},
{
plugin.Sym{Name: []string{"main"}, Start: 0x4000, End: 0x4FFF},
`000000000040052d <main>:
main():
/tmp/hello.c:3
40001: push %rbp`,
[]plugin.Inst{
{Addr: 0x40001, Text: "push %rbp", Function: "main", File: "/tmp/hello.c", Line: 3},
},
},
}

for _, tc := range testcases {
Expand Down
Loading

0 comments on commit ed96624

Please sign in to comment.