cmd/link, runtime: on Wasm, put only function index in method table and func table

In the type descriptor's method table, it contains relative PCs of
the methods (relative to the start of the text section) stored as
32-bit offsets. On Wasm, a PC is PC_F<<16 + PC_B, where PC_F is
the function index, and PC_B is the block index. When there are
more than 65536 functions, the PC will not fit into 32-bit (and
relative to the section start doesn't help). Since there are no
more bits for the function index, and the method table always
targets the entry of a method, we put just the PC_F there, and
rewrite back to a full PC at run time when we need the PC. This
way we can have more than 65536 functions.

The func table also contains 32-bit relative PCs, and it also
always points to function entries. Do the same there, as well
as other places where we use relative text offsets.

Also add the relocation type in the relocation overflow error
message.

Also add check for function too big on Wasm. If a function has
more than 65536 blocks, PC_B will overflow and PC = PC_F<<16 + PC_B
will points to the wrong function.

Fixes #64856.

Change-Id: If9c307e9fb1641f367a5f19c39f88f455805d0bb
Reviewed-on: https://go-review.googlesource.com/c/go/+/552835
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Cherry Mui 2023-12-26 15:35:56 -05:00
parent 9782dcfd16
commit 9c507e7942
4 changed files with 77 additions and 18 deletions

View file

@ -372,6 +372,9 @@ func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
}
tableIdxs = append(tableIdxs, uint64(numResumePoints))
s.Size = pc + 1
if pc >= 1<<16 {
ctxt.Diag("function too big: %s exceeds 65536 blocks", s)
}
if needMoreStack {
p := pMorestack

View file

@ -496,6 +496,15 @@ func (st *relocSymState) relocsym(s loader.Sym, P []byte) {
// to the start of the first text section, even if there are multiple.
if sect.Name == ".text" {
o = ldr.SymValue(rs) - int64(Segtext.Sections[0].Vaddr) + r.Add()
if target.IsWasm() {
// On Wasm, textoff (e.g. in the method table) is just the function index,
// whereas the "PC" (rs's Value) is function index << 16 + block index (see
// ../wasm/asm.go:assignAddress).
if o&(1<<16-1) != 0 {
st.err.Errorf(s, "textoff relocation %s does not target function entry: %s %#x", rt, ldr.SymName(rs), o)
}
o >>= 16
}
} else {
o = ldr.SymValue(rs) - int64(ldr.SymSect(rs).Vaddr) + r.Add()
}
@ -606,16 +615,16 @@ func (st *relocSymState) relocsym(s loader.Sym, P []byte) {
P[off] = byte(int8(o))
case 2:
if (rt == objabi.R_PCREL || rt == objabi.R_CALL) && o != int64(int16(o)) {
st.err.Errorf(s, "pc-relative relocation address for %s is too big: %#x", ldr.SymName(rs), o)
st.err.Errorf(s, "pc-relative relocation %s address for %s is too big: %#x", rt, ldr.SymName(rs), o)
} else if o != int64(int16(o)) && o != int64(uint16(o)) {
st.err.Errorf(s, "non-pc-relative relocation address for %s is too big: %#x", ldr.SymName(rs), uint64(o))
st.err.Errorf(s, "non-pc-relative relocation %s address for %s is too big: %#x", rt, ldr.SymName(rs), uint64(o))
}
target.Arch.ByteOrder.PutUint16(P[off:], uint16(o))
case 4:
if (rt == objabi.R_PCREL || rt == objabi.R_CALL) && o != int64(int32(o)) {
st.err.Errorf(s, "pc-relative relocation address for %s is too big: %#x", ldr.SymName(rs), o)
st.err.Errorf(s, "pc-relative relocation %s address for %s is too big: %#x", rt, ldr.SymName(rs), o)
} else if o != int64(int32(o)) && o != int64(uint32(o)) {
st.err.Errorf(s, "non-pc-relative relocation address for %s is too big: %#x", ldr.SymName(rs), uint64(o))
st.err.Errorf(s, "non-pc-relative relocation %s address for %s is too big: %#x", rt, ldr.SymName(rs), uint64(o))
}
target.Arch.ByteOrder.PutUint32(P[off:], uint32(o))
case 8:

View file

@ -614,16 +614,36 @@ func (state pclntab) calculateFunctabSize(ctxt *Link, funcs []loader.Sym) (int64
return size, startLocations
}
// textOff computes the offset of a text symbol, relative to textStart,
// similar to an R_ADDROFF relocation, for various runtime metadata and
// tables (see runtime/symtab.go:(*moduledata).textAddr).
func textOff(ctxt *Link, s loader.Sym, textStart int64) uint32 {
ldr := ctxt.loader
off := ldr.SymValue(s) - textStart
if off < 0 {
panic(fmt.Sprintf("expected func %s(%x) to be placed at or after textStart (%x)", ldr.SymName(s), ldr.SymValue(s), textStart))
}
if ctxt.IsWasm() {
// On Wasm, the function table contains just the function index, whereas
// the "PC" (s's Value) is function index << 16 + block index (see
// ../wasm/asm.go:assignAddress).
if off&(1<<16-1) != 0 {
ctxt.Errorf(s, "nonzero PC_B at function entry: %#x", off)
}
off >>= 16
}
if int64(uint32(off)) != off {
ctxt.Errorf(s, "textOff overflow: %#x", off)
}
return uint32(off)
}
// writePCToFunc writes the PC->func lookup table.
func writePCToFunc(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, startLocations []uint32) {
ldr := ctxt.loader
textStart := ldr.SymValue(ldr.Lookup("runtime.text", 0))
pcOff := func(s loader.Sym) uint32 {
off := ldr.SymValue(s) - textStart
if off < 0 {
panic(fmt.Sprintf("expected func %s(%x) to be placed at or after textStart (%x)", ldr.SymName(s), ldr.SymValue(s), textStart))
}
return uint32(off)
return textOff(ctxt, s, textStart)
}
for i, s := range funcs {
sb.SetUint32(ctxt.Arch, int64(i*2*4), pcOff(s))
@ -632,7 +652,11 @@ func writePCToFunc(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, sta
// Final entry of table is just end pc offset.
lastFunc := funcs[len(funcs)-1]
sb.SetUint32(ctxt.Arch, int64(len(funcs))*2*4, pcOff(lastFunc)+uint32(ldr.SymSize(lastFunc)))
lastPC := pcOff(lastFunc) + uint32(ldr.SymSize(lastFunc))
if ctxt.IsWasm() {
lastPC = pcOff(lastFunc) + 1 // On Wasm it is function index (see above)
}
sb.SetUint32(ctxt.Arch, int64(len(funcs))*2*4, lastPC)
}
// writeFuncs writes the func structures and pcdata to runtime.functab.
@ -646,7 +670,7 @@ func writeFuncs(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSym
var pcsp, pcfile, pcline, pcinline loader.Sym
var pcdata []loader.Sym
// Write the individual func objects.
// Write the individual func objects (runtime._func struct).
for i, s := range funcs {
startLine := int32(0)
fi := ldr.FuncInfo(s)
@ -658,10 +682,7 @@ func writeFuncs(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSym
off := int64(startLocations[i])
// entryOff uint32 (offset of func entry PC from textStart)
entryOff := ldr.SymValue(s) - textStart
if entryOff < 0 {
panic(fmt.Sprintf("expected func %s(%x) to be placed before or at textStart (%x)", ldr.SymName(s), ldr.SymValue(s), textStart))
}
entryOff := textOff(ctxt, s, textStart)
off = sb.SetUint32(ctxt.Arch, off, uint32(entryOff))
// nameOff int32

View file

@ -647,8 +647,15 @@ func moduledataverify1(datap *moduledata) {
min := datap.textAddr(datap.ftab[0].entryoff)
max := datap.textAddr(datap.ftab[nftab].entryoff)
if datap.minpc != min || datap.maxpc != max {
println("minpc=", hex(datap.minpc), "min=", hex(min), "maxpc=", hex(datap.maxpc), "max=", hex(max))
minpc := datap.minpc
maxpc := datap.maxpc
if GOARCH == "wasm" {
// On Wasm, the func table contains the function index, whereas
// the "PC" is function index << 16 + block index.
maxpc = alignUp(maxpc, 1<<16) // round up for end PC
}
if minpc != min || maxpc != max {
println("minpc=", hex(minpc), "min=", hex(min), "maxpc=", hex(maxpc), "max=", hex(max))
throw("minpc or maxpc invalid")
}
@ -694,6 +701,11 @@ func (md *moduledata) textAddr(off32 uint32) uintptr {
throw("runtime: text offset out of range")
}
}
if GOARCH == "wasm" {
// On Wasm, a text offset (e.g. in the method table) is function index, whereas
// the "PC" is function index << 16 + block index.
res <<= 16
}
return res
}
@ -704,8 +716,17 @@ func (md *moduledata) textAddr(off32 uint32) uintptr {
//
//go:nosplit
func (md *moduledata) textOff(pc uintptr) (uint32, bool) {
res := uint32(pc - md.text)
off := pc - md.text
if GOARCH == "wasm" {
// On Wasm, the func table contains the function index, whereas
// the "PC" is function index << 16 + block index.
off >>= 16
}
res := uint32(off)
if len(md.textsectmap) > 1 {
if GOARCH == "wasm" {
fatal("unexpected multiple text sections on Wasm")
}
for i, sect := range md.textsectmap {
if sect.baseaddr > pc {
// pc is not in any section.
@ -904,6 +925,11 @@ func findfunc(pc uintptr) funcInfo {
}
x := uintptr(pcOff) + datap.text - datap.minpc // TODO: are datap.text and datap.minpc always equal?
if GOARCH == "wasm" {
// On Wasm, pcOff is the function index, whereas
// the "PC" is function index << 16 + block index.
x = uintptr(pcOff)<<16 + datap.text - datap.minpc
}
b := x / abi.FuncTabBucketSize
i := x % abi.FuncTabBucketSize / (abi.FuncTabBucketSize / nsub)