proc: support multiple functions with the same name (#3297)

The compiler produces ABI compatibility wrappers for some functions.
We have changed the support for breakpoints to allow a single logical
breakpoint to correspond to multiple physical breakpoints, take
advantage of that to set breakpoints on both the ABI wrapper and the
real function.

Fixes #3296
This commit is contained in:
Alessandro Arzilli 2023-03-22 19:38:09 +01:00 committed by GitHub
parent 1522382336
commit 3507ff977a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 105 additions and 44 deletions

@ -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
}

@ -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 {

@ -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 {

@ -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)

@ -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 = "<multiple locations>"
lbp.Line = 0
}
fn := t.BinInfo().PCToFunc(bp.Addr)
if fn != nil {
lbp.FunctionName = fn.NameWithoutTypeParams()

@ -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())

@ -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
}

@ -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
}
}

@ -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.

@ -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 {

@ -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

@ -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")
}
})
}