diff --git a/internal/binutils/binutils.go b/internal/binutils/binutils.go index 967726d1f..5c97eb477 100644 --- a/internal/binutils/binutils.go +++ b/internal/binutils/binutils.go @@ -163,6 +163,14 @@ func (bu *Binutils) Disasm(file string, start, end uint64) ([]plugin.Inst, error fmt.Sprintf("--start-address=%#x", start), fmt.Sprintf("--stop-address=%#x", end), file) + + if runtime.GOOS == "darwin" { + cmd = exec.Command(b.objdump, "-disassemble-all", "-no-show-raw-insn", + "-line-numbers", 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) diff --git a/internal/report/report.go b/internal/report/report.go index 90b34a18b..c40e713cc 100644 --- a/internal/report/report.go +++ b/internal/report/report.go @@ -432,7 +432,13 @@ func PrintAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFuncs int) e ns := annotateAssembly(insts, sns, s.base) - fmt.Fprintf(w, "ROUTINE ======================== %s\n", demangle.Filter(s.sym.Name[0], options...)) + if strings.HasPrefix(s.sym.Name[0], "__Z") { + // Workaround to handle double leading underscores on Mac + // which are not properly handled by the demangle.Filter + fmt.Fprintf(w, "ROUTINE ======================== %s\n", demangle.Filter(s.sym.Name[0][1:], options...)) + } else { + fmt.Fprintf(w, "ROUTINE ======================== %s\n", demangle.Filter(s.sym.Name[0], options...)) + } for _, name := range s.sym.Name[1:] { fmt.Fprintf(w, " AKA ======================== %s\n", name) } diff --git a/internal/report/report_test.go b/internal/report/report_test.go index 7c4363fad..86b170bd6 100644 --- a/internal/report/report_test.go +++ b/internal/report/report_test.go @@ -17,8 +17,11 @@ package report import ( "bytes" "io/ioutil" + "os/exec" "regexp" "runtime" + "strconv" + "strings" "testing" "github.com/google/pprof/internal/binutils" @@ -100,6 +103,15 @@ var testM = []*profile.Mapping{ HasLineNumbers: true, HasInlineFrames: true, }, + { + // Entry for disassembly demangle test + ID: 2, + File: "testdata/disasm.bin", + HasFunctions: true, + HasFilenames: true, + HasLineNumbers: true, + HasInlineFrames: true, + }, } var testF = []*profile.Function{ @@ -123,6 +135,12 @@ var testF = []*profile.Function{ Name: "tee", Filename: "/some/path/testdata/source2", }, + { + // Entry for disassembly demangle test + ID: 5, + Name: "_ZStL8__ioinit", + Filename: "testdata/sample/disasm.cc", + }, } var testL = []*profile.Location{ @@ -176,6 +194,19 @@ var testL = []*profile.Location{ }, }, }, + { + // Entry for disassembly demangle test + ID: 6, + Mapping: testM[1], + // Update the following address if the binary disasm.bin is rebuilt + Address: 2101617, + Line: []profile.Line{ + { + Function: testF[4], + Line: 2, + }, + }, + }, } var testProfile = &profile.Profile{ @@ -213,6 +244,86 @@ var testProfile = &profile.Profile{ Mapping: testM, } +func TestPrintAssembly(t *testing.T) { + + if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { + t.Skip("This test only works on Linux or Mac") + } + + demangled := "ioinit" + + 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.Fatalf("cannot determine objdump version. %s", 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.Fatalf("cannot determine objdump version. %s", fields) + } + + if ver, _ := strconv.Atoi(fields[2]); ver < 9 { + t.Skip("objdump version too old. ", fields[0]) + } + + testM[1].File = "testdata/disasm.mac.bin" + testM[1].Start = 0x100000000 + testL[5].Address = 0 + testF[4].Name = "__ZNSt3__111char_traitsIcE11eq_int_typeEii" + // Update the following address if the binary, disasm.mac.bin is rebuilt + testL[5].Address = 0x100001cd0 + demangled = "eq_int_type" + } + + sampleValue1 := func(v []int64) int64 { + return v[1] + } + + asmProfile := testProfile.Copy() + asmProfile.Sample = []*profile.Sample{ + { + Location: []*profile.Location{testL[5]}, + Value: []int64{1, 1000}, + }, + } + + tc := testcase{ + rpt: New( + asmProfile, + &Options{ + OutputFormat: Dis, + Symbol: regexp.MustCompile(`.`), + TrimPath: "/some/path", + SampleValue: sampleValue1, + }, + ), + want: demangled, + } + + var b bytes.Buffer + if err := Generate(&b, tc.rpt, &binutils.Binutils{}); err != nil { + t.Fatalf("%s: %v", tc.want, err) + } + + r := regexp.MustCompile(`( *ROUTINE.*={24} )(.*)`) + fields := r.FindStringSubmatch(b.String()) + if len(fields) != 3 { + t.Fatalf("want: %s\n got: %s\n", tc.want, string(b.String())) + } + + demangledName := fields[2] + + if !(strings.Contains(demangledName, tc.want)) || + (strings.Contains(demangledName, "_Z")) { + t.Fatalf("want: %s\n got: %s\n", tc.want, string(b.String())) + } +} + func TestDisambiguation(t *testing.T) { parent1 := &graph.Node{Info: graph.NodeInfo{Name: "parent1"}} parent2 := &graph.Node{Info: graph.NodeInfo{Name: "parent2"}} diff --git a/internal/report/testdata/README.md b/internal/report/testdata/README.md index 2b60fcca6..eee3b7ef7 100644 --- a/internal/report/testdata/README.md +++ b/internal/report/testdata/README.md @@ -8,3 +8,11 @@ To update the binary and profile: go build -o sample.bin ./sample ./sample.bin -cpuprofile sample.cpu ``` + +To update the binary used for PrintAssembly test: +```shell +g++ -o disasm[.mac].bin disasm.cc +``` + +The address for testL[5] need to be manually updated +with the new adddess for the corresponding symbols diff --git a/internal/report/testdata/disasm.bin b/internal/report/testdata/disasm.bin new file mode 100755 index 000000000..6f30db148 Binary files /dev/null and b/internal/report/testdata/disasm.bin differ diff --git a/internal/report/testdata/disasm.mac.bin b/internal/report/testdata/disasm.mac.bin new file mode 100755 index 000000000..e3bd32a77 Binary files /dev/null and b/internal/report/testdata/disasm.mac.bin differ diff --git a/internal/report/testdata/sample/disasm.cc b/internal/report/testdata/sample/disasm.cc new file mode 100644 index 000000000..cc5c75102 --- /dev/null +++ b/internal/report/testdata/sample/disasm.cc @@ -0,0 +1,24 @@ +// Copyright 2019 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// sample program that is used to produce some of the files in +// pprof/internal/report/testdata. + +#include + +using namespace std; +int main(int argc, char** argv) { + cout << "Hello World!" << endl; + return 0; +}