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 (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"debug/elf"
|
||||||
"debug/macho"
|
"debug/macho"
|
||||||
|
"debug/pe"
|
||||||
"errors"
|
"errors"
|
||||||
|
"internal/abi"
|
||||||
"internal/platform"
|
"internal/platform"
|
||||||
"internal/testenv"
|
"internal/testenv"
|
||||||
|
"internal/xcoff"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -19,6 +23,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
imacho "cmd/internal/macho"
|
imacho "cmd/internal/macho"
|
||||||
"cmd/internal/objfile"
|
"cmd/internal/objfile"
|
||||||
|
|
@ -1773,3 +1778,335 @@ func TestLinknameBSS(t *testing.T) {
|
||||||
t.Errorf("executable failed to run: %v\n%s", err, out)
|
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