From: Cherry Mui Date: Tue, 26 Dec 2023 20:35:56 +0000 (-0500) Subject: cmd/link, runtime: on Wasm, put only function index in method table and func table X-Git-Tag: go1.25rc3~5^2~16 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=9c507e794297c9a7f160f71b9910d5de27b9a517;p=gostls13.git 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 Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI --- diff --git a/src/cmd/internal/obj/wasm/wasmobj.go b/src/cmd/internal/obj/wasm/wasmobj.go index e8bf927a82..3f9243613e 100644 --- a/src/cmd/internal/obj/wasm/wasmobj.go +++ b/src/cmd/internal/obj/wasm/wasmobj.go @@ -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 diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 42756e86bb..138547a3d3 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -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: diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index a09d3acd5e..9532b33a9b 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -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 diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index 866c46a83d..56f2a00d76 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -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)