From 0b632d26b99e3924aea14574e422065e13f2a1c5 Mon Sep 17 00:00:00 2001 From: Cherry Mui Date: Thu, 16 Jan 2025 13:56:15 -0500 Subject: [PATCH] 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 Reviewed-by: Michael Knyszek --- src/cmd/internal/obj/wasm/wasmobj.go | 25 +++++++++++++++++++++++-- src/cmd/link/internal/wasm/asm.go | 1 + src/runtime/asm_wasm.s | 10 ++++++++++ src/runtime/sys_wasm.go | 14 ++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/cmd/internal/obj/wasm/wasmobj.go b/src/cmd/internal/obj/wasm/wasmobj.go index 48eee4e3ea..42e5534f3b 100644 --- a/src/cmd/internal/obj/wasm/wasmobj.go +++ b/src/cmd/internal/obj/wasm/wasmobj.go @@ -129,6 +129,7 @@ var ( morestackNoCtxt *obj.LSym sigpanic *obj.LSym wasm_pc_f_loop_export *obj.LSym + runtimeNotInitialized *obj.LSym ) const ( @@ -149,6 +150,7 @@ func instinit(ctxt *obj.Link) { morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt") sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal) 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) { @@ -255,7 +257,7 @@ func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { 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 = appendp(p, AGet, regAddr(REG_SP)) 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") } + // 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 for i, f := range we.Params { p = appendp(p, AGet, regAddr(REG_SP)) @@ -1056,6 +1075,7 @@ var notUsePC_B = map[string]bool{ "runtime.gcWriteBarrier6": true, "runtime.gcWriteBarrier7": true, "runtime.gcWriteBarrier8": true, + "runtime.notInitialized": true, "runtime.wasmDiv": true, "runtime.wasmTruncS": true, "runtime.wasmTruncU": true, @@ -1121,7 +1141,8 @@ func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { "runtime.gcWriteBarrier5", "runtime.gcWriteBarrier6", "runtime.gcWriteBarrier7", - "runtime.gcWriteBarrier8": + "runtime.gcWriteBarrier8", + "runtime.notInitialized": // no locals useAssemblyRegMap() default: diff --git a/src/cmd/link/internal/wasm/asm.go b/src/cmd/link/internal/wasm/asm.go index 727da59da6..d03102cc6b 100644 --- a/src/cmd/link/internal/wasm/asm.go +++ b/src/cmd/link/internal/wasm/asm.go @@ -87,6 +87,7 @@ var wasmFuncTypes = map[string]*wasmFuncType{ "runtime.gcWriteBarrier6": {Results: []byte{I64}}, // -> bufptr "runtime.gcWriteBarrier7": {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 "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 diff --git a/src/runtime/asm_wasm.s b/src/runtime/asm_wasm.s index 016d2d3825..69da583a1d 100644 --- a/src/runtime/asm_wasm.s +++ b/src/runtime/asm_wasm.s @@ -614,3 +614,13 @@ TEXT runtime·pause(SB), NOSPLIT, $0-8 I32Const $1 Set PAUSE 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 diff --git a/src/runtime/sys_wasm.go b/src/runtime/sys_wasm.go index f88b992e9c..6b40a8d3e9 100644 --- a/src/runtime/sys_wasm.go +++ b/src/runtime/sys_wasm.go @@ -34,3 +34,17 @@ func gostartcall(buf *gobuf, fn, ctxt unsafe.Pointer) { buf.pc = uintptr(fn) 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") + } +} -- 2.48.1