proc: for optimized functions allow .closureptr to not exist (#3808)

* proc: flag variables correctly when range-over-func stmts are used

Argument variables of a range-over-func body closure should be returned
flagged as normal local variables, not as function arguments.

Updates #3806

* proc: for optimized functions allow .closureptr to not exist

For optimized functions .closureptr is sometimes omitted from DWARF,
allow it to be 0 and try to recover the range-over-func stack by best
effort.

Fixes #3806
This commit is contained in:
Alessandro Arzilli 2024-09-18 23:16:34 +02:00 committed by GitHub
parent b9fadbae9b
commit 582305a813
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 143 additions and 13 deletions

51
_fixtures/setiterator.go Normal file

@ -0,0 +1,51 @@
package main
import (
"fmt"
"iter"
"strconv"
"time"
)
func main() {
set := New[string]()
for i := 10; i < 100; i++ {
set.Add(strconv.Itoa(i))
}
PrintAllElements[string](set)
}
// Set holds a set of elements.
type Set[E comparable] struct {
m map[E]struct{}
}
// New returns a new [Set].
func New[E comparable]() *Set[E] {
return &Set[E]{m: make(map[E]struct{})}
}
// All is an iterator over the elements of s.
func (s *Set[E]) All() iter.Seq[E] {
return func(yield func(E) bool) {
for v := range s.m {
tmp := make([]byte, 1024)
str := string(tmp)
if !yield(v) {
return
}
go func() { println(str) }()
}
}
}
func (s *Set[E]) Add(v E) {
s.m[v] = struct{}{}
}
func PrintAllElements[E comparable](s *Set[E]) {
for v := range s.All() {
time.Sleep(100 * time.Second)
fmt.Println(v)
}
}

@ -463,7 +463,7 @@ type compileUnit struct {
entry *dwarf.Entry // debug_info entry describing this compile unit
isgo bool // true if this is the go compile unit
lineInfo *line.DebugLineInfo // debug_line segment associated with this compile unit
optimized bool // this compile unit is optimized
optimized optimizedFlags // this compile unit is optimized
producer string // producer attribute
offset dwarf.Offset // offset of the entry describing the compile unit
@ -471,6 +471,13 @@ type compileUnit struct {
image *Image // parent image of this compilation unit.
}
type optimizedFlags uint8
const (
optimizedInlined optimizedFlags = 1 << iota
optimizedOptimized
)
type fileLine struct {
file string
line int
@ -605,7 +612,7 @@ func (fn *Function) NameWithoutTypeParams() string {
// Optimized returns true if the function was optimized by the compiler.
func (fn *Function) Optimized() bool {
return fn.cu.optimized
return fn.cu.optimized != 0
}
// PrologueEndPC returns the PC just after the function prologue
@ -2472,9 +2479,18 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB
if cu.isgo && cu.producer != "" {
semicolon := strings.Index(cu.producer, ";")
if semicolon < 0 {
cu.optimized = goversion.ProducerAfterOrEqual(cu.producer, 1, 10)
cu.optimized = 0
if goversion.ProducerAfterOrEqual(cu.producer, 1, 10) {
cu.optimized = optimizedInlined | optimizedOptimized
}
} else {
cu.optimized = !strings.Contains(cu.producer[semicolon:], "-N") || !strings.Contains(cu.producer[semicolon:], "-l")
cu.optimized = optimizedInlined | optimizedOptimized
if strings.Contains(cu.producer[semicolon:], "-N") {
cu.optimized &^= optimizedOptimized
}
if strings.Contains(cu.producer[semicolon:], "-l") {
cu.optimized &^= optimizedInlined
}
const regabi = " regabi"
if i := strings.Index(cu.producer[semicolon:], regabi); i > 0 {
i += semicolon

@ -71,6 +71,10 @@ const (
// If localsOnlyRangeBodyClosures is set simpleLocals only returns
// variables containing the range body closure.
localsOnlyRangeBodyClosures
// If localsIsRangeBody is set DW_AT_formal_parameter variables will be
// considered local variables.
localsIsRangeBody
)
// ConvertEvalScope returns a new EvalScope in the context of the
@ -319,7 +323,12 @@ func (scope *EvalScope) Locals(flags localsFlags, wantedName string) ([]*Variabl
return vars2
}
vars0, err := scope.simpleLocals(flags, wantedName)
rangeBodyFlags := localsFlags(0)
if scope.Fn != nil && scope.Fn.rangeParentName() != "" {
rangeBodyFlags = localsFlags(localsIsRangeBody)
}
vars0, err := scope.simpleLocals(flags|rangeBodyFlags, wantedName)
if err != nil {
return nil, err
}
@ -344,7 +353,11 @@ func (scope *EvalScope) Locals(flags localsFlags, wantedName string) ([]*Variabl
scope2 = FrameToScope(scope.target, scope.target.Memory(), scope.g, scope.threadID, scope.rangeFrames[2*i:]...)
scope.enclosingRangeScopes[i] = scope2
}
vars, err := scope2.simpleLocals(flags, wantedName)
rangeBodyFlags := localsFlags(localsIsRangeBody)
if i == len(scope.enclosingRangeScopes)-1 {
rangeBodyFlags = 0
}
vars, err := scope2.simpleLocals(flags|rangeBodyFlags, wantedName)
if err != nil {
return nil, err
}
@ -377,7 +390,9 @@ func (scope *EvalScope) setupRangeFrames() error {
if err != nil {
return err
}
scope.rangeFrames = scope.rangeFrames[2:] // skip the first frame and its return frame
if len(scope.rangeFrames) > 0 {
scope.rangeFrames = scope.rangeFrames[2:] // skip the first frame and its return frame
}
scope.enclosingRangeScopes = make([]*EvalScope, len(scope.rangeFrames)/2)
return nil
}
@ -455,7 +470,7 @@ func (scope *EvalScope) simpleLocals(flags localsFlags, wantedName string) ([]*V
}
vars = append(vars, val)
depth := entry.Depth
if entry.Tag == dwarf.TagFormalParameter {
if (flags&localsIsRangeBody == 0) && (entry.Tag == dwarf.TagFormalParameter) {
if depth <= 1 {
depth = 0
}

@ -573,7 +573,7 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i
return 0, nil, fmt.Errorf("DWARF read error: %v", err)
}
if bi.regabi && fn.cu.optimized {
if bi.regabi && fn.Optimized() {
if runtimeWhitelist[fn.Name] {
runtimeOptimizedWorkaround(bi, fn.cu.image, dwarfTree)
} else {

@ -948,6 +948,10 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) {
nonMonotonicSP := false
var closurePtr int64
optimized := func(fn *Function) bool {
return fn.cu.optimized&optimizedOptimized != 0
}
appendFrame := func(fr Stackframe) {
frames = append(frames, fr)
if fr.closurePtr != 0 {
@ -960,6 +964,9 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) {
if fr.SystemStack {
return false
}
if closurePtr == 0 && optimized(fr.Call.Fn) {
return true
}
if closurePtr < 0 {
// closure is stack allocated, check that it is on this frame
return fr.contains(closurePtr)
@ -994,12 +1001,30 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) {
frames = append(frames, fr)
}
if fr.Call.Fn == nil {
if stage == startStage {
frames = nil
addRetFrame = false
stage = doneStage
return false
} else {
return true
}
}
switch stage {
case startStage:
appendFrame(fr)
rangeParent = fr.Call.Fn.extra(tgt.BinInfo()).rangeParent
stage = normalStage
if rangeParent == nil || closurePtr == 0 {
stop := false
if rangeParent == nil {
stop = true
}
if !optimized(fr.Call.Fn) && closurePtr == 0 {
stop = true
}
if stop {
frames = nil
addRetFrame = false
stage = doneStage
@ -1011,7 +1036,7 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) {
stage = lastFrameStage
} else if fr.Call.Fn.extra(tgt.BinInfo()).rangeParent == rangeParent && closurePtrOk(&fr) {
appendFrame(fr)
if closurePtr == 0 {
if !optimized(fr.Call.Fn) && closurePtr == 0 {
frames = nil
addRetFrame = false
stage = doneStage

@ -1836,8 +1836,31 @@ func TestCapturedVariable(t *testing.T) {
assertVariable(t, v, varTest{
name: "c",
preserveName: true,
value: "struct { main.name string; main.thing main.Thing } {name: \"Success\", thing: main.Thing {str: \"hello\"}}",
varType: "struct { main.name string; main.thing main.Thing }",
value: "struct { main.name string; main.thing main.Thing } {name: \"Success\", thing: main.Thing {str: \"hello\"}}",
varType: "struct { main.name string; main.thing main.Thing }",
})
})
}
func TestSetupRangeFramesCrash(t *testing.T) {
// See issue #3806
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 23) {
t.Skip("N/A")
}
for _, options := range []protest.BuildFlags{0, protest.EnableInlining | protest.EnableOptimization} {
withTestProcessArgs("setiterator", t, ".", []string{}, options, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
setFileBreakpoint(p, t, fixture.Source, 48)
assertNoError(grp.Continue(), t, "Continue")
scope, err := evalScope(p)
assertNoError(err, t, "EvalScope")
v, err := scope.LocalVariables(normalLoadConfig)
assertNoError(err, t, "LocalVariables")
t.Logf("%#v", v)
if len(v) != 1 {
t.Fatalf("wrong number of variables")
}
})
}
}