proc: simplify and generalize runtime.mallocgc workaround (#3571)

Instead of having a different version for each architecture have a
single version that uses an architecture specific list of registers.
Also generalize it so that, if we want, we can extend the workaround to
other runtime functions we might want to call (for example the channel
send/receive functions).
This commit is contained in:
Alessandro Arzilli 2023-11-20 19:43:15 +01:00 committed by GitHub
parent 03e8dd77bf
commit a5b03f0623
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 64 additions and 101 deletions

@ -43,6 +43,7 @@ func AMD64Arch(goos string) *Arch {
RegnumToString: regnum.AMD64ToName, RegnumToString: regnum.AMD64ToName,
debugCallMinStackSize: 256, debugCallMinStackSize: 256,
maxRegArgBytes: 9*8 + 15*8, maxRegArgBytes: 9*8 + 15*8,
argumentRegs: []int{regnum.AMD64_Rax, regnum.AMD64_Rbx, regnum.AMD64_Rcx},
} }
} }

@ -55,6 +55,8 @@ type Arch struct {
// maxRegArgBytes is extra padding for ABI1 call injections, equivalent to // maxRegArgBytes is extra padding for ABI1 call injections, equivalent to
// the maximum space occupied by register arguments. // the maximum space occupied by register arguments.
maxRegArgBytes int maxRegArgBytes int
// argumentRegs are function call injection registers for runtimeOptimizedWorkaround
argumentRegs []int
// asmRegisters maps assembly register numbers to dwarf registers. // asmRegisters maps assembly register numbers to dwarf registers.
asmRegisters map[int]asmRegister asmRegisters map[int]asmRegister

@ -53,6 +53,7 @@ func ARM64Arch(goos string) *Arch {
RegnumToString: regnum.ARM64ToName, RegnumToString: regnum.ARM64ToName,
debugCallMinStackSize: 288, debugCallMinStackSize: 288,
maxRegArgBytes: 16*8 + 16*8, // 16 int argument registers plus 16 float argument registers maxRegArgBytes: 16*8 + 16*8, // 16 int argument registers plus 16 float argument registers
argumentRegs: []int{regnum.ARM64_X0, regnum.ARM64_X0 + 1, regnum.ARM64_X0 + 2},
} }
} }

@ -858,7 +858,7 @@ type Image struct {
compileUnits []*compileUnit // compileUnits is sorted by increasing DWARF offset compileUnits []*compileUnit // compileUnits is sorted by increasing DWARF offset
dwarfTreeCache *simplelru.LRU dwarfTreeCache *simplelru.LRU
runtimeMallocgcTree *godwarf.Tree // patched version of runtime.mallocgc's DIE workaroundCache map[dwarf.Offset]*godwarf.Tree
// runtimeTypeToDIE maps between the offset of a runtime._type in // runtimeTypeToDIE maps between the offset of a runtime._type in
// runtime.moduledata.types and the offset of the DIE in debug_info. This // runtime.moduledata.types and the offset of the DIE in debug_info. This
@ -1012,8 +1012,8 @@ func (image *Image) LoadError() error {
} }
func (image *Image) getDwarfTree(off dwarf.Offset) (*godwarf.Tree, error) { func (image *Image) getDwarfTree(off dwarf.Offset) (*godwarf.Tree, error) {
if image.runtimeMallocgcTree != nil && off == image.runtimeMallocgcTree.Offset { if image.workaroundCache[off] != nil {
return image.runtimeMallocgcTree, nil return image.workaroundCache[off], nil
} }
if r, ok := image.dwarfTreeCache.Get(off); ok { if r, ok := image.dwarfTreeCache.Get(off); ok {
return r.(*godwarf.Tree), nil return r.(*godwarf.Tree), nil
@ -2257,23 +2257,6 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB
sort.Strings(bi.Sources) sort.Strings(bi.Sources)
bi.Sources = uniq(bi.Sources) bi.Sources = uniq(bi.Sources)
if bi.regabi {
// prepare patch for runtime.mallocgc's DIE
fn := bi.lookupOneFunc("runtime.mallocgc")
if fn != nil && fn.cu.image == image {
tree, err := image.getDwarfTree(fn.offset)
if err == nil {
children, err := regabiMallocgcWorkaround(bi)
if err != nil {
bi.logger.Errorf("could not patch runtime.mallocgc: %v", err)
} else {
tree.Children = children
image.runtimeMallocgcTree = tree
}
}
}
}
if cont != nil { if cont != nil {
cont() cont()
} }

@ -573,7 +573,10 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i
return 0, nil, fmt.Errorf("DWARF read error: %v", err) return 0, nil, fmt.Errorf("DWARF read error: %v", err)
} }
if bi.regabi && fn.cu.optimized && fn.Name != "runtime.mallocgc" { if bi.regabi && fn.cu.optimized {
if runtimeWhitelist[fn.Name] {
runtimeOptimizedWorkaround(bi, fn.cu.image, dwarfTree)
} else {
// Debug info for function arguments on optimized functions is currently // Debug info for function arguments on optimized functions is currently
// too incomplete to attempt injecting calls to arbitrary optimized // too incomplete to attempt injecting calls to arbitrary optimized
// functions. // functions.
@ -583,6 +586,7 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i
// correct for call injection purposes. // correct for call injection purposes.
return 0, nil, fmt.Errorf("can not call optimized function %s when regabi is in use", fn.Name) return 0, nil, fmt.Errorf("can not call optimized function %s when regabi is in use", fn.Name)
} }
}
varEntries := reader.Variables(dwarfTree, fn.Entry, int(^uint(0)>>1), reader.VariablesSkipInlinedSubroutines) varEntries := reader.Variables(dwarfTree, fn.Entry, int(^uint(0)>>1), reader.VariablesSkipInlinedSubroutines)
@ -1214,82 +1218,53 @@ func debugCallProtocolReg(archName string, version int) (uint64, bool) {
} }
} }
type fakeEntry map[dwarf.Attr]*dwarf.Field // runtimeWhitelist is a list of functions in the runtime that we can call
// (through call injection) even if they are optimized.
func (e fakeEntry) Val(attr dwarf.Attr) interface{} { var runtimeWhitelist = map[string]bool{
if e[attr] == nil { "runtime.mallocgc": true,
return nil
} }
return e[attr].Val // runtimeOptimizedWorkaround modifies the input DIE so that arguments and
// return variables have the appropriate registers for call injection.
// This function can not be called on arbitrary DIEs, it is only valid for
// the functions specified in runtimeWhitelist.
// In particular this will fail if any of the arguments of the function
// passed in input does not fit in an integer CPU register.
func runtimeOptimizedWorkaround(bi *BinaryInfo, image *Image, in *godwarf.Tree) {
if image.workaroundCache == nil {
image.workaroundCache = make(map[dwarf.Offset]*godwarf.Tree)
}
if image.workaroundCache[in.Offset] == in {
return
}
image.workaroundCache[in.Offset] = in
curArg, curRet := 0, 0
for _, child := range in.Children {
if child.Tag == dwarf.TagFormalParameter {
childEntry, ok := child.Entry.(*dwarf.Entry)
if !ok {
panic("internal error: bad DIE for runtimeOptimizedWorkaround")
}
isret, _ := child.Entry.Val(dwarf.AttrVarParam).(bool)
var reg int
if isret {
reg = bi.Arch.argumentRegs[curRet]
curRet++
} else {
reg = bi.Arch.argumentRegs[curArg]
curArg++
} }
func (e fakeEntry) AttrField(attr dwarf.Attr) *dwarf.Field { newlocfield := dwarf.Field{Attr: dwarf.AttrLocation, Val: []byte{byte(op.DW_OP_reg0) + byte(reg)}, Class: dwarf.ClassBlock}
return e[attr]
}
func regabiMallocgcWorkaround(bi *BinaryInfo) ([]*godwarf.Tree, error) { locfield := childEntry.AttrField(dwarf.AttrLocation)
ptrToRuntimeType := "*" + bi.runtimeTypeTypename() if locfield != nil {
*locfield = newlocfield
var err1 error } else {
childEntry.Field = append(childEntry.Field, newlocfield)
t := func(name string) godwarf.Type {
if err1 != nil {
return nil
}
typ, err := bi.findType(name)
if err != nil {
err1 = err
return nil
}
return typ
}
m := func(name string, typ godwarf.Type, reg int, isret bool) *godwarf.Tree {
if err1 != nil {
return nil
}
var e fakeEntry = map[dwarf.Attr]*dwarf.Field{
dwarf.AttrName: &dwarf.Field{Attr: dwarf.AttrName, Val: name, Class: dwarf.ClassString},
dwarf.AttrType: &dwarf.Field{Attr: dwarf.AttrType, Val: typ.Common().Offset, Class: dwarf.ClassReference},
dwarf.AttrLocation: &dwarf.Field{Attr: dwarf.AttrLocation, Val: []byte{byte(op.DW_OP_reg0) + byte(reg)}, Class: dwarf.ClassBlock},
dwarf.AttrVarParam: &dwarf.Field{Attr: dwarf.AttrVarParam, Val: isret, Class: dwarf.ClassFlag},
}
return &godwarf.Tree{
Entry: e,
Tag: dwarf.TagFormalParameter,
} }
} }
switch bi.Arch.Name {
case "amd64":
r := []*godwarf.Tree{
m("size", t("uintptr"), regnum.AMD64_Rax, false),
m("typ", t(ptrToRuntimeType), regnum.AMD64_Rbx, false),
m("needzero", t("bool"), regnum.AMD64_Rcx, false),
m("~r1", t("unsafe.Pointer"), regnum.AMD64_Rax, true),
}
return r, err1
case "arm64":
r := []*godwarf.Tree{
m("size", t("uintptr"), regnum.ARM64_X0, false),
m("typ", t(ptrToRuntimeType), regnum.ARM64_X0+1, false),
m("needzero", t("bool"), regnum.ARM64_X0+2, false),
m("~r1", t("unsafe.Pointer"), regnum.ARM64_X0, true),
}
return r, err1
case "ppc64le":
r := []*godwarf.Tree{
m("size", t("uintptr"), regnum.PPC64LE_R0+3, false),
m("typ", t(ptrToRuntimeType), regnum.PPC64LE_R0+4, false),
m("needzero", t("bool"), regnum.PPC64LE_R0+5, false),
m("~r1", t("unsafe.Pointer"), regnum.PPC64LE_R0+3, true),
}
return r, err1
default:
// do nothing
return nil, nil
} }
} }

@ -41,6 +41,7 @@ func PPC64LEArch(goos string) *Arch {
RegisterNameToDwarf: nameToDwarfFunc(regnum.PPC64LENameToDwarf), RegisterNameToDwarf: nameToDwarfFunc(regnum.PPC64LENameToDwarf),
debugCallMinStackSize: 320, debugCallMinStackSize: 320,
maxRegArgBytes: 13*8 + 13*8, maxRegArgBytes: 13*8 + 13*8,
argumentRegs: []int{regnum.PPC64LE_R0 + 3, regnum.PPC64LE_R0 + 4, regnum.PPC64LE_R0 + 5},
} }
} }