cmd/link: test that funcdata values are in gopclntab section

This is a test for CL 719440.

For #76038

Change-Id: I8fc55118b3c7dea39a36e04ffb060fcb6150af54
Reviewed-on: https://go-review.googlesource.com/c/go/+/721460
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
Ian Lance Taylor 2025-11-17 18:29:42 -08:00 committed by Gopher Robot
parent c03e25a263
commit 21b6ab57d5

View file

@ -7,10 +7,14 @@ package main
import (
"bufio"
"bytes"
"debug/elf"
"debug/macho"
"debug/pe"
"errors"
"internal/abi"
"internal/platform"
"internal/testenv"
"internal/xcoff"
"os"
"os/exec"
"path/filepath"
@ -19,6 +23,7 @@ import (
"strconv"
"strings"
"testing"
"unsafe"
imacho "cmd/internal/macho"
"cmd/internal/objfile"
@ -1773,3 +1778,335 @@ func TestLinknameBSS(t *testing.T) {
t.Errorf("executable failed to run: %v\n%s", err, out)
}
}
// setValueFromBytes copies from a []byte to a variable.
// This is used to get correctly aligned values in TestFuncdataPlacement.
func setValueFromBytes[T any](p *T, s []byte) {
copy(unsafe.Slice((*byte)(unsafe.Pointer(p)), unsafe.Sizeof(*p)), s)
}
// Test that all funcdata values are stored in the .gopclntab section.
// This is pretty ugly as there is no API for accessing this data.
// This test will have to be updated when the data formats change.
func TestFuncdataPlacement(t *testing.T) {
testenv.MustHaveGoBuild(t)
t.Parallel()
tmpdir := t.TempDir()
src := filepath.Join(tmpdir, "x.go")
if err := os.WriteFile(src, []byte(trivialSrc), 0o444); err != nil {
t.Fatal(err)
}
exe := filepath.Join(tmpdir, "x.exe")
cmd := goCmd(t, "build", "-o", exe, src)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("build failed; %v, output:\n%s", err, out)
}
// We want to find the funcdata in the executable.
// We look at the section table to find the .gopclntab section,
// which starts with the pcHeader.
// That will give us the table of functions,
// which we can use to find the funcdata.
ef, _ := elf.Open(exe)
mf, _ := macho.Open(exe)
pf, _ := pe.Open(exe)
xf, _ := xcoff.Open(exe)
// TODO: plan9
if ef == nil && mf == nil && pf == nil && xf == nil {
t.Skip("unrecognized executable file format")
}
const moddataSymName = "runtime.firstmoduledata"
const gofuncSymName = "go:func.*"
var (
pclntab []byte
pclntabAddr uint64
pclntabEnd uint64
moddataAddr uint64
moddataBytes []byte
gofuncAddr uint64
imageBase uint64
)
switch {
case ef != nil:
defer ef.Close()
syms, err := ef.Symbols()
if err != nil {
t.Fatal(err)
}
for _, sym := range syms {
switch sym.Name {
case moddataSymName:
moddataAddr = sym.Value
case gofuncSymName:
gofuncAddr = sym.Value
}
}
for _, sec := range ef.Sections {
if sec.Name == ".gopclntab" {
data, err := sec.Data()
if err != nil {
t.Fatal(err)
}
pclntab = data
pclntabAddr = sec.Addr
pclntabEnd = sec.Addr + sec.Size
}
if sec.Flags&elf.SHF_ALLOC != 0 && moddataAddr >= sec.Addr && moddataAddr < sec.Addr+sec.Size {
data, err := sec.Data()
if err != nil {
t.Fatal(err)
}
moddataBytes = data[moddataAddr-sec.Addr:]
}
}
case mf != nil:
defer mf.Close()
for _, sym := range mf.Symtab.Syms {
switch sym.Name {
case moddataSymName:
moddataAddr = sym.Value
case gofuncSymName:
gofuncAddr = sym.Value
}
}
for _, sec := range mf.Sections {
if sec.Name == "__gopclntab" {
data, err := sec.Data()
if err != nil {
t.Fatal(err)
}
pclntab = data
pclntabAddr = sec.Addr
pclntabEnd = sec.Addr + sec.Size
}
if moddataAddr >= sec.Addr && moddataAddr < sec.Addr+sec.Size {
data, err := sec.Data()
if err != nil {
t.Fatal(err)
}
moddataBytes = data[moddataAddr-sec.Addr:]
}
}
case pf != nil:
defer pf.Close()
switch ohdr := pf.OptionalHeader.(type) {
case *pe.OptionalHeader32:
imageBase = uint64(ohdr.ImageBase)
case *pe.OptionalHeader64:
imageBase = ohdr.ImageBase
}
var moddataSym, gofuncSym, pclntabSym, epclntabSym *pe.Symbol
for _, sym := range pf.Symbols {
switch sym.Name {
case moddataSymName:
moddataSym = sym
case gofuncSymName:
gofuncSym = sym
case "runtime.pclntab":
pclntabSym = sym
case "runtime.epclntab":
epclntabSym = sym
}
}
if moddataSym == nil {
t.Fatalf("could not find symbol %s", moddataSymName)
}
if gofuncSym == nil {
t.Fatalf("could not find symbol %s", gofuncSymName)
}
if pclntabSym == nil {
t.Fatal("could not find symbol runtime.pclntab")
}
if epclntabSym == nil {
t.Fatal("could not find symbol runtime.epclntab")
}
sec := pf.Sections[moddataSym.SectionNumber-1]
data, err := sec.Data()
if err != nil {
t.Fatal(err)
}
moddataBytes = data[moddataSym.Value:]
moddataAddr = uint64(sec.VirtualAddress + moddataSym.Value)
sec = pf.Sections[gofuncSym.SectionNumber-1]
gofuncAddr = uint64(sec.VirtualAddress + gofuncSym.Value)
if pclntabSym.SectionNumber != epclntabSym.SectionNumber {
t.Fatalf("runtime.pclntab section %d != runtime.epclntab section %d", pclntabSym.SectionNumber, epclntabSym.SectionNumber)
}
sec = pf.Sections[pclntabSym.SectionNumber-1]
data, err = sec.Data()
if err != nil {
t.Fatal(err)
}
pclntab = data[pclntabSym.Value:epclntabSym.Value]
pclntabAddr = uint64(sec.VirtualAddress + pclntabSym.Value)
pclntabEnd = uint64(sec.VirtualAddress + epclntabSym.Value)
case xf != nil:
defer xf.Close()
for _, sym := range xf.Symbols {
switch sym.Name {
case moddataSymName:
moddataAddr = sym.Value
case gofuncSymName:
gofuncAddr = sym.Value
}
}
for _, sec := range xf.Sections {
if sec.Name == ".go.pclntab" {
data, err := sec.Data()
if err != nil {
t.Fatal(err)
}
pclntab = data
pclntabAddr = sec.VirtualAddress
pclntabEnd = sec.VirtualAddress + sec.Size
}
if moddataAddr >= sec.VirtualAddress && moddataAddr < sec.VirtualAddress+sec.Size {
data, err := sec.Data()
if err != nil {
t.Fatal(err)
}
moddataBytes = data[moddataAddr-sec.VirtualAddress:]
}
}
default:
panic("can't happen")
}
if len(pclntab) == 0 {
t.Fatal("could not find pclntab section")
}
if moddataAddr == 0 {
t.Fatalf("could not find %s symbol", moddataSymName)
}
if gofuncAddr == 0 {
t.Fatalf("could not find %s symbol", gofuncSymName)
}
if gofuncAddr < pclntabAddr || gofuncAddr >= pclntabEnd {
t.Fatalf("%s out of range: value %#x not between %#x and %#x", gofuncSymName, gofuncAddr, pclntabAddr, pclntabEnd)
}
if len(moddataBytes) == 0 {
t.Fatal("could not find module data")
}
// What a slice looks like in the object file.
type moddataSlice struct {
addr uintptr
len int
cap int
}
// This needs to match the struct defined in runtime/symtab.go,
// and written out by (*Link).symtab.
// This is not the complete moddata struct, only what we need here.
type moddataType struct {
pcHeader uintptr
funcnametab moddataSlice
cutab moddataSlice
filetab moddataSlice
pctab moddataSlice
pclntable moddataSlice
ftab moddataSlice
findfunctab uintptr
minpc, maxpc uintptr
text, etext uintptr
noptrdata, enoptrdata uintptr
data, edata uintptr
bss, ebss uintptr
noptrbss, enoptrbss uintptr
covctrs, ecovctrs uintptr
end, gcdata, gcbss uintptr
types, etypes uintptr
rodata uintptr
gofunc uintptr
}
// The executable is on the same system as we are running,
// so the sizes and alignments should match.
// But moddataBytes itself may not be aligned as needed.
// Copy to a variable to ensure alignment.
var moddata moddataType
setValueFromBytes(&moddata, moddataBytes)
ftabAddr := uint64(moddata.ftab.addr) - imageBase
if ftabAddr < pclntabAddr || ftabAddr >= pclntabEnd {
t.Fatalf("ftab address %#x not between %#x and %#x", ftabAddr, pclntabAddr, pclntabEnd)
}
// From runtime/symtab.go and the linker function writePCToFunc.
type functab struct {
entryoff uint32
funcoff uint32
}
// The ftab slice in moddata has one extra entry used to record
// the final PC.
ftabLen := moddata.ftab.len - 1
ftab := make([]functab, ftabLen)
copy(ftab, unsafe.Slice((*functab)(unsafe.Pointer(&pclntab[ftabAddr-pclntabAddr])), ftabLen))
ftabBase := uint64(moddata.pclntable.addr) - imageBase
// From runtime/runtime2.go _func and the linker function writeFuncs.
type funcEntry struct {
entryOff uint32
nameOff int32
args int32
deferreturn uint32
pcsp uint32
pcfile uint32
pcln uint32
npcdata uint32
cuOffset uint32
startLine int32
funcID abi.FuncID
flag abi.FuncFlag
_ [1]byte
nfuncdata uint8
}
for i, ftabEntry := range ftab {
funcAddr := ftabBase + uint64(ftabEntry.funcoff)
if funcAddr < pclntabAddr || funcAddr >= pclntabEnd {
t.Errorf("ftab entry %d address %#x not between %#x and %#x", i, funcAddr, pclntabAddr, pclntabEnd)
continue
}
var fe funcEntry
setValueFromBytes(&fe, pclntab[funcAddr-pclntabAddr:])
funcdataVals := funcAddr + uint64(unsafe.Sizeof(fe)) + uint64(fe.npcdata*4)
for j := range fe.nfuncdata {
var funcdataVal uint32
setValueFromBytes(&funcdataVal, pclntab[funcdataVals+uint64(j)*4-pclntabAddr:])
if funcdataVal == ^uint32(0) {
continue
}
funcdataAddr := gofuncAddr + uint64(funcdataVal)
if funcdataAddr < pclntabAddr || funcdataAddr >= pclntabEnd {
t.Errorf("ftab entry %d funcdata %d address %#x not between %#x and %#x", i, j, funcdataAddr, pclntabAddr, pclntabEnd)
}
}
}
}