Skip to content

Commit 0b632d2

Browse files
committed
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>
1 parent 6a4effa commit 0b632d2

File tree

4 files changed

+48
-2
lines changed

4 files changed

+48
-2
lines changed

‎src/cmd/internal/obj/wasm/wasmobj.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ var (
129129
morestackNoCtxt *obj.LSym
130130
sigpanic *obj.LSym
131131
wasm_pc_f_loop_export *obj.LSym
132+
runtimeNotInitialized *obj.LSym
132133
)
133134

134135
const (
@@ -149,6 +150,7 @@ func instinit(ctxt *obj.Link) {
149150
morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt")
150151
sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal)
151152
wasm_pc_f_loop_export = ctxt.Lookup("wasm_pc_f_loop_export")
153+
runtimeNotInitialized = ctxt.Lookup("runtime.notInitialized")
152154
}
153155

154156
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) {
255257
p = appendp(p, AEnd)
256258
}
257259

258-
if framesize > 0 {
260+
if framesize > 0 && s.Func().WasmExport == nil { // genWasmExportWrapper has its own prologue generation
259261
p := s.Func().Text
260262
p = appendp(p, AGet, regAddr(REG_SP))
261263
p = appendp(p, AI32Const, constAddr(framesize))
@@ -935,6 +937,23 @@ func genWasmExportWrapper(s *obj.LSym, appendp func(p *obj.Prog, as obj.As, args
935937
panic("wrapper functions for WASM export should not have a body")
936938
}
937939

940+
// Detect and error out if called before runtime initialization
941+
// SP is 0 if not initialized
942+
p = appendp(p, AGet, regAddr(REG_SP))
943+
p = appendp(p, AI32Eqz)
944+
p = appendp(p, AIf)
945+
p = appendp(p, ACall, obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: runtimeNotInitialized})
946+
p = appendp(p, AEnd)
947+
948+
// Now that we've checked the SP, generate the prologue
949+
if framesize > 0 {
950+
p = appendp(p, AGet, regAddr(REG_SP))
951+
p = appendp(p, AI32Const, constAddr(framesize))
952+
p = appendp(p, AI32Sub)
953+
p = appendp(p, ASet, regAddr(REG_SP))
954+
p.Spadj = int32(framesize)
955+
}
956+
938957
// Store args
939958
for i, f := range we.Params {
940959
p = appendp(p, AGet, regAddr(REG_SP))
@@ -1056,6 +1075,7 @@ var notUsePC_B = map[string]bool{
10561075
"runtime.gcWriteBarrier6": true,
10571076
"runtime.gcWriteBarrier7": true,
10581077
"runtime.gcWriteBarrier8": true,
1078+
"runtime.notInitialized": true,
10591079
"runtime.wasmDiv": true,
10601080
"runtime.wasmTruncS": true,
10611081
"runtime.wasmTruncU": true,
@@ -1121,7 +1141,8 @@ func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
11211141
"runtime.gcWriteBarrier5",
11221142
"runtime.gcWriteBarrier6",
11231143
"runtime.gcWriteBarrier7",
1124-
"runtime.gcWriteBarrier8":
1144+
"runtime.gcWriteBarrier8",
1145+
"runtime.notInitialized":
11251146
// no locals
11261147
useAssemblyRegMap()
11271148
default:

‎src/cmd/link/internal/wasm/asm.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ var wasmFuncTypes = map[string]*wasmFuncType{
8787
"runtime.gcWriteBarrier6": {Results: []byte{I64}}, // -> bufptr
8888
"runtime.gcWriteBarrier7": {Results: []byte{I64}}, // -> bufptr
8989
"runtime.gcWriteBarrier8": {Results: []byte{I64}}, // -> bufptr
90+
"runtime.notInitialized": {}, //
9091
"cmpbody": {Params: []byte{I64, I64, I64, I64}, Results: []byte{I64}}, // a, alen, b, blen -> -1/0/1
9192
"memeqbody": {Params: []byte{I64, I64, I64}, Results: []byte{I64}}, // a, b, len -> 0/1
9293
"memcmp": {Params: []byte{I32, I32, I32}, Results: []byte{I32}}, // a, b, len -> <0/0/>0

‎src/runtime/asm_wasm.s

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,3 +614,13 @@ TEXT runtime·pause(SB), NOSPLIT, $0-8
614614
I32Const $1
615615
Set PAUSE
616616
RETUNWIND
617+
618+
// Called if a wasmexport function is called before runtime initialization
619+
TEXT runtime·notInitialized(SB), NOSPLIT, $0
620+
MOVD $runtime·wasmStack+(m0Stack__size-16-8)(SB), SP
621+
I32Const $0 // entry PC_B
622+
Call runtime·notInitialized1(SB)
623+
Drop
624+
I32Const $0 // entry PC_B
625+
Call runtime·abort(SB)
626+
UNDEF

‎src/runtime/sys_wasm.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,17 @@ func gostartcall(buf *gobuf, fn, ctxt unsafe.Pointer) {
3434
buf.pc = uintptr(fn)
3535
buf.ctxt = ctxt
3636
}
37+
38+
func notInitialized() // defined in assembly, call notInitialized1
39+
40+
// Called if a wasmexport function is called before runtime initialization
41+
//
42+
//go:nosplit
43+
func notInitialized1() {
44+
writeErrStr("runtime: wasmexport function called before runtime initialization\n")
45+
if isarchive || islibrary {
46+
writeErrStr("\tcall _initialize first\n")
47+
} else {
48+
writeErrStr("\tcall _start first\n")
49+
}
50+
}

0 commit comments

Comments
 (0)