Skip to content

Commit 71c2bf5

Browse files
derekparkerrandall77
authored andcommitted
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 Change-Id: Id4ec6b1ab6681fd77b8fefb47a4ec05060c128ef GitHub-Last-Rev: 5ab6a53 GitHub-Pull-Request: #74398 Reviewed-on: https://go-review.googlesource.com/c/go/+/684377 Reviewed-by: Michael Knyszek <mknyszek@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Keith Randall <khr@google.com>
1 parent c74399e commit 71c2bf5

File tree

5 files changed

+165
-15
lines changed

5 files changed

+165
-15
lines changed

src/cmd/compile/internal/dwarfgen/dwarf.go

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,6 @@ func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir
248248
if n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT {
249249
tag = dwarf.DW_TAG_formal_parameter
250250
}
251-
if n.Esc() == ir.EscHeap {
252-
// The variable in question has been promoted to the heap.
253-
// Its address is in n.Heapaddr.
254-
// TODO(thanm): generate a better location expression
255-
}
256251
inlIndex := 0
257252
if base.Flag.GenDwarfInl > 1 {
258253
if n.InlFormal() || n.InlLocal() {
@@ -263,7 +258,7 @@ func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir
263258
}
264259
}
265260
declpos := base.Ctxt.InnermostPos(n.Pos())
266-
vars = append(vars, &dwarf.Var{
261+
dvar := &dwarf.Var{
267262
Name: n.Sym().Name,
268263
IsReturnValue: isReturnValue,
269264
Tag: tag,
@@ -277,8 +272,19 @@ func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir
277272
ChildIndex: -1,
278273
DictIndex: n.DictIndex,
279274
ClosureOffset: closureOffset(n, closureVars),
280-
})
281-
// Record go type of to insure that it gets emitted by the linker.
275+
}
276+
if n.Esc() == ir.EscHeap {
277+
if n.Heapaddr == nil {
278+
base.Fatalf("invalid heap allocated var without Heapaddr")
279+
}
280+
debug := fn.DebugInfo.(*ssa.FuncDebug)
281+
list := createHeapDerefLocationList(n, fnsym, debug.EntryID, ssa.FuncEnd.ID)
282+
dvar.PutLocationList = func(listSym, startPC dwarf.Sym) {
283+
debug.PutLocationList(list, base.Ctxt, listSym.(*obj.LSym), startPC.(*obj.LSym))
284+
}
285+
}
286+
vars = append(vars, dvar)
287+
// Record go type to ensure that it gets emitted by the linker.
282288
fnsym.Func().RecordAutoType(reflectdata.TypeLinksym(n.Type()))
283289
}
284290

@@ -550,6 +556,29 @@ func createComplexVar(fnsym *obj.LSym, fn *ir.Func, varID ssa.VarID, closureVars
550556
return dvar
551557
}
552558

559+
// createHeapDerefLocationList creates a location list for a heap-escaped variable
560+
// that describes "dereference pointer at stack offset"
561+
func createHeapDerefLocationList(n *ir.Name, fnsym *obj.LSym, entryID, prologEndID ssa.ID) []byte {
562+
// Get the stack offset where the heap pointer is stored
563+
heapPtrOffset := n.Heapaddr.FrameOffset()
564+
if base.Ctxt.Arch.FixedFrameSize == 0 {
565+
heapPtrOffset -= int64(types.PtrSize)
566+
}
567+
if buildcfg.FramePointerEnabled {
568+
heapPtrOffset -= int64(types.PtrSize)
569+
}
570+
571+
// Create a location expression: DW_OP_fbreg <offset> DW_OP_deref
572+
var locExpr []byte
573+
var sizeIdx int
574+
locExpr, sizeIdx = ssa.SetupLocList(base.Ctxt, entryID, locExpr, ssa.BlockStart.ID, ssa.FuncEnd.ID)
575+
locExpr = append(locExpr, dwarf.DW_OP_fbreg)
576+
locExpr = dwarf.AppendSleb128(locExpr, heapPtrOffset)
577+
locExpr = append(locExpr, dwarf.DW_OP_deref)
578+
base.Ctxt.Arch.ByteOrder.PutUint16(locExpr[sizeIdx:], uint16(len(locExpr)-sizeIdx-2))
579+
return locExpr
580+
}
581+
553582
// RecordFlags records the specified command-line flags to be placed
554583
// in the DWARF info.
555584
func RecordFlags(flags ...string) {

src/cmd/compile/internal/ssa/debug.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ type FuncDebug struct {
4141
RegOutputParams []*ir.Name
4242
// Variable declarations that were removed during optimization
4343
OptDcl []*ir.Name
44+
// The ssa.Func.EntryID value, used to build location lists for
45+
// return values promoted to heap in later DWARF generation.
46+
EntryID ID
4447

4548
// Filled in by the user. Translates Block and Value ID to PC.
4649
//
@@ -1645,13 +1648,13 @@ func readPtr(ctxt *obj.Link, buf []byte) uint64 {
16451648

16461649
}
16471650

1648-
// setupLocList creates the initial portion of a location list for a
1651+
// SetupLocList creates the initial portion of a location list for a
16491652
// user variable. It emits the encoded start/end of the range and a
16501653
// placeholder for the size. Return value is the new list plus the
16511654
// slot in the list holding the size (to be updated later).
1652-
func setupLocList(ctxt *obj.Link, f *Func, list []byte, st, en ID) ([]byte, int) {
1653-
start, startOK := encodeValue(ctxt, f.Entry.ID, st)
1654-
end, endOK := encodeValue(ctxt, f.Entry.ID, en)
1655+
func SetupLocList(ctxt *obj.Link, entryID ID, list []byte, st, en ID) ([]byte, int) {
1656+
start, startOK := encodeValue(ctxt, entryID, st)
1657+
end, endOK := encodeValue(ctxt, entryID, en)
16551658
if !startOK || !endOK {
16561659
// This could happen if someone writes a function that uses
16571660
// >65K values on a 32-bit platform. Hopefully a degraded debugging
@@ -1800,7 +1803,6 @@ func isNamedRegParam(p abi.ABIParamAssignment) bool {
18001803
// appropriate for the ".closureptr" compiler-synthesized variable
18011804
// needed by the debugger for range func bodies.
18021805
func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset func(LocalSlot) int32, rval *FuncDebug) {
1803-
18041806
needCloCtx := f.CloSlot != nil
18051807
pri := f.ABISelf.ABIAnalyzeFuncType(f.Type)
18061808

@@ -1911,7 +1913,7 @@ func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, sta
19111913
// Param is arriving in one or more registers. We need a 2-element
19121914
// location expression for it. First entry in location list
19131915
// will correspond to lifetime in input registers.
1914-
list, sizeIdx := setupLocList(ctxt, f, rval.LocationLists[pidx],
1916+
list, sizeIdx := SetupLocList(ctxt, f.Entry.ID, rval.LocationLists[pidx],
19151917
BlockStart.ID, afterPrologVal)
19161918
if list == nil {
19171919
pidx++
@@ -1961,7 +1963,7 @@ func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, sta
19611963

19621964
// Second entry in the location list will be the stack home
19631965
// of the param, once it has been spilled. Emit that now.
1964-
list, sizeIdx = setupLocList(ctxt, f, list,
1966+
list, sizeIdx = SetupLocList(ctxt, f.Entry.ID, list,
19651967
afterPrologVal, FuncEnd.ID)
19661968
if list == nil {
19671969
pidx++

src/cmd/compile/internal/ssagen/ssa.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6960,6 +6960,9 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
69606960
if base.Ctxt.Flag_locationlists {
69616961
var debugInfo *ssa.FuncDebug
69626962
debugInfo = e.curfn.DebugInfo.(*ssa.FuncDebug)
6963+
// Save off entry ID in case we need it later for DWARF generation
6964+
// for return values promoted to the heap.
6965+
debugInfo.EntryID = f.Entry.ID
69636966
if e.curfn.ABI == obj.ABIInternal && base.Flag.N != 0 {
69646967
ssa.BuildFuncDebugNoOptimized(base.Ctxt, f, base.Debug.LocationLists > 1, StackOffset, debugInfo)
69656968
} else {

src/cmd/link/dwarf_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,111 @@ func TestDWARFiOS(t *testing.T) {
256256
testDWARF(t, "c-archive", true, cc, "CGO_ENABLED=1", "GOOS=ios", "GOARCH=arm64")
257257
})
258258
}
259+
260+
func TestDWARFLocationList(t *testing.T) {
261+
testenv.MustHaveCGO(t)
262+
testenv.MustHaveGoBuild(t)
263+
264+
if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) {
265+
t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH)
266+
}
267+
268+
t.Parallel()
269+
270+
tmpDir := t.TempDir()
271+
exe := filepath.Join(tmpDir, "issue65405.exe")
272+
dir := "./testdata/dwarf/issue65405"
273+
274+
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-toolexec", os.Args[0], "-gcflags=all=-N -l", "-o", exe, dir)
275+
cmd.Env = append(os.Environ(), "CGO_CFLAGS=")
276+
cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1")
277+
out, err := cmd.CombinedOutput()
278+
if err != nil {
279+
t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out)
280+
}
281+
282+
f, err := objfile.Open(exe)
283+
if err != nil {
284+
t.Fatal(err)
285+
}
286+
defer f.Close()
287+
288+
d, err := f.DWARF()
289+
if err != nil {
290+
t.Fatal(err)
291+
}
292+
293+
// Find the net.sendFile function and check its return parameter location list
294+
reader := d.Reader()
295+
296+
for {
297+
entry, err := reader.Next()
298+
if err != nil {
299+
t.Fatal(err)
300+
}
301+
if entry == nil {
302+
break
303+
}
304+
305+
// Look for the net.sendFile subprogram
306+
if entry.Tag == dwarf.TagSubprogram {
307+
fnName, ok := entry.Val(dwarf.AttrName).(string)
308+
if !ok {
309+
continue
310+
}
311+
if strings.Contains(fnName, ".eq") || // Ignore autogenerated equality funcs
312+
strings.HasPrefix(fnName, "internal/") || // Ignore internal/runtime package TODO(deparker): Fix these too (likely same issue as other ignored packages below).
313+
strings.HasPrefix(fnName, "runtime.") || // Ignore runtime package which contain funcs implemented in assembly or exposed through linkname which seems to not generate location lists correctly (most likely linkname causing this). TODO(deparker) Fix these too.
314+
strings.HasPrefix(fnName, "reflect.") || // Ignore reflect package. TODO(deparker) Fix these too.
315+
strings.HasPrefix(fnName, "time.") { // Ignore funcs in time package which are exposed through linkname and seem to not generate location lists correctly TODO(deparker) Fix these too.
316+
continue
317+
}
318+
if fnName == "syscall.compileCallback" || fnName == "maps.clone" {
319+
continue // Ignore for now, possibly caused by linkname usage. TODO(deparker) Fix this too.
320+
}
321+
if runtime.GOOS == "windows" && strings.HasPrefix(fnName, "syscall.") {
322+
continue // Ignore, caused by linkname usage. TODO(deparker) Fix these too.
323+
}
324+
325+
for {
326+
paramEntry, err := reader.Next()
327+
if err != nil {
328+
t.Fatal(err)
329+
}
330+
if paramEntry == nil || paramEntry.Tag == 0 {
331+
break
332+
}
333+
334+
if paramEntry.Tag == dwarf.TagFormalParameter {
335+
paramName, hasName := paramEntry.Val(dwarf.AttrName).(string)
336+
if !hasName {
337+
continue
338+
}
339+
if paramName[0] == '~' {
340+
continue
341+
}
342+
// Check if this parameter has a location attribute
343+
if loc := paramEntry.Val(dwarf.AttrLocation); loc != nil {
344+
switch locData := loc.(type) {
345+
case []byte:
346+
if len(locData) == 0 {
347+
t.Errorf("%s return parameter %q has empty location list", fnName, paramName)
348+
return
349+
}
350+
case int64:
351+
// Location list offset - this means it has a location list
352+
if locData == 0 {
353+
t.Errorf("%s return parameter %q has zero location list offset", fnName, paramName)
354+
return
355+
}
356+
default:
357+
t.Errorf("%s return parameter %q has unexpected location type %T: %v", fnName, paramName, locData, locData)
358+
}
359+
} else {
360+
t.Errorf("%s return parameter %q has no location attribute", fnName, paramName)
361+
}
362+
}
363+
}
364+
}
365+
}
366+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package main
2+
3+
import "net/http"
4+
5+
func main() {
6+
http.Handle("/", http.StripPrefix("/static/", http.FileServer(http.Dir("./output"))))
7+
http.ListenAndServe(":8000", nil)
8+
}

0 commit comments

Comments
 (0)