]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/internal/obj/wasm, runtime: detect wasmexport call before runtime initialization
authorCherry Mui <cherryyz@google.com>
Thu, 16 Jan 2025 18:56:15 +0000 (13:56 -0500)
committerCherry Mui <cherryyz@google.com>
Thu, 16 Jan 2025 20:14:23 +0000 (12:14 -0800)
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>
src/cmd/internal/obj/wasm/wasmobj.go
src/cmd/link/internal/wasm/asm.go
src/runtime/asm_wasm.s
src/runtime/sys_wasm.go

index 48eee4e3ea12de20f887ca22cbb058cd5de3b37f..42e5534f3b6254cc31960c0f92c30b6be76db4b7 100644 (file)
@@ -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:
index 727da59da630d44a62f80a35212cecc9c3efaa19..d03102cc6be629cc1cbeb13e709bc7cc6ca5ab9a 100644 (file)
@@ -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
index 016d2d3825ac49f8780621f21c6118daabdfb7f6..69da583a1d54e9635bd45afc4b41ea28cf441c68 100644 (file)
@@ -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
index f88b992e9c39a38a4670047152f7627001e3c51f..6b40a8d3e94c96acc27cc65b43c263e285e9afd8 100644 (file)
@@ -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")
+       }
+}