From 7bbed4fd6e4a556ac348f4eb3bfff4f4e74277e1 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Tue, 24 Jun 2025 17:15:22 -0700 Subject: [PATCH 1/3] cmd/compile: fix loclist for heap return vars without optimizations When compiling without optimizations certain variables such as return params end up missing location lists. Fixes #65405 --- src/cmd/compile/internal/dwarfgen/dwarf.go | 45 ++++++-- src/cmd/compile/internal/ssa/debug.go | 16 +-- src/cmd/compile/internal/ssagen/ssa.go | 3 + src/cmd/link/dwarf_test.go | 100 ++++++++++++++++++ .../link/testdata/dwarf/issue65405/main.go | 8 ++ 5 files changed, 157 insertions(+), 15 deletions(-) create mode 100644 src/cmd/link/testdata/dwarf/issue65405/main.go diff --git a/src/cmd/compile/internal/dwarfgen/dwarf.go b/src/cmd/compile/internal/dwarfgen/dwarf.go index fa13f07fdf37ec..2bed167ce1641b 100644 --- a/src/cmd/compile/internal/dwarfgen/dwarf.go +++ b/src/cmd/compile/internal/dwarfgen/dwarf.go @@ -248,11 +248,6 @@ func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir if n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT { tag = dwarf.DW_TAG_formal_parameter } - if n.Esc() == ir.EscHeap { - // The variable in question has been promoted to the heap. - // Its address is in n.Heapaddr. - // TODO(thanm): generate a better location expression - } inlIndex := 0 if base.Flag.GenDwarfInl > 1 { if n.InlFormal() || n.InlLocal() { @@ -263,7 +258,7 @@ func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir } } declpos := base.Ctxt.InnermostPos(n.Pos()) - vars = append(vars, &dwarf.Var{ + dvar := &dwarf.Var{ Name: n.Sym().Name, IsReturnValue: isReturnValue, Tag: tag, @@ -277,8 +272,19 @@ func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir ChildIndex: -1, DictIndex: n.DictIndex, ClosureOffset: closureOffset(n, closureVars), - }) - // Record go type of to insure that it gets emitted by the linker. + } + if n.Esc() == ir.EscHeap { + if n.Heapaddr == nil { + panic("invalid heap allocated var without Heapaddr") + } + debug := fn.DebugInfo.(*ssa.FuncDebug) + list := createHeapDerefLocationList(n, fnsym, debug.EntryID, ssa.FuncEnd.ID) + dvar.PutLocationList = func(listSym, startPC dwarf.Sym) { + debug.PutLocationList(list, base.Ctxt, listSym.(*obj.LSym), startPC.(*obj.LSym)) + } + } + vars = append(vars, dvar) + // Record go type to ensure that it gets emitted by the linker. fnsym.Func().RecordAutoType(reflectdata.TypeLinksym(n.Type())) } @@ -550,6 +556,29 @@ func createComplexVar(fnsym *obj.LSym, fn *ir.Func, varID ssa.VarID, closureVars return dvar } +// createHeapDerefLocationList creates a location list for a heap-escaped variable +// that describes "dereference pointer at stack offset" +func createHeapDerefLocationList(n *ir.Name, fnsym *obj.LSym, entryID, prologEndID ssa.ID) []byte { + // Get the stack offset where the heap pointer is stored + heapPtrOffset := n.Heapaddr.FrameOffset() + if base.Ctxt.Arch.FixedFrameSize == 0 { + heapPtrOffset -= int64(types.PtrSize) + } + if buildcfg.FramePointerEnabled { + heapPtrOffset -= int64(types.PtrSize) + } + + // Create a location expression: DW_OP_fbreg DW_OP_deref + var locExpr []byte + var sizeIdx int + locExpr, sizeIdx = ssa.SetupLocList(base.Ctxt, entryID, locExpr, ssa.BlockStart.ID, ssa.FuncEnd.ID) + locExpr = append(locExpr, dwarf.DW_OP_fbreg) + locExpr = dwarf.AppendSleb128(locExpr, heapPtrOffset) + locExpr = append(locExpr, dwarf.DW_OP_deref) + base.Ctxt.Arch.ByteOrder.PutUint16(locExpr[sizeIdx:], uint16(len(locExpr)-sizeIdx-2)) + return locExpr +} + // RecordFlags records the specified command-line flags to be placed // in the DWARF info. func RecordFlags(flags ...string) { diff --git a/src/cmd/compile/internal/ssa/debug.go b/src/cmd/compile/internal/ssa/debug.go index aa503eda879ea1..e92b37fb7bc342 100644 --- a/src/cmd/compile/internal/ssa/debug.go +++ b/src/cmd/compile/internal/ssa/debug.go @@ -41,6 +41,9 @@ type FuncDebug struct { RegOutputParams []*ir.Name // Variable declarations that were removed during optimization OptDcl []*ir.Name + // The ssa.Func.EntryID value, used to build location lists for + // return values promoted to heap in later DWARF generation. + EntryID ID // Filled in by the user. Translates Block and Value ID to PC. // @@ -1645,13 +1648,13 @@ func readPtr(ctxt *obj.Link, buf []byte) uint64 { } -// setupLocList creates the initial portion of a location list for a +// SetupLocList creates the initial portion of a location list for a // user variable. It emits the encoded start/end of the range and a // placeholder for the size. Return value is the new list plus the // slot in the list holding the size (to be updated later). -func setupLocList(ctxt *obj.Link, f *Func, list []byte, st, en ID) ([]byte, int) { - start, startOK := encodeValue(ctxt, f.Entry.ID, st) - end, endOK := encodeValue(ctxt, f.Entry.ID, en) +func SetupLocList(ctxt *obj.Link, entryID ID, list []byte, st, en ID) ([]byte, int) { + start, startOK := encodeValue(ctxt, entryID, st) + end, endOK := encodeValue(ctxt, entryID, en) if !startOK || !endOK { // This could happen if someone writes a function that uses // >65K values on a 32-bit platform. Hopefully a degraded debugging @@ -1800,7 +1803,6 @@ func isNamedRegParam(p abi.ABIParamAssignment) bool { // appropriate for the ".closureptr" compiler-synthesized variable // needed by the debugger for range func bodies. func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset func(LocalSlot) int32, rval *FuncDebug) { - needCloCtx := f.CloSlot != nil pri := f.ABISelf.ABIAnalyzeFuncType(f.Type) @@ -1911,7 +1913,7 @@ func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, sta // Param is arriving in one or more registers. We need a 2-element // location expression for it. First entry in location list // will correspond to lifetime in input registers. - list, sizeIdx := setupLocList(ctxt, f, rval.LocationLists[pidx], + list, sizeIdx := SetupLocList(ctxt, f.Entry.ID, rval.LocationLists[pidx], BlockStart.ID, afterPrologVal) if list == nil { pidx++ @@ -1961,7 +1963,7 @@ func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, sta // Second entry in the location list will be the stack home // of the param, once it has been spilled. Emit that now. - list, sizeIdx = setupLocList(ctxt, f, list, + list, sizeIdx = SetupLocList(ctxt, f.Entry.ID, list, afterPrologVal, FuncEnd.ID) if list == nil { pidx++ diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index e241e9b9bc0f4f..6ca299cac54836 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -6960,6 +6960,9 @@ func genssa(f *ssa.Func, pp *objw.Progs) { if base.Ctxt.Flag_locationlists { var debugInfo *ssa.FuncDebug debugInfo = e.curfn.DebugInfo.(*ssa.FuncDebug) + // Save off entry ID in case we need it later for DWARF generation + // for return values promoted to the heap. + debugInfo.EntryID = f.Entry.ID if e.curfn.ABI == obj.ABIInternal && base.Flag.N != 0 { ssa.BuildFuncDebugNoOptimized(base.Ctxt, f, base.Debug.LocationLists > 1, StackOffset, debugInfo) } else { diff --git a/src/cmd/link/dwarf_test.go b/src/cmd/link/dwarf_test.go index 68849d7db9efa2..42834236739159 100644 --- a/src/cmd/link/dwarf_test.go +++ b/src/cmd/link/dwarf_test.go @@ -256,3 +256,103 @@ func TestDWARFiOS(t *testing.T) { testDWARF(t, "c-archive", true, cc, "CGO_ENABLED=1", "GOOS=ios", "GOARCH=arm64") }) } + +func TestDWARFLocationList(t *testing.T) { + testenv.MustHaveCGO(t) + testenv.MustHaveGoBuild(t) + + if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) { + t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH) + } + + t.Parallel() + + tmpDir := t.TempDir() + exe := filepath.Join(tmpDir, "issue65405.exe") + dir := "./testdata/dwarf/issue65405" + + cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-toolexec", os.Args[0], "-gcflags=all=-N -l", "-o", exe, dir) + cmd.Env = append(os.Environ(), "CGO_CFLAGS=") + cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out) + } + t.Log("output:\n", string(out)) + + f, err := objfile.Open(exe) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + d, err := f.DWARF() + if err != nil { + t.Fatal(err) + } + + // Find the net.sendFile function and check its return parameter location list + reader := d.Reader() + found := false + + for { + entry, err := reader.Next() + if err != nil { + t.Fatal(err) + } + if entry == nil { + break + } + + // Look for the net.sendFile subprogram + if entry.Tag == dwarf.TagSubprogram { + name, ok := entry.Val(dwarf.AttrName).(string) + if ok && name == "net.sendFile" { + found = true + + // Look for formal parameters (including return parameters) + for { + paramEntry, err := reader.Next() + if err != nil { + t.Fatal(err) + } + if paramEntry == nil || paramEntry.Tag == 0 { + break + } + + if paramEntry.Tag == dwarf.TagFormalParameter { + paramName, hasName := paramEntry.Val(dwarf.AttrName).(string) + if hasName && paramName == "handled" { + // Check if this parameter has a location attribute + if loc := paramEntry.Val(dwarf.AttrLocation); loc != nil { + switch locData := loc.(type) { + case []byte: + if len(locData) == 0 { + t.Errorf("net.sendFile return parameter 'handled' has empty location list") + } + case int64: + // Location list offset - this means it has a location list + if locData == 0 { + t.Errorf("net.sendFile return parameter 'handled' has zero location list offset") + } else { + // Non-zero offset means valid location list + t.Logf("net.sendFile return parameter 'handled' has location list at offset %d", locData) + } + default: + t.Logf("net.sendFile return parameter 'handled' has location of type %T: %v", locData, locData) + } + } else { + t.Errorf("net.sendFile return parameter 'handled' has no location attribute") + } + return // Found what we're looking for + } + } + } + } + } + } + + if !found { + t.Fatal("could not find net.sendFile function in DWARF info") + } +} diff --git a/src/cmd/link/testdata/dwarf/issue65405/main.go b/src/cmd/link/testdata/dwarf/issue65405/main.go new file mode 100644 index 00000000000000..f76e464b23d643 --- /dev/null +++ b/src/cmd/link/testdata/dwarf/issue65405/main.go @@ -0,0 +1,8 @@ +package main + +import "net/http" + +func main() { + http.Handle("/", http.StripPrefix("/static/", http.FileServer(http.Dir("./output")))) + http.ListenAndServe(":8000", nil) +} From 1af2fc8d49a5b0826a1e493e08e41565dd524fa9 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Sun, 29 Jun 2025 20:32:53 -0700 Subject: [PATCH 2/3] cmd/link: cleanup test --- src/cmd/link/dwarf_test.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/cmd/link/dwarf_test.go b/src/cmd/link/dwarf_test.go index 42834236739159..346da218853c09 100644 --- a/src/cmd/link/dwarf_test.go +++ b/src/cmd/link/dwarf_test.go @@ -278,7 +278,6 @@ func TestDWARFLocationList(t *testing.T) { if err != nil { t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out) } - t.Log("output:\n", string(out)) f, err := objfile.Open(exe) if err != nil { @@ -310,7 +309,6 @@ func TestDWARFLocationList(t *testing.T) { if ok && name == "net.sendFile" { found = true - // Look for formal parameters (including return parameters) for { paramEntry, err := reader.Next() if err != nil { @@ -334,17 +332,15 @@ func TestDWARFLocationList(t *testing.T) { // Location list offset - this means it has a location list if locData == 0 { t.Errorf("net.sendFile return parameter 'handled' has zero location list offset") - } else { - // Non-zero offset means valid location list - t.Logf("net.sendFile return parameter 'handled' has location list at offset %d", locData) } + break default: - t.Logf("net.sendFile return parameter 'handled' has location of type %T: %v", locData, locData) + t.Errorf("net.sendFile return parameter 'handled' has unexpected location type %T: %v", locData, locData) } } else { t.Errorf("net.sendFile return parameter 'handled' has no location attribute") } - return // Found what we're looking for + return } } } From 994b9b07f6a70449991fcba373ad34145fdc628c Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Sun, 29 Jun 2025 20:36:19 -0700 Subject: [PATCH 3/3] cmd/link: remove unnecessary break in loop in test --- src/cmd/link/dwarf_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cmd/link/dwarf_test.go b/src/cmd/link/dwarf_test.go index 346da218853c09..02c133f123f468 100644 --- a/src/cmd/link/dwarf_test.go +++ b/src/cmd/link/dwarf_test.go @@ -333,7 +333,6 @@ func TestDWARFLocationList(t *testing.T) { if locData == 0 { t.Errorf("net.sendFile return parameter 'handled' has zero location list offset") } - break default: t.Errorf("net.sendFile return parameter 'handled' has unexpected location type %T: %v", locData, locData) }