mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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:
parent
c03e25a263
commit
21b6ab57d5
1 changed files with 337 additions and 0 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue