9696695000
This implements the first part of WebAssembly debug evaluate. The patch includes the foundation required to execute evaluator modules. It only implements the first of the APIs of the evaluator module spec. Bug: chromium:1020120 Change-Id: I06ec98a63d0a0ec8d81c2eac4319c4b85d3e16c1 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2089936 Commit-Queue: Philip Pfaffe <pfaffe@chromium.org> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Cr-Commit-Position: refs/heads/master@{#66787}
315 lines
11 KiB
C++
315 lines
11 KiB
C++
// 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-interpreter.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)
|
|
: compiler_(&runner->NewFunction<FunctionArgsT...>()), code_(code) {
|
|
compiler_->Build(code.begin(), code.end());
|
|
}
|
|
|
|
Handle<BreakPoint> BreakOnReturn(WasmRunnerBase* runner) {
|
|
uint32_t return_offset_in_function = FindReturn();
|
|
|
|
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());
|
|
CHECK(WasmScript::SetBreakPoint(script, &return_offset_in_module,
|
|
break_point));
|
|
int set_breakpoint_offset = return_offset_in_module - function_offset;
|
|
// Also set breakpoint on the debug info of the instance directly, since
|
|
// the instance chain is not set up properly in tests.
|
|
Handle<WasmDebugInfo> debug_info =
|
|
WasmInstanceObject::GetOrCreateDebugInfo(instance);
|
|
WasmDebugInfo::SetBreakpoint(debug_info, function_index,
|
|
set_breakpoint_offset);
|
|
|
|
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_;
|
|
};
|
|
|
|
class WasmEvaluatorBuilder {
|
|
public:
|
|
explicit WasmEvaluatorBuilder(ExecutionTier 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"));
|
|
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_));
|
|
}
|
|
|
|
void push_back(std::initializer_list<byte> code) {
|
|
wasm_format_function->EmitCode(code.begin(),
|
|
static_cast<uint32_t>(code.size()));
|
|
}
|
|
|
|
void CallGetMemory(std::initializer_list<byte> args) {
|
|
push_back(args);
|
|
push_back({WASM_CALL_FUNCTION0(wasm_format_function->func_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;
|
|
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_);
|
|
|
|
FrameSummary::WasmInterpretedFrameSummary summary =
|
|
FrameSummary::GetTop(frame_it.frame()).AsWasmInterpreted();
|
|
Handle<WasmInstanceObject> instance = summary.wasm_instance();
|
|
WasmInterpreter::FramePtr frame =
|
|
instance->debug_info().GetInterpretedFrame(frame_it.frame()->fp(), 0);
|
|
|
|
MaybeHandle<String> result_handle = v8::internal::wasm::DebugEvaluate(
|
|
{evaluator_bytes_.begin(), evaluator_bytes_.size()}, instance,
|
|
std::move(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))});
|
|
|
|
// Create a module that doesn't compile by missing the END bytecode
|
|
WasmEvaluatorBuilder evaluator(execution_tier);
|
|
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_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");
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace wasm
|
|
} // namespace internal
|
|
} // namespace v8
|