cmd/internal/obj/wasm, runtime: detect wasmexport call before runtime initialization

If a wasmexport function is called from the host before
initializing the Go Wasm module, currently it will likely fail
with a bounds error, because the uninitialized SP is 0, and any
SP decrement will make it out of bounds.

As at least some Wasm runtime doesn't call _initialize by default,
This error can be common. And the bounds error looks confusing to
the users. Therefore, we detect this case and emit a clearer error.

Fixes #71240.
Updates #65199.

Change-Id: I107095f08c76cdceb7781ab0304218eab7029ab6
Reviewed-on: https://go-review.googlesource.com/c/go/+/643115
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
This commit is contained in:
Cherry Mui 2025-01-16 13:56:15 -05:00
parent 6a4effa08b
commit 0b632d26b9
4 changed files with 48 additions and 2 deletions

View file

@ -129,6 +129,7 @@ var (
morestackNoCtxt *obj.LSym morestackNoCtxt *obj.LSym
sigpanic *obj.LSym sigpanic *obj.LSym
wasm_pc_f_loop_export *obj.LSym wasm_pc_f_loop_export *obj.LSym
runtimeNotInitialized *obj.LSym
) )
const ( const (
@ -149,6 +150,7 @@ func instinit(ctxt *obj.Link) {
morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt") morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt")
sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal) sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal)
wasm_pc_f_loop_export = ctxt.Lookup("wasm_pc_f_loop_export") wasm_pc_f_loop_export = ctxt.Lookup("wasm_pc_f_loop_export")
runtimeNotInitialized = ctxt.Lookup("runtime.notInitialized")
} }
func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
@ -255,7 +257,7 @@ func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
p = appendp(p, AEnd) p = appendp(p, AEnd)
} }
if framesize > 0 { if framesize > 0 && s.Func().WasmExport == nil { // genWasmExportWrapper has its own prologue generation
p := s.Func().Text p := s.Func().Text
p = appendp(p, AGet, regAddr(REG_SP)) p = appendp(p, AGet, regAddr(REG_SP))
p = appendp(p, AI32Const, constAddr(framesize)) p = appendp(p, AI32Const, constAddr(framesize))
@ -935,6 +937,23 @@ func genWasmExportWrapper(s *obj.LSym, appendp func(p *obj.Prog, as obj.As, args
panic("wrapper functions for WASM export should not have a body") panic("wrapper functions for WASM export should not have a body")
} }
// Detect and error out if called before runtime initialization
// SP is 0 if not initialized
p = appendp(p, AGet, regAddr(REG_SP))
p = appendp(p, AI32Eqz)
p = appendp(p, AIf)
p = appendp(p, ACall, obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: runtimeNotInitialized})
p = appendp(p, AEnd)
// Now that we've checked the SP, generate the prologue
if framesize > 0 {
p = appendp(p, AGet, regAddr(REG_SP))
p = appendp(p, AI32Const, constAddr(framesize))
p = appendp(p, AI32Sub)
p = appendp(p, ASet, regAddr(REG_SP))
p.Spadj = int32(framesize)
}
// Store args // Store args
for i, f := range we.Params { for i, f := range we.Params {
p = appendp(p, AGet, regAddr(REG_SP)) p = appendp(p, AGet, regAddr(REG_SP))
@ -1056,6 +1075,7 @@ var notUsePC_B = map[string]bool{
"runtime.gcWriteBarrier6": true, "runtime.gcWriteBarrier6": true,
"runtime.gcWriteBarrier7": true, "runtime.gcWriteBarrier7": true,
"runtime.gcWriteBarrier8": true, "runtime.gcWriteBarrier8": true,
"runtime.notInitialized": true,
"runtime.wasmDiv": true, "runtime.wasmDiv": true,
"runtime.wasmTruncS": true, "runtime.wasmTruncS": true,
"runtime.wasmTruncU": true, "runtime.wasmTruncU": true,
@ -1121,7 +1141,8 @@ func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
"runtime.gcWriteBarrier5", "runtime.gcWriteBarrier5",
"runtime.gcWriteBarrier6", "runtime.gcWriteBarrier6",
"runtime.gcWriteBarrier7", "runtime.gcWriteBarrier7",
"runtime.gcWriteBarrier8": "runtime.gcWriteBarrier8",
"runtime.notInitialized":
// no locals // no locals
useAssemblyRegMap() useAssemblyRegMap()
default: default:

View file

@ -87,6 +87,7 @@ var wasmFuncTypes = map[string]*wasmFuncType{
"runtime.gcWriteBarrier6": {Results: []byte{I64}}, // -> bufptr "runtime.gcWriteBarrier6": {Results: []byte{I64}}, // -> bufptr
"runtime.gcWriteBarrier7": {Results: []byte{I64}}, // -> bufptr "runtime.gcWriteBarrier7": {Results: []byte{I64}}, // -> bufptr
"runtime.gcWriteBarrier8": {Results: []byte{I64}}, // -> bufptr "runtime.gcWriteBarrier8": {Results: []byte{I64}}, // -> bufptr
"runtime.notInitialized": {}, //
"cmpbody": {Params: []byte{I64, I64, I64, I64}, Results: []byte{I64}}, // a, alen, b, blen -> -1/0/1 "cmpbody": {Params: []byte{I64, I64, I64, I64}, Results: []byte{I64}}, // a, alen, b, blen -> -1/0/1
"memeqbody": {Params: []byte{I64, I64, I64}, Results: []byte{I64}}, // a, b, len -> 0/1 "memeqbody": {Params: []byte{I64, I64, I64}, Results: []byte{I64}}, // a, b, len -> 0/1
"memcmp": {Params: []byte{I32, I32, I32}, Results: []byte{I32}}, // a, b, len -> <0/0/>0 "memcmp": {Params: []byte{I32, I32, I32}, Results: []byte{I32}}, // a, b, len -> <0/0/>0

View file

@ -614,3 +614,13 @@ TEXT runtime·pause(SB), NOSPLIT, $0-8
I32Const $1 I32Const $1
Set PAUSE Set PAUSE
RETUNWIND RETUNWIND
// Called if a wasmexport function is called before runtime initialization
TEXT runtime·notInitialized(SB), NOSPLIT, $0
MOVD $runtime·wasmStack+(m0Stack__size-16-8)(SB), SP
I32Const $0 // entry PC_B
Call runtime·notInitialized1(SB)
Drop
I32Const $0 // entry PC_B
Call runtime·abort(SB)
UNDEF

View file

@ -34,3 +34,17 @@ func gostartcall(buf *gobuf, fn, ctxt unsafe.Pointer) {
buf.pc = uintptr(fn) buf.pc = uintptr(fn)
buf.ctxt = ctxt buf.ctxt = ctxt
} }
func notInitialized() // defined in assembly, call notInitialized1
// Called if a wasmexport function is called before runtime initialization
//
//go:nosplit
func notInitialized1() {
writeErrStr("runtime: wasmexport function called before runtime initialization\n")
if isarchive || islibrary {
writeErrStr("\tcall _initialize first\n")
} else {
writeErrStr("\tcall _start first\n")
}
}