// Copyright 2016 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/codegen/assembler-inl.h" #include "src/debug/debug-interface.h" #include "src/execution/frames-inl.h" #include "src/objects/property-descriptor.h" #include "src/utils/utils.h" #include "src/wasm/wasm-debug.h" #include "src/wasm/wasm-objects-inl.h" #include "test/cctest/cctest.h" #include "test/cctest/compiler/value-helper.h" #include "test/cctest/wasm/wasm-run-utils.h" #include "test/common/wasm/test-signatures.h" #include "test/common/wasm/wasm-macro-gen.h" namespace v8 { namespace internal { namespace wasm { namespace { debug::Location TranslateLocation(WasmRunnerBase* runner, const debug::Location& loc) { // Convert locations from {func_index, offset_in_func} to // {0, offset_in_module}. int func_index = loc.GetLineNumber(); int func_offset = runner->builder().GetFunctionAt(func_index)->code.offset(); int offset = loc.GetColumnNumber() + func_offset; return {0, offset}; } void CheckLocations( WasmRunnerBase* runner, NativeModule* native_module, debug::Location start, debug::Location end, std::initializer_list<debug::Location> expected_locations_init) { std::vector<debug::BreakLocation> locations; std::vector<debug::Location> expected_locations; for (auto loc : expected_locations_init) { expected_locations.push_back(TranslateLocation(runner, loc)); } bool success = WasmScript::GetPossibleBreakpoints( native_module, TranslateLocation(runner, start), TranslateLocation(runner, end), &locations); CHECK(success); printf("got %d locations: ", static_cast<int>(locations.size())); for (size_t i = 0, e = locations.size(); i != e; ++i) { printf("%s<%d,%d>", i == 0 ? "" : ", ", locations[i].GetLineNumber(), locations[i].GetColumnNumber()); } printf("\n"); CHECK_EQ(expected_locations.size(), locations.size()); for (size_t i = 0, e = locations.size(); i != e; ++i) { CHECK_EQ(expected_locations[i].GetLineNumber(), locations[i].GetLineNumber()); CHECK_EQ(expected_locations[i].GetColumnNumber(), locations[i].GetColumnNumber()); } } void CheckLocationsFail(WasmRunnerBase* runner, NativeModule* native_module, debug::Location start, debug::Location end) { std::vector<debug::BreakLocation> locations; bool success = WasmScript::GetPossibleBreakpoints( native_module, TranslateLocation(runner, start), TranslateLocation(runner, end), &locations); CHECK(!success); } class BreakHandler : public debug::DebugDelegate { public: enum Action { Continue = StepAction::LastStepAction + 1, StepOver = StepAction::StepOver, StepInto = StepAction::StepInto, StepOut = StepAction::StepOut }; struct BreakPoint { int position; Action action; std::function<void(void)> pre_action; BreakPoint(int position, Action action) : position(position), action(action), pre_action([]() {}) {} BreakPoint(int position, Action action, std::function<void(void)> pre_action) : position(position), action(action), pre_action(pre_action) {} }; explicit BreakHandler(Isolate* isolate, std::initializer_list<BreakPoint> expected_breaks) : isolate_(isolate), expected_breaks_(expected_breaks) { v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this); } ~BreakHandler() override { // Check that all expected breakpoints have been hit. CHECK_EQ(count_, expected_breaks_.size()); v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), nullptr); } int count() const { return count_; } private: Isolate* isolate_; int count_ = 0; std::vector<BreakPoint> expected_breaks_; void BreakProgramRequested(v8::Local<v8::Context> paused_context, const std::vector<int>&) override { printf("Break #%d\n", count_); CHECK_GT(expected_breaks_.size(), count_); // Check the current position. StackTraceFrameIterator frame_it(isolate_); auto summ = FrameSummary::GetTop(frame_it.frame()).AsWasm(); CHECK_EQ(expected_breaks_[count_].position, summ.byte_offset()); expected_breaks_[count_].pre_action(); Action next_action = expected_breaks_[count_].action; switch (next_action) { case Continue: break; case StepOver: case StepInto: case StepOut: isolate_->debug()->PrepareStep(static_cast<StepAction>(next_action)); break; default: UNREACHABLE(); } ++count_; } }; Handle<BreakPoint> SetBreakpoint(WasmRunnerBase* runner, int function_index, int byte_offset, int expected_set_byte_offset = -1) { runner->TierDown(); int func_offset = runner->builder().GetFunctionAt(function_index)->code.offset(); int code_offset = func_offset + byte_offset; if (expected_set_byte_offset == -1) expected_set_byte_offset = byte_offset; Handle<WasmInstanceObject> instance = runner->builder().instance_object(); Handle<Script> script(instance->module_object().script(), runner->main_isolate()); static int break_index = 0; Handle<BreakPoint> break_point = runner->main_isolate()->factory()->NewBreakPoint( break_index++, runner->main_isolate()->factory()->empty_string()); CHECK(WasmScript::SetBreakPoint(script, &code_offset, break_point)); return break_point; } void ClearBreakpoint(WasmRunnerBase* runner, int function_index, int byte_offset, Handle<BreakPoint> break_point) { int func_offset = runner->builder().GetFunctionAt(function_index)->code.offset(); int code_offset = func_offset + byte_offset; Handle<WasmInstanceObject> instance = runner->builder().instance_object(); Handle<Script> script(instance->module_object().script(), runner->main_isolate()); CHECK(WasmScript::ClearBreakPoint(script, code_offset, break_point)); } // Wrapper with operator<<. struct WasmValWrapper { WasmValue val; bool operator==(const WasmValWrapper& other) const { return val == other.val; } }; // Only needed in debug builds. Avoid unused warning otherwise. #ifdef DEBUG std::ostream& operator<<(std::ostream& out, const WasmValWrapper& wrapper) { switch (wrapper.val.type().kind()) { case kI32: out << "i32: " << wrapper.val.to<int32_t>(); break; case kI64: out << "i64: " << wrapper.val.to<int64_t>(); break; case kF32: out << "f32: " << wrapper.val.to<float>(); break; case kF64: out << "f64: " << wrapper.val.to<double>(); break; default: UNIMPLEMENTED(); } return out; } #endif class CollectValuesBreakHandler : public debug::DebugDelegate { public: struct BreakpointValues { std::vector<WasmValue> locals; std::vector<WasmValue> stack; }; explicit CollectValuesBreakHandler( Isolate* isolate, std::initializer_list<BreakpointValues> expected_values) : isolate_(isolate), expected_values_(expected_values) { v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this); } ~CollectValuesBreakHandler() override { v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), nullptr); } private: Isolate* isolate_; int count_ = 0; std::vector<BreakpointValues> expected_values_; void BreakProgramRequested(v8::Local<v8::Context> paused_context, const std::vector<int>&) override { printf("Break #%d\n", count_); CHECK_GT(expected_values_.size(), count_); auto& expected = expected_values_[count_]; ++count_; HandleScope handles(isolate_); StackTraceFrameIterator frame_it(isolate_); WasmFrame* frame = WasmFrame::cast(frame_it.frame()); DebugInfo* debug_info = frame->native_module()->GetDebugInfo(); int num_locals = debug_info->GetNumLocals(frame->pc()); CHECK_EQ(expected.locals.size(), num_locals); for (int i = 0; i < num_locals; ++i) { WasmValue local_value = debug_info->GetLocalValue( i, frame->pc(), frame->fp(), frame->callee_fp(), isolate_); CHECK_EQ(WasmValWrapper{expected.locals[i]}, WasmValWrapper{local_value}); } int stack_depth = debug_info->GetStackDepth(frame->pc()); CHECK_EQ(expected.stack.size(), stack_depth); for (int i = 0; i < stack_depth; ++i) { WasmValue stack_value = debug_info->GetStackValue( i, frame->pc(), frame->fp(), frame->callee_fp(), isolate_); CHECK_EQ(WasmValWrapper{expected.stack[i]}, WasmValWrapper{stack_value}); } isolate_->debug()->PrepareStep(StepAction::StepInto); } }; // Special template to explicitly cast to WasmValue. template <typename Arg> WasmValue MakeWasmVal(Arg arg) { return WasmValue(arg); } // Translate long to i64 (ambiguous otherwise). template <> WasmValue MakeWasmVal(long arg) { // NOLINT: allow long parameter return WasmValue(static_cast<int64_t>(arg)); } template <typename... Args> std::vector<WasmValue> wasmVec(Args... args) { std::array<WasmValue, sizeof...(args)> arr{{MakeWasmVal(args)...}}; return std::vector<WasmValue>{arr.begin(), arr.end()}; } int GetIntReturnValue(MaybeHandle<Object> retval) { CHECK(!retval.is_null()); int result; CHECK(retval.ToHandleChecked()->ToInt32(&result)); return result; } } // namespace WASM_COMPILED_EXEC_TEST(WasmCollectPossibleBreakpoints) { WasmRunner<int> runner(execution_tier); BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_ZERO, WASM_ONE)); WasmInstanceObject instance = *runner.builder().instance_object(); NativeModule* native_module = instance.module_object().native_module(); std::vector<debug::Location> locations; // Check all locations for function 0. CheckLocations(&runner, native_module, {0, 0}, {0, 10}, {{0, 1}, {0, 2}, {0, 4}, {0, 6}, {0, 7}}); // Check a range ending at an instruction. CheckLocations(&runner, native_module, {0, 2}, {0, 4}, {{0, 2}}); // Check a range ending one behind an instruction. CheckLocations(&runner, native_module, {0, 2}, {0, 5}, {{0, 2}, {0, 4}}); // Check a range starting at an instruction. CheckLocations(&runner, native_module, {0, 7}, {0, 8}, {{0, 7}}); // Check from an instruction to beginning of next function. CheckLocations(&runner, native_module, {0, 7}, {0, 10}, {{0, 7}}); // Check from end of one function (no valid instruction position) to beginning // of next function. Must be empty, but not fail. CheckLocations(&runner, native_module, {0, 8}, {0, 10}, {}); // Check from one after the end of the function. Must fail. CheckLocationsFail(&runner, native_module, {0, 9}, {0, 10}); } WASM_COMPILED_EXEC_TEST(WasmSimpleBreak) { WasmRunner<int> runner(execution_tier); Isolate* isolate = runner.main_isolate(); BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3))); Handle<JSFunction> main_fun_wrapper = runner.builder().WrapCode(runner.function_index()); SetBreakpoint(&runner, runner.function_index(), 4, 4); BreakHandler count_breaks(isolate, {{4, BreakHandler::Continue}}); Handle<Object> global(isolate->context().global_object(), isolate); MaybeHandle<Object> retval = Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr); CHECK_EQ(14, GetIntReturnValue(retval)); } WASM_COMPILED_EXEC_TEST(WasmNonBreakablePosition) { WasmRunner<int> runner(execution_tier); Isolate* isolate = runner.main_isolate(); BUILD(runner, WASM_RETURN1(WASM_I32V_2(1024))); Handle<JSFunction> main_fun_wrapper = runner.builder().WrapCode(runner.function_index()); SetBreakpoint(&runner, runner.function_index(), 2, 4); BreakHandler count_breaks(isolate, {{4, BreakHandler::Continue}}); Handle<Object> global(isolate->context().global_object(), isolate); MaybeHandle<Object> retval = Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr); CHECK_EQ(1024, GetIntReturnValue(retval)); } WASM_COMPILED_EXEC_TEST(WasmSimpleStepping) { WasmRunner<int> runner(execution_tier); BUILD(runner, WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3))); Isolate* isolate = runner.main_isolate(); Handle<JSFunction> main_fun_wrapper = runner.builder().WrapCode(runner.function_index()); // Set breakpoint at the first I32Const. SetBreakpoint(&runner, runner.function_index(), 1, 1); BreakHandler count_breaks(isolate, { {1, BreakHandler::StepOver}, // I32Const {3, BreakHandler::StepOver}, // I32Const {5, BreakHandler::Continue} // I32Add }); Handle<Object> global(isolate->context().global_object(), isolate); MaybeHandle<Object> retval = Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr); CHECK_EQ(14, GetIntReturnValue(retval)); } WASM_COMPILED_EXEC_TEST(WasmStepInAndOut) { WasmRunner<int, int> runner(execution_tier); runner.TierDown(); WasmFunctionCompiler& f2 = runner.NewFunction<void>(); f2.AllocateLocal(kWasmI32); // Call f2 via indirect call, because a direct call requires f2 to exist when // we compile main, but we need to compile main first so that the order of // functions in the code section matches the function indexes. // return arg0 BUILD(runner, WASM_RETURN1(WASM_LOCAL_GET(0))); // for (int i = 0; i < 10; ++i) { f2(i); } BUILD(f2, WASM_LOOP( WASM_BR_IF(0, WASM_BINOP(kExprI32GeU, WASM_LOCAL_GET(0), WASM_I32V_1(10))), WASM_LOCAL_SET( 0, WASM_BINOP(kExprI32Sub, WASM_LOCAL_GET(0), WASM_ONE)), WASM_CALL_FUNCTION(runner.function_index(), WASM_LOCAL_GET(0)), WASM_DROP, WASM_BR(1))); Isolate* isolate = runner.main_isolate(); Handle<JSFunction> main_fun_wrapper = runner.builder().WrapCode(f2.function_index()); // Set first breakpoint on the LocalGet (offset 19) before the Call. SetBreakpoint(&runner, f2.function_index(), 19, 19); BreakHandler count_breaks(isolate, { {19, BreakHandler::StepInto}, // LocalGet {21, BreakHandler::StepInto}, // Call {1, BreakHandler::StepOut}, // in f2 {23, BreakHandler::Continue} // After Call }); Handle<Object> global(isolate->context().global_object(), isolate); CHECK(!Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr) .is_null()); } WASM_COMPILED_EXEC_TEST(WasmGetLocalsAndStack) { WasmRunner<void, int> runner(execution_tier); runner.AllocateLocal(kWasmI64); runner.AllocateLocal(kWasmF32); runner.AllocateLocal(kWasmF64); BUILD(runner, // set [1] to 17 WASM_LOCAL_SET(1, WASM_I64V_1(17)), // set [2] to <arg0> = 7 WASM_LOCAL_SET(2, WASM_F32_SCONVERT_I32(WASM_LOCAL_GET(0))), // set [3] to <arg1>/2 = 8.5 WASM_LOCAL_SET(3, WASM_F64_DIV(WASM_F64_SCONVERT_I64(WASM_LOCAL_GET(1)), WASM_F64(2)))); Isolate* isolate = runner.main_isolate(); Handle<JSFunction> main_fun_wrapper = runner.builder().WrapCode(runner.function_index()); // Set breakpoint at the first instruction (7 bytes for local decls: num // entries + 3x<count, type>). SetBreakpoint(&runner, runner.function_index(), 7, 7); CollectValuesBreakHandler break_handler( isolate, { // params + locals stack {wasmVec(7, 0L, 0.f, 0.), wasmVec()}, // 0: i64.const[17] {wasmVec(7, 0L, 0.f, 0.), wasmVec(17L)}, // 1: set_local[1] {wasmVec(7, 17L, 0.f, 0.), wasmVec()}, // 2: get_local[0] {wasmVec(7, 17L, 0.f, 0.), wasmVec(7)}, // 3: f32.convert_s {wasmVec(7, 17L, 0.f, 0.), wasmVec(7.f)}, // 4: set_local[2] {wasmVec(7, 17L, 7.f, 0.), wasmVec()}, // 5: get_local[1] {wasmVec(7, 17L, 7.f, 0.), wasmVec(17L)}, // 6: f64.convert_s {wasmVec(7, 17L, 7.f, 0.), wasmVec(17.)}, // 7: f64.const[2] {wasmVec(7, 17L, 7.f, 0.), wasmVec(17., 2.)}, // 8: f64.div {wasmVec(7, 17L, 7.f, 0.), wasmVec(8.5)}, // 9: set_local[3] {wasmVec(7, 17L, 7.f, 8.5), wasmVec()}, // 10: end }); Handle<Object> global(isolate->context().global_object(), isolate); Handle<Object> args[]{handle(Smi::FromInt(7), isolate)}; CHECK(!Execution::Call(isolate, main_fun_wrapper, global, 1, args).is_null()); } WASM_COMPILED_EXEC_TEST(WasmRemoveBreakPoint) { WasmRunner<int> runner(execution_tier); Isolate* isolate = runner.main_isolate(); BUILD(runner, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_I32V_1(14)); Handle<JSFunction> main_fun_wrapper = runner.builder().WrapCode(runner.function_index()); SetBreakpoint(&runner, runner.function_index(), 1, 1); SetBreakpoint(&runner, runner.function_index(), 2, 2); Handle<BreakPoint> to_delete = SetBreakpoint(&runner, runner.function_index(), 3, 3); SetBreakpoint(&runner, runner.function_index(), 4, 4); BreakHandler count_breaks(isolate, {{1, BreakHandler::Continue}, {2, BreakHandler::Continue, [&runner, &to_delete]() { ClearBreakpoint( &runner, runner.function_index(), 3, to_delete); }}, {4, BreakHandler::Continue}}); Handle<Object> global(isolate->context().global_object(), isolate); MaybeHandle<Object> retval = Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr); CHECK_EQ(14, GetIntReturnValue(retval)); } WASM_COMPILED_EXEC_TEST(WasmRemoveLastBreakPoint) { WasmRunner<int> runner(execution_tier); Isolate* isolate = runner.main_isolate(); BUILD(runner, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_I32V_1(14)); Handle<JSFunction> main_fun_wrapper = runner.builder().WrapCode(runner.function_index()); SetBreakpoint(&runner, runner.function_index(), 1, 1); SetBreakpoint(&runner, runner.function_index(), 2, 2); Handle<BreakPoint> to_delete = SetBreakpoint(&runner, runner.function_index(), 3, 3); BreakHandler count_breaks( isolate, {{1, BreakHandler::Continue}, {2, BreakHandler::Continue, [&runner, &to_delete]() { ClearBreakpoint(&runner, runner.function_index(), 3, to_delete); }}}); Handle<Object> global(isolate->context().global_object(), isolate); MaybeHandle<Object> retval = Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr); CHECK_EQ(14, GetIntReturnValue(retval)); } WASM_COMPILED_EXEC_TEST(WasmRemoveAllBreakPoint) { WasmRunner<int> runner(execution_tier); Isolate* isolate = runner.main_isolate(); BUILD(runner, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_I32V_1(14)); Handle<JSFunction> main_fun_wrapper = runner.builder().WrapCode(runner.function_index()); Handle<BreakPoint> bp1 = SetBreakpoint(&runner, runner.function_index(), 1, 1); Handle<BreakPoint> bp2 = SetBreakpoint(&runner, runner.function_index(), 2, 2); Handle<BreakPoint> bp3 = SetBreakpoint(&runner, runner.function_index(), 3, 3); BreakHandler count_breaks( isolate, {{1, BreakHandler::Continue, [&runner, &bp1, &bp2, &bp3]() { ClearBreakpoint(&runner, runner.function_index(), 1, bp1); ClearBreakpoint(&runner, runner.function_index(), 3, bp3); ClearBreakpoint(&runner, runner.function_index(), 2, bp2); }}}); Handle<Object> global(isolate->context().global_object(), isolate); MaybeHandle<Object> retval = Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr); CHECK_EQ(14, GetIntReturnValue(retval)); } WASM_COMPILED_EXEC_TEST(WasmBreakInPostMVP) { // This test checks that we don't fail if experimental / post-MVP opcodes are // being used. There was a bug where we were trying to update the "detected" // features set, but we were passing a nullptr when compiling with // breakpoints. WasmRunner<int> runner(execution_tier); Isolate* isolate = runner.main_isolate(); // [] -> [i32, i32] ValueType sig_types[] = {kWasmI32, kWasmI32}; FunctionSig sig{2, 0, sig_types}; uint8_t sig_idx = runner.builder().AddSignature(&sig); constexpr int kReturn = 13; constexpr int kIgnored = 23; BUILD(runner, WASM_BLOCK_X(sig_idx, WASM_I32V_1(kReturn), WASM_I32V_1(kIgnored)), WASM_DROP); Handle<JSFunction> main_fun_wrapper = runner.builder().WrapCode(runner.function_index()); SetBreakpoint(&runner, runner.function_index(), 3, 3); BreakHandler count_breaks(isolate, {{3, BreakHandler::Continue}}); Handle<Object> global(isolate->context().global_object(), isolate); MaybeHandle<Object> retval = Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr); CHECK_EQ(kReturn, GetIntReturnValue(retval)); } WASM_COMPILED_EXEC_TEST(Regress10889) { FLAG_SCOPE(print_wasm_code); WasmRunner<int> runner(execution_tier); BUILD(runner, WASM_I32V_1(0)); SetBreakpoint(&runner, runner.function_index(), 1, 1); } } // namespace wasm } // namespace internal } // namespace v8