mirror of
https://github.com/golang/go.git
synced 2025-10-19 19:13:18 +00:00
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: 5ab6a53981
GitHub-Pull-Request: golang/go#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>
This commit is contained in:
parent
c74399e7f5
commit
71c2bf5513
5 changed files with 165 additions and 15 deletions
|
@ -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 {
|
||||
base.Fatalf("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 <offset> 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) {
|
||||
|
|
|
@ -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++
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -256,3 +256,111 @@ 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)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
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 {
|
||||
fnName, ok := entry.Val(dwarf.AttrName).(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(fnName, ".eq") || // Ignore autogenerated equality funcs
|
||||
strings.HasPrefix(fnName, "internal/") || // Ignore internal/runtime package TODO(deparker): Fix these too (likely same issue as other ignored packages below).
|
||||
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.
|
||||
strings.HasPrefix(fnName, "reflect.") || // Ignore reflect package. TODO(deparker) Fix these too.
|
||||
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.
|
||||
continue
|
||||
}
|
||||
if fnName == "syscall.compileCallback" || fnName == "maps.clone" {
|
||||
continue // Ignore for now, possibly caused by linkname usage. TODO(deparker) Fix this too.
|
||||
}
|
||||
if runtime.GOOS == "windows" && strings.HasPrefix(fnName, "syscall.") {
|
||||
continue // Ignore, caused by linkname usage. TODO(deparker) Fix these too.
|
||||
}
|
||||
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
if paramName[0] == '~' {
|
||||
continue
|
||||
}
|
||||
// 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("%s return parameter %q has empty location list", fnName, paramName)
|
||||
return
|
||||
}
|
||||
case int64:
|
||||
// Location list offset - this means it has a location list
|
||||
if locData == 0 {
|
||||
t.Errorf("%s return parameter %q has zero location list offset", fnName, paramName)
|
||||
return
|
||||
}
|
||||
default:
|
||||
t.Errorf("%s return parameter %q has unexpected location type %T: %v", fnName, paramName, locData, locData)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("%s return parameter %q has no location attribute", fnName, paramName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
8
src/cmd/link/testdata/dwarf/issue65405/main.go
vendored
Normal file
8
src/cmd/link/testdata/dwarf/issue65405/main.go
vendored
Normal file
|
@ -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)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue