// Copyright 2020 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 <initializer_list> #include "src/api/api-inl.h" #include "src/base/macros.h" #include "src/codegen/assembler-inl.h" #include "src/compiler/heap-refs.h" #include "src/debug/debug-evaluate.h" #include "src/debug/debug-interface.h" #include "src/diagnostics/disassembler.h" #include "src/execution/frames-inl.h" #include "src/execution/frames.h" #include "src/objects/js-objects.h" #include "src/objects/property-descriptor.h" #include "src/utils/utils.h" #include "src/utils/vector.h" #include "src/wasm/compilation-environment.h" #include "src/wasm/module-decoder.h" #include "src/wasm/value-type.h" #include "src/wasm/wasm-constants.h" #include "src/wasm/wasm-debug-evaluate.h" #include "src/wasm/wasm-debug.h" #include "src/wasm/wasm-module-builder.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects-inl.h" #include "src/wasm/wasm-opcodes.h" #include "src/wasm/wasm-tier.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 { template <typename... FunctionArgsT> class TestCode { public: TestCode(WasmRunnerBase* runner, std::initializer_list<byte> code, std::initializer_list<ValueType::Kind> locals = {}) : compiler_(&runner->NewFunction<FunctionArgsT...>()), code_(code), locals_(static_cast<int32_t>(locals.size())) { for (ValueType::Kind T : locals) { compiler_->AllocateLocal(ValueType::Primitive(T)); } compiler_->Build(code.begin(), code.end()); } Handle<BreakPoint> BreakOnReturn(WasmRunnerBase* runner) { runner->TierDown(); uint32_t return_idx = FindReturn(); uint32_t return_offset_in_function = static_cast<uint32_t>(LEBHelper::sizeof_i32v(locals_)) + 2 * locals_ + return_idx; int function_index = compiler_->function_index(); int function_offset = runner->builder().GetFunctionAt(function_index)->code.offset(); int return_offset_in_module = function_offset + return_offset_in_function; 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()); int expected_breakpoint_position = return_offset_in_module; CHECK(WasmScript::SetBreakPoint(script, &return_offset_in_module, break_point)); // Check that the breakpoint doesn't slide DCHECK_EQ(expected_breakpoint_position, return_offset_in_module); USE(expected_breakpoint_position); return break_point; } MaybeHandle<Object> Run(WasmRunnerBase* runner) { Isolate* isolate = runner->main_isolate(); Handle<JSFunction> fun_wrapper = runner->builder().WrapCode(compiler_->function_index()); Handle<Object> global(isolate->context().global_object(), isolate); return Execution::Call(isolate, fun_wrapper, global, 0, nullptr); } private: uint32_t FindReturn() const { for (auto i = code_.begin(); i != code_.end(); i += OpcodeLength(&*i, &*code_.end())) { if (*i == kExprReturn) { return static_cast<uint32_t>(std::distance(code_.begin(), i)); } } UNREACHABLE(); } WasmFunctionCompiler* compiler_; std::vector<byte> code_; int32_t locals_; }; class WasmEvaluatorBuilder { public: explicit WasmEvaluatorBuilder(TestExecutionTier execution_tier, uint32_t min_memory = 1, uint32_t max_memory = 1) : zone_(&allocator_, ZONE_NAME), builder_(&zone_) { get_memory_function_index = AddImport<void, uint32_t, uint32_t, uint32_t>( CStrVector("__getMemory")); get_local_function_index = AddImport<void, uint32_t, uint32_t>(CStrVector("__getLocal")); get_global_function_index = AddImport<void, uint32_t, uint32_t>(CStrVector("__getGlobal")); get_operand_function_index = AddImport<void, uint32_t, uint32_t>(CStrVector("__getOperand")); sbrk_function_index = AddImport<uint32_t, uint32_t>(CStrVector("__sbrk")); wasm_format_function = builder_.AddFunction(WasmRunnerBase::CreateSig<uint32_t>(&zone_)); wasm_format_function->SetName(CStrVector("wasm_format")); builder_.AddExport(CStrVector("wasm_format"), wasm_format_function); builder_.SetMinMemorySize(min_memory); builder_.SetMaxMemorySize(max_memory); } template <typename ReturnT, typename... ArgTs> uint32_t AddImport(Vector<const char> name) { return builder_.AddImport( name, WasmRunnerBase::CreateSig<ReturnT, ArgTs...>(&zone_), CStrVector("env")); } void push_back(std::initializer_list<byte> code) { wasm_format_function->EmitCode(code.begin(), static_cast<uint32_t>(code.size())); } void CallSbrk(std::initializer_list<byte> args) { push_back(args); push_back({WASM_CALL_FUNCTION0(sbrk_function_index)}); } void CallGetOperand(std::initializer_list<byte> args) { push_back(args); push_back({WASM_CALL_FUNCTION0(get_operand_function_index)}); } void CallGetGlobal(std::initializer_list<byte> args) { push_back(args); push_back({WASM_CALL_FUNCTION0(get_global_function_index)}); } void CallGetLocal(std::initializer_list<byte> args) { push_back(args); push_back({WASM_CALL_FUNCTION0(get_local_function_index)}); } void CallGetMemory(std::initializer_list<byte> args) { push_back(args); push_back({WASM_CALL_FUNCTION0(get_memory_function_index)}); } ZoneBuffer bytes() { ZoneBuffer bytes(&zone_); builder_.WriteTo(&bytes); return bytes; } private: v8::internal::AccountingAllocator allocator_; Zone zone_; WasmModuleBuilder builder_; uint32_t get_memory_function_index = 0; uint32_t get_local_function_index = 0; uint32_t get_global_function_index = 0; uint32_t get_operand_function_index = 0; uint32_t sbrk_function_index = 0; WasmFunctionBuilder* wasm_format_function = nullptr; }; class WasmBreakHandler : public debug::DebugDelegate { public: struct EvaluationResult { Maybe<std::string> result = Nothing<std::string>(); Maybe<std::string> error = Nothing<std::string>(); }; WasmBreakHandler(Isolate* isolate, ZoneBuffer evaluator_bytes) : isolate_(isolate), evaluator_bytes_(std::move(evaluator_bytes)), result_(Nothing<EvaluationResult>()) { v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this); } ~WasmBreakHandler() override { v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), nullptr); } const Maybe<EvaluationResult>& result() const { return result_; } private: Isolate* isolate_; ZoneBuffer evaluator_bytes_; Maybe<EvaluationResult> result_; Maybe<std::string> GetPendingExceptionAsString() { if (!isolate_->has_pending_exception()) return Nothing<std::string>(); Handle<Object> exception(isolate_->pending_exception(), isolate_); isolate_->clear_pending_exception(); Handle<String> exception_string; if (!Object::ToString(isolate_, exception).ToHandle(&exception_string)) { return Just<std::string>(""); } return Just<std::string>(exception_string->ToCString().get()); } void BreakProgramRequested(v8::Local<v8::Context> paused_context, const std::vector<int>&) override { // Check the current position. StackTraceFrameIterator frame_it(isolate_); WasmFrame* frame = WasmFrame::cast(frame_it.frame()); Handle<WasmInstanceObject> instance{frame->wasm_instance(), isolate_}; MaybeHandle<String> result_handle = v8::internal::wasm::DebugEvaluate( {evaluator_bytes_.begin(), evaluator_bytes_.size()}, instance, frame_it.frame()); Maybe<std::string> error_message = GetPendingExceptionAsString(); Maybe<std::string> result_message = result_handle.is_null() ? Nothing<std::string>() : Just<std::string>( result_handle.ToHandleChecked()->ToCString().get()); isolate_->clear_pending_exception(); result_ = Just<EvaluationResult>({result_message, error_message}); } }; WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_CompileFailed) { WasmRunner<int> runner(execution_tier); TestCode<int> code(&runner, {WASM_RETURN1(WASM_I32V_1(32))}); code.BreakOnReturn(&runner); WasmEvaluatorBuilder evaluator(execution_tier); // Create a module that doesn't compile by missing the END bytecode. evaluator.push_back({WASM_RETURN1(WASM_I32V_1(33))}); Isolate* isolate = runner.main_isolate(); WasmBreakHandler break_handler(isolate, evaluator.bytes()); CHECK(!code.Run(&runner).is_null()); WasmBreakHandler::EvaluationResult result = break_handler.result().ToChecked(); CHECK(result.result.IsNothing()); CHECK_NE(result.error.ToChecked().find( "function body must end with \"end\" opcode"), std::string::npos); } WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_MissingEntrypoint) { WasmRunner<int> runner(execution_tier); TestCode<int> code(&runner, {WASM_RETURN1(WASM_I32V_1(32))}); code.BreakOnReturn(&runner); v8::internal::AccountingAllocator allocator; Zone zone(&allocator, ZONE_NAME); WasmModuleBuilder evaluator(&zone); ZoneBuffer evaluator_bytes(&zone); evaluator.WriteTo(&evaluator_bytes); Isolate* isolate = runner.main_isolate(); WasmBreakHandler break_handler(isolate, std::move(evaluator_bytes)); CHECK(!code.Run(&runner).is_null()); WasmBreakHandler::EvaluationResult result = break_handler.result().ToChecked(); CHECK(result.result.IsNothing()); CHECK_NE(result.error.ToChecked().find("Missing export: \"wasm_format\""), std::string::npos); } WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_ExecuteFailed_SEGV) { WasmRunner<int> runner(execution_tier); runner.builder().AddMemoryElems<int32_t>(64); TestCode<int> code(&runner, {WASM_RETURN1(WASM_I32V_1(32))}); // Use a max memory size of 2 here to verify the precondition for the // GrowMemory test below. WasmEvaluatorBuilder evaluator(execution_tier, 1, 2); code.BreakOnReturn(&runner); // Load 1 byte from an address that's too high. evaluator.CallGetMemory( {WASM_I32V_1(32), WASM_I32V_1(1), WASM_I32V_3((1 << 16) + 1)}); evaluator.push_back({WASM_RETURN1(WASM_I32V_1(33)), WASM_END}); Isolate* isolate = runner.main_isolate(); WasmBreakHandler break_handler(isolate, evaluator.bytes()); CHECK(!code.Run(&runner).is_null()); WasmBreakHandler::EvaluationResult result = break_handler.result().ToChecked(); CHECK(result.result.IsNothing()); CHECK_NE( result.error.ToChecked().find("Illegal access to out-of-bounds memory"), std::string::npos); } WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_GrowMemory) { WasmRunner<int> runner(execution_tier); runner.builder().AddMemoryElems<int32_t>(64); TestCode<int> code( &runner, {WASM_STORE_MEM(MachineType::Int32(), WASM_I32V_1(32), WASM_I32V_2('A')), WASM_RETURN1(WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V_1(32)))}); code.BreakOnReturn(&runner); WasmEvaluatorBuilder evaluator(execution_tier, 1, 2); // Grow the memory. evaluator.CallSbrk({WASM_I32V_1(1)}); // Load 1 byte from an address that's too high for the default memory. evaluator.CallGetMemory( {WASM_I32V_1(32), WASM_I32V_1(1), WASM_I32V_3((1 << 16) + 1)}); evaluator.push_back({WASM_RETURN1(WASM_I32V_3((1 << 16) + 1)), WASM_END}); Isolate* isolate = runner.main_isolate(); WasmBreakHandler break_handler(isolate, evaluator.bytes()); CHECK(!code.Run(&runner).is_null()); WasmBreakHandler::EvaluationResult result = break_handler.result().ToChecked(); CHECK(result.error.IsNothing()); CHECK_EQ(result.result.ToChecked(), "A"); } WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_LinearMemory) { WasmRunner<int> runner(execution_tier); runner.builder().AddMemoryElems<int32_t>(64); TestCode<int> code( &runner, {WASM_STORE_MEM(MachineType::Int32(), WASM_I32V_1(32), WASM_I32V_2('A')), WASM_RETURN1(WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V_1(32)))}); code.BreakOnReturn(&runner); WasmEvaluatorBuilder evaluator(execution_tier); // Load 4 bytes from debuggee memory at address 32, and store at the offset 33 // of the linear memory. evaluator.CallGetMemory({WASM_I32V_1(32), WASM_I32V_1(4), WASM_I32V_1(33)}); evaluator.push_back({WASM_RETURN1(WASM_I32V_1(33)), WASM_END}); Isolate* isolate = runner.main_isolate(); WasmBreakHandler break_handler(isolate, evaluator.bytes()); CHECK(!code.Run(&runner).is_null()); WasmBreakHandler::EvaluationResult result = break_handler.result().ToChecked(); CHECK(result.error.IsNothing()); CHECK_EQ(result.result.ToChecked(), "A"); } WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_Locals) { WasmRunner<int> runner(execution_tier); runner.builder().AddMemoryElems<int32_t>(64); TestCode<int> code( &runner, {WASM_SET_LOCAL(0, WASM_I32V_2('A')), WASM_RETURN1(WASM_GET_LOCAL(0))}, {ValueType::kI32}); code.BreakOnReturn(&runner); WasmEvaluatorBuilder evaluator(execution_tier); evaluator.CallGetLocal({WASM_I32V_1(0), WASM_I32V_1(33)}); evaluator.push_back({WASM_RETURN1(WASM_I32V_1(33)), WASM_END}); Isolate* isolate = runner.main_isolate(); WasmBreakHandler break_handler(isolate, evaluator.bytes()); CHECK(!code.Run(&runner).is_null()); WasmBreakHandler::EvaluationResult result = break_handler.result().ToChecked(); CHECK(result.error.IsNothing()); CHECK_EQ(result.result.ToChecked(), "A"); } WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_Globals) { WasmRunner<int> runner(execution_tier); runner.builder().AddMemoryElems<int32_t>(64); runner.builder().AddGlobal<int32_t>(); runner.builder().AddGlobal<int32_t>(); TestCode<void> code(&runner, {WASM_SET_GLOBAL(0, WASM_I32V_1('4')), WASM_SET_GLOBAL(1, WASM_I32V_1('5')), WASM_RETURN0}, {}); code.BreakOnReturn(&runner); WasmEvaluatorBuilder evaluator(execution_tier); evaluator.CallGetGlobal({WASM_I32V_1(0), WASM_I32V_1(33)}); evaluator.CallGetGlobal({WASM_I32V_1(1), WASM_I32V_1(34)}); evaluator.push_back({WASM_RETURN1(WASM_I32V_1(33)), WASM_END}); Isolate* isolate = runner.main_isolate(); WasmBreakHandler break_handler(isolate, evaluator.bytes()); CHECK(!code.Run(&runner).is_null()); WasmBreakHandler::EvaluationResult result = break_handler.result().ToChecked(); CHECK(result.error.IsNothing()); CHECK_EQ(result.result.ToChecked(), "45"); } WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_Operands) { WasmRunner<int> runner(execution_tier); runner.builder().AddMemoryElems<int32_t>(64); TestCode<int> code(&runner, {WASM_SET_LOCAL(0, WASM_I32V_1('4')), WASM_GET_LOCAL(0), WASM_RETURN1(WASM_I32V_1('5'))}, {ValueType::kI32}); code.BreakOnReturn(&runner); WasmEvaluatorBuilder evaluator(execution_tier); evaluator.CallGetOperand({WASM_I32V_1(0), WASM_I32V_1(33)}); evaluator.CallGetOperand({WASM_I32V_1(1), WASM_I32V_1(34)}); evaluator.push_back({WASM_RETURN1(WASM_I32V_1(33)), WASM_END}); Isolate* isolate = runner.main_isolate(); WasmBreakHandler break_handler(isolate, evaluator.bytes()); CHECK(!code.Run(&runner).is_null()); WasmBreakHandler::EvaluationResult result = break_handler.result().ToChecked(); CHECK(result.error.IsNothing()); CHECK_EQ(result.result.ToChecked(), "45"); } } // namespace } // namespace wasm } // namespace internal } // namespace v8