Sets up the unwinding frame limit on runtime error (#2029)

Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
This commit is contained in:
Takeshi Yoneda
2024-02-08 12:15:17 -08:00
committed by GitHub
parent 71e464c670
commit 095b49f74a
8 changed files with 80 additions and 1 deletions

View File

@@ -873,6 +873,7 @@ func (ce *callEngine) deferredOnCall(ctx context.Context, m *wasm.ModuleInstance
stackBasePointer := int(ce.stackBasePointerInBytes >> 3)
functionListeners := make([]functionListenerInvocation, 0, 16)
left := wasmdebug.MaxFrames
for {
def := fn.definition()
@@ -886,6 +887,7 @@ func (ce *callEngine) deferredOnCall(ctx context.Context, m *wasm.ModuleInstance
}
}
builder.AddFrame(def.DebugName(), def.ParamTypes(), def.ResultTypes(), sources)
left--
if fn.parent.listener != nil {
functionListeners = append(functionListeners, functionListenerInvocation{
@@ -895,7 +897,7 @@ func (ce *callEngine) deferredOnCall(ctx context.Context, m *wasm.ModuleInstance
}
callFrameOffset := callFrameOffset(fn.funcType)
if stackBasePointer != 0 {
if left > 0 && stackBasePointer != 0 {
frame := *(*callFrame)(unsafe.Pointer(&ce.stack[stackBasePointer+callFrameOffset]))
fn = frame.function
pc = uint64(frame.returnAddress)

View File

@@ -624,6 +624,9 @@ func (ce *callEngine) recoverOnCall(ctx context.Context, m *wasm.ModuleInstance,
frameCount := len(ce.frames)
functionListeners := make([]functionListenerInvocation, 0, 16)
if frameCount > wasmdebug.MaxFrames {
frameCount = wasmdebug.MaxFrames
}
for i := 0; i < frameCount; i++ {
frame := ce.popFrame()
f := frame.f

View File

@@ -4,6 +4,8 @@ import (
"encoding/binary"
"reflect"
"unsafe"
"github.com/tetratelabs/wazero/internal/wasmdebug"
)
func stackView(rbp, top uintptr) []byte {
@@ -53,6 +55,9 @@ func UnwindStack(_, rbp, top uintptr, returnAddresses []uintptr) []uintptr {
retAddr := binary.LittleEndian.Uint64(stackBuf[i+8:])
returnAddresses = append(returnAddresses, uintptr(retAddr))
i = callerRBP - uint64(rbp)
if len(returnAddresses) == wasmdebug.MaxFrames {
break
}
}
return returnAddresses
}

View File

@@ -4,6 +4,8 @@ import (
"encoding/binary"
"reflect"
"unsafe"
"github.com/tetratelabs/wazero/internal/wasmdebug"
)
// UnwindStack implements wazevo.unwindStack.
@@ -55,6 +57,9 @@ func UnwindStack(sp, _, top uintptr, returnAddresses []uintptr) []uintptr {
sizeOfArgRet := binary.LittleEndian.Uint64(stackBuf[i:])
i += 8 + sizeOfArgRet
returnAddresses = append(returnAddresses, uintptr(retAddr))
if len(returnAddresses) == wasmdebug.MaxFrames {
break
}
}
return returnAddresses
}

View File

@@ -36,6 +36,7 @@ type testCase struct {
}
var tests = map[string]testCase{
"huge call stack unwind": {f: testHugeCallStackUnwind},
"imported mutable global": {f: testImportedMutableGlobalUpdate},
"huge stack": {f: testHugeStack},
"unreachable": {f: testUnreachable},
@@ -129,6 +130,8 @@ var (
globalExtendWasm []byte
//go:embed testdata/infinite_loop.wasm
infiniteLoopWasm []byte
//go:embed testdata/huge_call_stack_unwind.wasm
hugeCallStackUnwind []byte
)
func testEnsureTerminationOnClose(t *testing.T, r wazero.Runtime) {
@@ -2127,3 +2130,42 @@ func testImportedMutableGlobalUpdate(t *testing.T, r wazero.Runtime) {
v = reExportedG.Get()
require.Equal(t, uint64(2), v)
}
func testHugeCallStackUnwind(t *testing.T, r wazero.Runtime) {
ctx := context.Background()
_, err := r.Instantiate(ctx, hugeCallStackUnwind)
require.Error(t, err)
require.Equal(t, `start function[0] failed: wasm error: integer divide by zero
wasm stack trace:
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
.$0()
... maybe followed by omitted frames`, err.Error())
}

View File

@@ -0,0 +1,16 @@
(module
(func
;; check global and if it is not zero, call the function again
(if (i32.ne (global.get $g) (i32.const 0))
(then
(global.set $g (i32.sub (global.get $g) (i32.const 1)))
(call 0)
)
)
;; otherwise do i32.div by zero and crash
(i32.div_s (i32.const 0) (i32.const 0))
drop
)
(global $g (mut i32) (i32.const 1000))
(start 0)
)

View File

@@ -147,6 +147,9 @@ func (s *stackTrace) FromRecovered(recovered interface{}) error {
}
}
// MaxFrames is the maximum number of frames to include in the stack trace.
const MaxFrames = 30
// AddFrame implements ErrorBuilder.AddFrame
func (s *stackTrace) AddFrame(funcName string, paramTypes, resultTypes []api.ValueType, sources []string) {
sig := signature(funcName, paramTypes, resultTypes)
@@ -154,4 +157,7 @@ func (s *stackTrace) AddFrame(funcName string, paramTypes, resultTypes []api.Val
for _, source := range sources {
s.frames = append(s.frames, "\t"+source)
}
if len(s.frames) == MaxFrames {
s.frames = append(s.frames, "... maybe followed by omitted frames")
}
}