v8/test/cctest/wasm/test-wasm-debug-evaluate.cc
Philip Pfaffe afd2692564 Add more index spaces to the WebAssembly JS debug proxy
This CL adds the globals index space to the JS debug proxy as well as the
stack object. It also adds few small helpers to simplify the proxy setup
a little, since all index spaces work exaclty the same.

Bug: chromium:1127914
Change-Id: I707292ab7f44aafb73751c17fdacfef976316f39
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2448468
Commit-Queue: Philip Pfaffe <pfaffe@chromium.org>
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70332}
2020-10-06 09:43:06 +00:00

576 lines
20 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-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 {
static Handle<String> V8String(Isolate* isolate, const char* str) {
return isolate->factory()->NewStringFromAsciiChecked(str);
}
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});
}
};
class WasmJSBreakHandler : public debug::DebugDelegate {
public:
struct EvaluationResult {
Maybe<std::string> result = Nothing<std::string>();
Maybe<std::string> error = Nothing<std::string>();
};
WasmJSBreakHandler(Isolate* isolate, Handle<String> snippet)
: isolate_(isolate),
snippet_(snippet),
result_(Nothing<EvaluationResult>()) {
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this);
}
~WasmJSBreakHandler() override {
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_),
nullptr);
}
const Maybe<EvaluationResult>& result() const { return result_; }
private:
Isolate* isolate_;
Handle<String> snippet_;
Maybe<EvaluationResult> result_;
Maybe<std::string> GetPendingExceptionAsString() const {
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());
}
Maybe<std::string> GetResultAsString(MaybeHandle<Object> result) const {
Handle<Object> just_result;
if (!result.ToHandle(&just_result)) return Nothing<std::string>();
MaybeHandle<String> maybe_string = Object::ToString(isolate_, just_result);
Handle<String> just_string;
if (!maybe_string.ToHandle(&just_string)) return Nothing<std::string>();
return Just<std::string>(just_string->ToCString().get());
}
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
const std::vector<int>&) override {
StackTraceFrameIterator frame_it(isolate_);
WasmFrame* frame = WasmFrame::cast(frame_it.frame());
Handle<WasmInstanceObject> instance{frame->wasm_instance(), isolate_};
MaybeHandle<Object> result_handle = DebugEvaluate::WebAssembly(
instance, frame_it.frame()->id(), snippet_, false);
Maybe<std::string> error_message = GetPendingExceptionAsString();
Maybe<std::string> result_message = GetResultAsString(result_handle);
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");
}
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_JavaScript) {
WasmRunner<int> runner(execution_tier);
runner.builder().AddGlobal<int32_t>();
runner.builder().AddMemoryElems<int32_t>(64);
uint16_t index = 0;
runner.builder().AddIndirectFunctionTable(&index, 1);
TestCode<int64_t> code(
&runner,
{WASM_SET_GLOBAL(0, WASM_I32V_2('B')),
WASM_SET_LOCAL(0, WASM_I64V_2('A')), WASM_RETURN1(WASM_GET_LOCAL(0))},
{ValueType::kI64});
code.BreakOnReturn(&runner);
Isolate* isolate = runner.main_isolate();
Handle<String> snippet =
V8String(isolate,
"JSON.stringify(["
"$global0, "
//"$table0, "
"$var0, "
//"$main, "
//"$memory0, "
"globals[0], "
//"tables[0], "
"locals[0], "
//"functions[0], "
//"memories[0], "
//"memories, "
//"tables, "
//"stack, "
//"imports, "
//"exports, "
"globals, "
"locals, "
//"functions, "
"], (k, v) => k === 'at' || typeof v === 'undefined' || typeof "
"v === 'object' ? v : v.toString())");
WasmJSBreakHandler break_handler(isolate, snippet);
CHECK(!code.Run(&runner).is_null());
WasmJSBreakHandler::EvaluationResult result =
break_handler.result().ToChecked();
CHECK_WITH_MSG(result.error.IsNothing(), result.error.ToChecked().c_str());
CHECK_EQ(result.result.ToChecked(), "[\"66\",\"65\",\"66\",\"65\",{},{}]");
//"[\"66\",{},\"65\",\"function 0() { [native code] }\",{},"
//"\"66\",{},\"65\",\"function 0() { [native code] }\",{},"
//"{},{},{\"0\":\"65\"},{},{},{},{},{}]");
}
} // namespace
} // namespace wasm
} // namespace internal
} // namespace v8