Skip to content

Commit

Permalink
cmd/internal/obj/wasm, runtime: detect wasmexport call before runtime…
Browse files Browse the repository at this point in the history
… 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>
  • Loading branch information
cherrymui committed Jan 16, 2025
1 parent 6a4effa commit 0b632d2
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 2 deletions.
25 changes: 23 additions & 2 deletions src/cmd/internal/obj/wasm/wasmobj.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ var (
morestackNoCtxt *obj.LSym
sigpanic *obj.LSym
wasm_pc_f_loop_export *obj.LSym
runtimeNotInitialized *obj.LSym
)

const (
Expand All @@ -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) {
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions src/cmd/link/internal/wasm/asm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions src/runtime/asm_wasm.s
Original file line number Diff line number Diff line change
Expand Up @@ -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
14 changes: 14 additions & 0 deletions src/runtime/sys_wasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}

0 comments on commit 0b632d2

Please sign in to comment.