diff --git a/pkg/locspec/locations.go b/pkg/locspec/locations.go index fd01f4a2..c16a5b24 100644 --- a/pkg/locspec/locations.go +++ b/pkg/locspec/locations.go @@ -450,7 +450,8 @@ func (loc *NormalLocationSpec) findFuncCandidates(bi *proc.BinaryInfo, limit int } candidateFuncs[fname] = struct{}{} } - for _, f := range bi.LookupFunc { + for _, fns := range bi.LookupFunc() { + f := fns[0] if len(candidateFuncs) >= limit { break } diff --git a/pkg/proc/amd64_arch.go b/pkg/proc/amd64_arch.go index a39e94c9..1d4ab4b9 100644 --- a/pkg/proc/amd64_arch.go +++ b/pkg/proc/amd64_arch.go @@ -49,7 +49,7 @@ func AMD64Arch(goos string) *Arch { func amd64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext { a := bi.Arch if a.sigreturnfn == nil { - a.sigreturnfn = bi.LookupFunc["runtime.sigreturn"] + a.sigreturnfn = bi.lookupOneFunc("runtime.sigreturn") } if fctxt == nil || (a.sigreturnfn != nil && pc >= a.sigreturnfn.Entry && pc < a.sigreturnfn.End) { @@ -96,7 +96,7 @@ func amd64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary } if a.crosscall2fn == nil { - a.crosscall2fn = bi.LookupFunc["crosscall2"] + a.crosscall2fn = bi.lookupOneFunc("crosscall2") } if a.crosscall2fn != nil && pc >= a.crosscall2fn.Entry && pc < a.crosscall2fn.End { diff --git a/pkg/proc/arm64_arch.go b/pkg/proc/arm64_arch.go index 01272e69..46ddd463 100644 --- a/pkg/proc/arm64_arch.go +++ b/pkg/proc/arm64_arch.go @@ -60,7 +60,7 @@ func ARM64Arch(goos string) *Arch { func arm64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext { a := bi.Arch if a.sigreturnfn == nil { - a.sigreturnfn = bi.LookupFunc["runtime.sigreturn"] + a.sigreturnfn = bi.lookupOneFunc("runtime.sigreturn") } if fctxt == nil || (a.sigreturnfn != nil && pc >= a.sigreturnfn.Entry && pc < a.sigreturnfn.End) { @@ -107,7 +107,7 @@ func arm64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary } if a.crosscall2fn == nil { - a.crosscall2fn = bi.LookupFunc["crosscall2"] + a.crosscall2fn = bi.lookupOneFunc("crosscall2") } if a.crosscall2fn != nil && pc >= a.crosscall2fn.Entry && pc < a.crosscall2fn.End { diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 950bdda1..5f862a9e 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -60,8 +60,8 @@ type BinaryInfo struct { Functions []Function // Sources is a list of all source files found in debug_line. Sources []string - // LookupFunc maps function names to a description of the function. - LookupFunc map[string]*Function + // lookupFunc maps function names to a description of the function. + lookupFunc map[string][]*Function // lookupGenericFunc maps function names, with their type parameters removed, to functions. // Functions that are not generic are not added to this map. lookupGenericFunc map[string][]*Function @@ -311,8 +311,8 @@ func allInlineCallRanges(tree *godwarf.Tree) []inlRange { // FindFunction returns the functions with name funcName. func (bi *BinaryInfo) FindFunction(funcName string) ([]*Function, error) { - if fn := bi.LookupFunc[funcName]; fn != nil { - return []*Function{fn}, nil + if fns := bi.LookupFunc()[funcName]; fns != nil { + return fns, nil } fns := bi.LookupGenericFunc()[funcName] if len(fns) == 0 { @@ -393,7 +393,7 @@ func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error } func findRetPC(t *Target, name string) ([]uint64, error) { - fn := t.BinInfo().LookupFunc[name] + fn := t.BinInfo().lookupOneFunc(name) if fn == nil { return nil, fmt.Errorf("could not find %s", name) } @@ -2111,11 +2111,8 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB sort.Sort(functionsDebugInfoByEntry(bi.Functions)) sort.Sort(packageVarsByAddr(bi.packageVars)) - bi.LookupFunc = make(map[string]*Function) + bi.lookupFunc = nil bi.lookupGenericFunc = nil - for i := range bi.Functions { - bi.LookupFunc[bi.Functions[i].Name] = &bi.Functions[i] - } for _, cu := range image.compileUnits { if cu.lineInfo != nil { @@ -2129,7 +2126,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB if bi.regabi { // prepare patch for runtime.mallocgc's DIE - fn := bi.LookupFunc["runtime.mallocgc"] + fn := bi.lookupOneFunc("runtime.mallocgc") if fn != nil && fn.cu.image == image { tree, err := image.getDwarfTree(fn.offset) if err == nil { @@ -2166,6 +2163,25 @@ func (bi *BinaryInfo) LookupGenericFunc() map[string][]*Function { return bi.lookupGenericFunc } +func (bi *BinaryInfo) LookupFunc() map[string][]*Function { + if bi.lookupFunc == nil { + bi.lookupFunc = make(map[string][]*Function) + for i := range bi.Functions { + name := bi.Functions[i].Name + bi.lookupFunc[name] = append(bi.lookupFunc[name], &bi.Functions[i]) + } + } + return bi.lookupFunc +} + +func (bi *BinaryInfo) lookupOneFunc(name string) *Function { + fns := bi.LookupFunc()[name] + if fns == nil { + return nil + } + return fns[0] +} + // loadDebugInfoMapsCompileUnit loads entry from a single compile unit. func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContext, image *Image, reader *reader.Reader, cu *compileUnit) { hasAttrGoPkgName := goversion.ProducerAfterOrEqual(cu.producer, 1, 13) diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go index ce17cccb..fb35aba2 100644 --- a/pkg/proc/breakpoints.go +++ b/pkg/proc/breakpoints.go @@ -674,8 +674,13 @@ func (t *Target) setBreakpointInternal(logicalID int, addr uint64, kind Breakpoi if breaklet != nil && breaklet.Cond == nil { breaklet.Cond = lbp.Cond } - lbp.File = bp.File - lbp.Line = bp.Line + if lbp.File == "" && lbp.Line == 0 { + lbp.File = bp.File + lbp.Line = bp.Line + } else if bp.File != lbp.File || bp.Line != lbp.Line { + lbp.File = "" + lbp.Line = 0 + } fn := t.BinInfo().PCToFunc(bp.Addr) if fn != nil { lbp.FunctionName = fn.NameWithoutTypeParams() diff --git a/pkg/proc/dwarf_expr_test.go b/pkg/proc/dwarf_expr_test.go index 89acaaec..88c9a46e 100644 --- a/pkg/proc/dwarf_expr_test.go +++ b/pkg/proc/dwarf_expr_test.go @@ -153,7 +153,7 @@ func TestDwarfExprRegisters(t *testing.T) { bi, _ := fakeBinaryInfo(t, dwb) - mainfn := bi.LookupFunc["main.main"] + mainfn := bi.LookupFunc()["main.main"][0] mem := newFakeMemory(fakeCFA(), uint64(0), uint64(testCases["b"])) regs := linutil.AMD64Registers{Regs: &linutil.AMD64PtraceRegs{}} regs.Regs.Rax = uint64(testCases["a"]) @@ -215,7 +215,7 @@ func TestDwarfExprComposite(t *testing.T) { bi, _ := fakeBinaryInfo(t, dwb) - mainfn := bi.LookupFunc["main.main"] + mainfn := bi.LookupFunc()["main.main"][0] mem := newFakeMemory(fakeCFA(), uint64(0), uint64(0), uint16(testCases["pair.v"]), []byte(stringVal)) var regs linutil.AMD64Registers @@ -289,7 +289,7 @@ func TestDwarfExprLoclist(t *testing.T) { bi, _ := fakeBinaryInfo(t, dwb) - mainfn := bi.LookupFunc["main.main"] + mainfn := bi.LookupFunc()["main.main"][0] mem := newFakeMemory(fakeCFA(), uint16(before), uint16(after)) const PC = 0x40100 @@ -326,7 +326,7 @@ func TestIssue1419(t *testing.T) { bi, _ := fakeBinaryInfo(t, dwb) - mainfn := bi.LookupFunc["main.main"] + mainfn := bi.LookupFunc()["main.main"][0] mem := newFakeMemory(fakeCFA()) diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index 1afa2294..83ff7d3d 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -2280,8 +2280,8 @@ func (v *Variable) findMethod(mname string) (*Variable, error) { //TODO(aarzilli): support generic functions? - if fn, ok := v.bi.LookupFunc[fmt.Sprintf("%s.%s.%s", pkg, receiver, mname)]; ok { - r, err := functionToVariable(fn, v.bi, v.mem) + if fns := v.bi.LookupFunc()[fmt.Sprintf("%s.%s.%s", pkg, receiver, mname)]; len(fns) == 1 { + r, err := functionToVariable(fns[0], v.bi, v.mem) if err != nil { return nil, err } @@ -2293,8 +2293,8 @@ func (v *Variable) findMethod(mname string) (*Variable, error) { return r, nil } - if fn, ok := v.bi.LookupFunc[fmt.Sprintf("%s.(*%s).%s", pkg, receiver, mname)]; ok { - r, err := functionToVariable(fn, v.bi, v.mem) + if fns := v.bi.LookupFunc()[fmt.Sprintf("%s.(*%s).%s", pkg, receiver, mname)]; len(fns) == 1 { + r, err := functionToVariable(fns[0], v.bi, v.mem) if err != nil { return nil, err } diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go index 5e9b3bc6..13100434 100644 --- a/pkg/proc/fncall.go +++ b/pkg/proc/fncall.go @@ -1211,8 +1211,8 @@ func findCallInjectionStateForThread(t *Target, thread Thread) (*G, *callInjecti func debugCallFunction(bi *BinaryInfo) (*Function, int) { for version := maxDebugCallVersion; version >= 1; version-- { name := debugCallFunctionNamePrefix2 + "V" + strconv.Itoa(version) - fn, ok := bi.LookupFunc[name] - if ok && fn != nil { + fn := bi.lookupOneFunc(name) + if fn != nil { return fn, version } } diff --git a/pkg/proc/i386_arch.go b/pkg/proc/i386_arch.go index 50b9d41c..aaafad0e 100644 --- a/pkg/proc/i386_arch.go +++ b/pkg/proc/i386_arch.go @@ -43,7 +43,7 @@ func I386Arch(goos string) *Arch { func i386FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext { i := bi.Arch if i.sigreturnfn == nil { - i.sigreturnfn = bi.LookupFunc["runtime.sigreturn"] + i.sigreturnfn = bi.lookupOneFunc("runtime.sigreturn") } if fctxt == nil || (i.sigreturnfn != nil && pc >= i.sigreturnfn.Entry && pc < i.sigreturnfn.End) { @@ -90,7 +90,7 @@ func i386FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryI } if i.crosscall2fn == nil { - i.crosscall2fn = bi.LookupFunc["crosscall2"] + i.crosscall2fn = bi.lookupOneFunc("crosscall2") } // TODO(chainhelen), need to check whether there is a bad frame descriptor like amd64. diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index 4686ebe6..e549928b 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -827,10 +827,11 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf return errors.New("too many arguments in traced function, max is 12 input+return") } - fn, ok := dbp.bi.LookupFunc[fnName] - if !ok { + fns := dbp.bi.LookupFunc()[fnName] + if len(fns) != 1 { return fmt.Errorf("could not find function: %s", fnName) } + fn := fns[0] offset, err := dbp.BinInfo().GStructOffset(dbp.Memory()) if err != nil { diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 61df0f78..3c18975e 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -209,23 +209,35 @@ func TestExitAfterContinue(t *testing.T) { } func setFunctionBreakpoint(p *proc.Target, t testing.TB, fname string) *proc.Breakpoint { - _, f, l, _ := runtime.Caller(1) - f = filepath.Base(f) - + t.Helper() addrs, err := proc.FindFunctionLocation(p, fname, 0) if err != nil { - t.Fatalf("%s:%d: FindFunctionLocation(%s): %v", f, l, fname, err) + t.Fatalf("FindFunctionLocation(%s): %v", fname, err) } if len(addrs) != 1 { - t.Fatalf("%s:%d: setFunctionBreakpoint(%s): too many results %v", f, l, fname, addrs) + t.Fatalf("setFunctionBreakpoint(%s): too many results %v", fname, addrs) } bp, err := p.SetBreakpoint(int(addrs[0]), addrs[0], proc.UserBreakpoint, nil) if err != nil { - t.Fatalf("%s:%d: FindFunctionLocation(%s): %v", f, l, fname, err) + t.Fatalf("FindFunctionLocation(%s): %v", fname, err) } return bp } +func setFunctionBreakpointAll(p *proc.Target, t testing.TB, fname string) { + t.Helper() + addrs, err := proc.FindFunctionLocation(p, fname, 0) + if err != nil { + t.Fatalf("FindFunctionLocation(%s): %v", fname, err) + } + for _, addr := range addrs { + _, err := p.SetBreakpoint(int(addr), addr, proc.UserBreakpoint, nil) + if err != nil { + t.Fatalf("FindFunctionLocation(%s): %v", fname, err) + } + } +} + func setFileBreakpoint(p *proc.Target, t testing.TB, path string, lineno int) *proc.Breakpoint { _, f, l, _ := runtime.Caller(1) f = filepath.Base(f) @@ -1927,7 +1939,7 @@ func TestIssue332_Part2(t *testing.T) { assertNoError(err, t, "Registers()") pc := regs.PC() pcAfterPrologue := findFunctionLocation(p, t, "main.changeMe") - if pcAfterPrologue == p.BinInfo().LookupFunc["main.changeMe"].Entry { + if pcAfterPrologue == p.BinInfo().LookupFunc()["main.changeMe"][0].Entry { t.Fatalf("main.changeMe and main.changeMe:0 are the same (%x)", pcAfterPrologue) } if pc != pcAfterPrologue { @@ -2252,7 +2264,7 @@ func TestIssue573(t *testing.T) { func TestTestvariables2Prologue(t *testing.T) { withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { - addrEntry := p.BinInfo().LookupFunc["main.main"].Entry + addrEntry := p.BinInfo().LookupFunc()["main.main"][0].Entry addrPrologue := findFunctionLocation(p, t, "main.main") if addrEntry == addrPrologue { t.Fatalf("Prologue detection failed on testvariables2.go/main.main") @@ -3483,7 +3495,7 @@ func TestSystemstackOnRuntimeNewstack(t *testing.T) { assertNoError(err, t, "GetG") mainGoroutineID := g.ID - setFunctionBreakpoint(p, t, "runtime.newstack") + setFunctionBreakpointAll(p, t, "runtime.newstack") for { assertNoError(grp.Continue(), t, "second continue") g, err = proc.GetG(p.CurrentThread()) @@ -3710,7 +3722,7 @@ func TestDisassembleGlobalVars(t *testing.T) { t.Skip("On 386 linux when pie, symLookup can't look up global variables") } withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { - mainfn := p.BinInfo().LookupFunc["main.main"] + mainfn := p.BinInfo().LookupFunc()["main.main"][0] regs, _ := p.CurrentThread().Registers() text, err := proc.Disassemble(p.Memory(), regs, p.Breakpoints(), p.BinInfo(), mainfn.Entry, mainfn.End) assertNoError(err, t, "Disassemble") @@ -4140,7 +4152,7 @@ func TestStepOutReturn(t *testing.T) { func TestOptimizationCheck(t *testing.T) { withTestProcess("continuetestprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { - fn := p.BinInfo().LookupFunc["main.main"] + fn := p.BinInfo().LookupFunc()["main.main"][0] if fn.Optimized() { t.Fatalf("main.main is optimized") } @@ -4148,7 +4160,7 @@ func TestOptimizationCheck(t *testing.T) { if goversion.VersionAfterOrEqual(runtime.Version(), 1, 10) { withTestProcessArgs("continuetestprog", t, ".", []string{}, protest.EnableOptimization|protest.EnableInlining, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { - fn := p.BinInfo().LookupFunc["main.main"] + fn := p.BinInfo().LookupFunc()["main.main"][0] if !fn.Optimized() { t.Fatalf("main.main is not optimized") } @@ -5818,7 +5830,7 @@ func TestCallInjectionFlagCorruption(t *testing.T) { protest.MustSupportFunctionCalls(t, testBackend) withTestProcessArgs("badflags", t, ".", []string{"0"}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { - mainfn := p.BinInfo().LookupFunc["main.main"] + mainfn := p.BinInfo().LookupFunc()["main.main"][0] // Find JNZ instruction on line :14 var addr uint64 diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index 068ff105..4ea1dd1d 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -2991,3 +2991,29 @@ func TestClientServer_autoBreakpoints(t *testing.T) { } }) } + +func TestClientServer_breakpointOnFuncWithABIWrapper(t *testing.T) { + // Setting a breakpoint on an assembly function that has an ABI + // compatibility wrapper should end up setting a breakpoint on the real + // function (also setting a breakpoint on the wrapper is fine). + // Issue #3296 + protest.AllowRecording(t) + withTestClient2("math", t, func(c service.Client) { + bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "runtime.schedinit"}) + assertNoError(err, t, "CreateBreakpoin()") + t.Log(bp) + + found := false + for _, pc := range bp.Addrs { + text, err := c.DisassemblePC(api.EvalScope{}, pc, api.IntelFlavour) + assertNoError(err, t, fmt.Sprint("DisassemblePC", pc)) + t.Log("First instruction for", pc, text[0]) + if strings.HasSuffix(text[0].Loc.File, "runtime/proc.go") { + found = true + } + } + if !found { + t.Error("breakpoint not set on the runtime/proc.go function") + } + }) +}