summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAli Mohammad Pur <ali.mpfard@gmail.com>2021-05-21 21:10:44 +0430
committerAli Mohammad Pur <Ali.mpfard@gmail.com>2021-05-26 15:34:13 +0430
commitba5da79617fed8f059bde2b4adb263e87f73c7e6 (patch)
treebe8ad7bf334e029e249a42034c9efde62a1cf77e
parentf740667fa11e5f4979f78635e62e8064861b6832 (diff)
downloadserenity-ba5da79617fed8f059bde2b4adb263e87f73c7e6.zip
LibWasm: Add execution hooks and a debugger mode to the wasm tool
This is useful for debugging *our* implementation of wasm :P
-rw-r--r--Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp9
-rw-r--r--Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h4
-rw-r--r--Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp3
-rw-r--r--Userland/Libraries/LibWasm/AbstractMachine/Configuration.h3
-rw-r--r--Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp23
-rw-r--r--Userland/Libraries/LibWasm/AbstractMachine/Interpreter.h4
-rw-r--r--Userland/Utilities/CMakeLists.txt2
-rw-r--r--Userland/Utilities/wasm.cpp255
8 files changed, 299 insertions, 4 deletions
diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp
index 65960ad9aa..49704fddd4 100644
--- a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp
+++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp
@@ -113,6 +113,8 @@ InstantiationResult AbstractMachine::instantiate(const Module& module, Vector<Ex
entry.expression(),
1);
Configuration config { m_store };
+ config.pre_interpret_hook = &pre_interpret_hook;
+ config.post_interpret_hook = &post_interpret_hook;
config.set_frame(move(frame));
auto result = config.execute();
// What if this traps?
@@ -143,6 +145,8 @@ InstantiationResult AbstractMachine::instantiate(const Module& module, Vector<Ex
data.offset,
1);
Configuration config { m_store };
+ config.pre_interpret_hook = &pre_interpret_hook;
+ config.post_interpret_hook = &post_interpret_hook;
config.set_frame(move(frame));
auto result = config.execute();
size_t offset = 0;
@@ -281,7 +285,10 @@ Optional<InstantiationError> AbstractMachine::allocate_all(const Module& module,
Result AbstractMachine::invoke(FunctionAddress address, Vector<Value> arguments)
{
- return Configuration { m_store }.call(address, move(arguments));
+ Configuration configuration { m_store };
+ configuration.pre_interpret_hook = &pre_interpret_hook;
+ configuration.post_interpret_hook = &post_interpret_hook;
+ return configuration.call(address, move(arguments));
}
void Linker::link(const ModuleInstance& instance)
diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h
index 315d6cee8d..dd51e74225 100644
--- a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h
+++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h
@@ -16,6 +16,7 @@
namespace Wasm {
class Configuration;
+struct Interpreter;
struct InstantiationError {
String error { "Unknown error" };
@@ -445,6 +446,9 @@ public:
auto& store() const { return m_store; }
auto& store() { return m_store; }
+ Function<bool(Configuration&, InstructionPointer&, const Instruction&)> pre_interpret_hook;
+ Function<bool(Configuration&, InstructionPointer&, const Instruction&, const Interpreter&)> post_interpret_hook;
+
private:
Optional<InstantiationError> allocate_all(const Module&, ModuleInstance&, Vector<ExternValue>&, Vector<Value>& global_values);
Store m_store;
diff --git a/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp b/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp
index 09894d3469..171ec39547 100644
--- a/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp
+++ b/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp
@@ -53,6 +53,9 @@ Result Configuration::call(FunctionAddress address, Vector<Value> arguments)
Result Configuration::execute()
{
Interpreter interpreter;
+ interpreter.pre_interpret_hook = pre_interpret_hook;
+ interpreter.post_interpret_hook = post_interpret_hook;
+
interpreter.interpret(*this);
if (interpreter.did_trap())
return Trap {};
diff --git a/Userland/Libraries/LibWasm/AbstractMachine/Configuration.h b/Userland/Libraries/LibWasm/AbstractMachine/Configuration.h
index 8299f80420..dc06a114d6 100644
--- a/Userland/Libraries/LibWasm/AbstractMachine/Configuration.h
+++ b/Userland/Libraries/LibWasm/AbstractMachine/Configuration.h
@@ -40,6 +40,9 @@ public:
void dump_stack();
+ Function<bool(Configuration&, InstructionPointer&, const Instruction&)>* pre_interpret_hook { nullptr };
+ Function<bool(Configuration&, InstructionPointer&, const Instruction&, const Interpreter&)>* post_interpret_hook { nullptr };
+
private:
Store& m_store;
Frame* m_current_frame { nullptr };
diff --git a/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp b/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp
index d4ff51e630..50ead0dd8e 100644
--- a/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp
+++ b/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp
@@ -126,6 +126,8 @@ void Interpreter::call_address(Configuration& configuration, FunctionAddress add
args.prepend(move(*configuration.stack().pop().get<NonnullOwnPtr<Value>>()));
}
Configuration function_configuration { configuration.store() };
+ function_configuration.pre_interpret_hook = pre_interpret_hook;
+ function_configuration.post_interpret_hook = post_interpret_hook;
function_configuration.depth() = configuration.depth() + 1;
auto result = function_configuration.call(address, move(args));
if (result.is_trap()) {
@@ -338,8 +340,25 @@ Vector<NonnullOwnPtr<Value>> Interpreter::pop_values(Configuration& configuratio
void Interpreter::interpret(Configuration& configuration, InstructionPointer& ip, const Instruction& instruction)
{
dbgln_if(WASM_TRACE_DEBUG, "Executing instruction {} at ip {}", instruction_name(instruction.opcode()), ip.value());
- if constexpr (WASM_TRACE_DEBUG)
- configuration.dump_stack();
+
+ if (pre_interpret_hook && *pre_interpret_hook) {
+ auto result = pre_interpret_hook->operator()(configuration, ip, instruction);
+ if (!result) {
+ m_do_trap = true;
+ return;
+ }
+ }
+
+ ScopeGuard guard { [&] {
+ if (post_interpret_hook && *post_interpret_hook) {
+ auto result = post_interpret_hook->operator()(configuration, ip, instruction, *this);
+ if (!result) {
+ m_do_trap = true;
+ return;
+ }
+ }
+ } };
+
switch (instruction.opcode().value()) {
case Instructions::unreachable.value():
m_do_trap = true;
diff --git a/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.h b/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.h
index 7f5dfcdb5c..68b02550f3 100644
--- a/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.h
+++ b/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.h
@@ -13,6 +13,10 @@ namespace Wasm {
struct Interpreter {
void interpret(Configuration&);
bool did_trap() const { return m_do_trap; }
+ void clear_trap() { m_do_trap = false; }
+
+ Function<bool(Configuration&, InstructionPointer&, const Instruction&)>* pre_interpret_hook { nullptr };
+ Function<bool(Configuration&, InstructionPointer&, const Instruction&, const Interpreter&)>* post_interpret_hook { nullptr };
private:
void interpret(Configuration&, InstructionPointer&, const Instruction&);
diff --git a/Userland/Utilities/CMakeLists.txt b/Userland/Utilities/CMakeLists.txt
index dd63e4dce6..db281f1c7b 100644
--- a/Userland/Utilities/CMakeLists.txt
+++ b/Userland/Utilities/CMakeLists.txt
@@ -56,4 +56,4 @@ target_link_libraries(unzip LibArchive LibCompress)
target_link_libraries(zip LibArchive LibCompress LibCrypto)
target_link_libraries(cpp-parser LibCpp LibGUI)
target_link_libraries(PreprocessorTest LibCpp LibGUI)
-target_link_libraries(wasm LibWasm)
+target_link_libraries(wasm LibWasm LibLine)
diff --git a/Userland/Utilities/wasm.cpp b/Userland/Utilities/wasm.cpp
index c9d7609103..ed814b5548 100644
--- a/Userland/Utilities/wasm.cpp
+++ b/Userland/Utilities/wasm.cpp
@@ -7,9 +7,232 @@
#include <LibCore/ArgsParser.h>
#include <LibCore/File.h>
#include <LibCore/FileStream.h>
+#include <LibLine/Editor.h>
#include <LibWasm/AbstractMachine/AbstractMachine.h>
+#include <LibWasm/AbstractMachine/Interpreter.h>
#include <LibWasm/Printer/Printer.h>
#include <LibWasm/Types.h>
+#include <signal.h>
+#include <unistd.h>
+
+RefPtr<Line::Editor> g_line_editor;
+static auto g_stdout = Core::OutputFileStream::standard_error();
+static Wasm::Printer g_printer { g_stdout };
+static bool g_continue { false };
+static void (*old_signal)(int);
+
+static void print_buffer(ReadonlyBytes buffer, int split)
+{
+ for (size_t i = 0; i < buffer.size(); ++i) {
+ if (split > 0) {
+ if (i % split == 0 && i) {
+ printf(" ");
+ for (size_t j = i - split; j < i; ++j) {
+ auto ch = buffer[j];
+ printf("%c", ch >= 32 && ch <= 127 ? ch : '.'); // silly hack
+ }
+ puts("");
+ }
+ }
+ printf("%02x ", buffer[i]);
+ }
+ puts("");
+}
+
+static void sigint_handler(int)
+{
+ if (!g_continue) {
+ signal(SIGINT, old_signal);
+ kill(getpid(), SIGINT);
+ }
+ g_continue = false;
+}
+
+static bool post_interpret_hook(Wasm::Configuration&, Wasm::InstructionPointer&, const Wasm::Instruction&, const Wasm::Interpreter& interpreter)
+{
+ if (interpreter.did_trap()) {
+ g_continue = false;
+ const_cast<Wasm::Interpreter&>(interpreter).clear_trap();
+ }
+ return true;
+}
+
+static bool pre_interpret_hook(Wasm::Configuration& config, Wasm::InstructionPointer& ip, const Wasm::Instruction& instr)
+{
+ static bool always_print_stack = false;
+ static bool always_print_instruction = false;
+ if (always_print_stack)
+ config.dump_stack();
+ if (always_print_instruction) {
+ g_stdout.write(String::formatted("{:0>4} ", ip.value()).bytes());
+ g_printer.print(instr);
+ }
+ if (g_continue)
+ return true;
+ g_stdout.write(String::formatted("{:0>4} ", ip.value()).bytes());
+ g_printer.print(instr);
+ String last_command = "";
+ for (;;) {
+ auto result = g_line_editor->get_line("> ");
+ if (result.is_error()) {
+ return false;
+ }
+ auto str = result.release_value();
+ g_line_editor->add_to_history(str);
+ if (str.is_empty())
+ str = last_command;
+ else
+ last_command = str;
+ auto args = str.split_view(' ');
+ if (args.is_empty())
+ continue;
+ auto& cmd = args[0];
+ if (cmd.is_one_of("s", "step", "next")) {
+ return true;
+ }
+ if (cmd.is_one_of("p", "print")) {
+ if (args.size() < 2) {
+ warnln("Print what?");
+ continue;
+ }
+ auto& what = args[1];
+ if (what.is_one_of("s", "stack")) {
+ config.dump_stack();
+ continue;
+ }
+ if (what.is_one_of("m", "mem", "memory")) {
+ if (args.size() < 3) {
+ warnln("print what memory?");
+ continue;
+ }
+ auto value = args[2].to_uint<u64>();
+ if (!value.has_value()) {
+ warnln("invalid memory index {}", args[2]);
+ continue;
+ }
+ auto mem = config.store().get(Wasm::MemoryAddress(value.value()));
+ if (!mem) {
+ warnln("invalid memory index {} (not found)", args[2]);
+ continue;
+ }
+ print_buffer(mem->data(), 32);
+ continue;
+ }
+ if (what.is_one_of("i", "instr", "instruction")) {
+ g_printer.print(instr);
+ continue;
+ }
+ if (what.is_one_of("f", "func", "function")) {
+ if (args.size() < 3) {
+ warnln("print what function?");
+ continue;
+ }
+ auto value = args[2].to_uint<u64>();
+ if (!value.has_value()) {
+ warnln("invalid function index {}", args[2]);
+ continue;
+ }
+ auto fn = config.store().get(Wasm::FunctionAddress(value.value()));
+ if (!fn) {
+ warnln("invalid function index {} (not found)", args[2]);
+ continue;
+ }
+ if (auto* fn_value = fn->get_pointer<Wasm::HostFunction>()) {
+ warnln("Host function at {:p}", &fn_value->function());
+ continue;
+ }
+ if (auto* fn_value = fn->get_pointer<Wasm::WasmFunction>()) {
+ g_printer.print(fn_value->code());
+ continue;
+ }
+ }
+ }
+ if (cmd == "call"sv) {
+ if (args.size() < 2) {
+ warnln("call what?");
+ continue;
+ }
+ Optional<Wasm::FunctionAddress> address;
+ auto index = args[1].to_uint<u64>();
+ if (index.has_value()) {
+ address = config.frame()->module().functions()[index.value()];
+ } else {
+ auto& name = args[1];
+ for (auto& export_ : config.frame()->module().exports()) {
+ if (export_.name() == name) {
+ if (auto addr = export_.value().get_pointer<Wasm::FunctionAddress>()) {
+ address = *addr;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!address.has_value()) {
+ failed_to_find:;
+ warnln("Could not find a function {}", args[1]);
+ continue;
+ }
+
+ auto fn = config.store().get(*address);
+ if (!fn)
+ goto failed_to_find;
+
+ auto type = fn->visit([&](auto& value) { return value.type(); });
+ if (type.parameters().size() + 2 != args.size()) {
+ warnln("Expected {} arguments for call, but found only {}", type.parameters().size(), args.size() - 2);
+ continue;
+ }
+ Vector<u64> values_to_push;
+ Vector<Wasm::Value> values;
+ for (size_t index = 2; index < args.size(); ++index)
+ values_to_push.append(args[index].to_uint().value_or(0));
+ for (auto& param : type.parameters())
+ values.append(Wasm::Value { param, values_to_push.take_last() });
+
+ auto result = config.call(*address, move(values));
+ if (result.is_trap())
+ warnln("Execution trapped!");
+ if (!result.values().is_empty())
+ warnln("Returned:");
+ for (auto& value : result.values()) {
+ auto str = value.value().visit(
+ [&](const auto& value) {
+ if constexpr (requires { value.value(); })
+ return String::formatted(" -> addr{} ", value.value());
+ else
+ return String::formatted(" -> {} ", value);
+ });
+ g_stdout.write(str.bytes());
+ g_printer.print(value.type());
+ }
+ continue;
+ }
+ if (cmd.is_one_of("set", "unset")) {
+ auto value = !cmd.starts_with('u');
+ if (args.size() < 3) {
+ warnln("(un)set what (to what)?");
+ continue;
+ }
+ if (args[1] == "print"sv) {
+ if (args[2] == "stack"sv)
+ always_print_stack = value;
+ else if (args[2].is_one_of("instr", "instruction"))
+ always_print_instruction = value;
+ else
+ warnln("Unknown print category '{}'", args[2]);
+ continue;
+ }
+ warnln("Unknown set category '{}'", args[1]);
+ continue;
+ }
+ if (cmd.is_one_of("c", "continue")) {
+ g_continue = true;
+ return true;
+ }
+ warnln("Command not understood: {}", cmd);
+ }
+}
static Optional<Wasm::Module> parse(const StringView& filename)
{
@@ -40,12 +263,14 @@ int main(int argc, char* argv[])
const char* filename = nullptr;
bool print = false;
bool attempt_instantiate = false;
+ bool debug = false;
String exported_function_to_execute;
Vector<u64> values_to_push;
Vector<String> modules_to_link_in;
Core::ArgsParser parser;
parser.add_positional_argument(filename, "File name to parse", "file");
+ parser.add_option(debug, "Open a debugger", "debug", 'd');
parser.add_option(print, "Print the parsed module", "print", 'p');
parser.add_option(attempt_instantiate, "Attempt to instantiate the module", "instantiate", 'i');
parser.add_option(exported_function_to_execute, "Attempt to execute the named exported function from the module (implies -i)", "execute", 'e', "name");
@@ -79,6 +304,15 @@ int main(int argc, char* argv[])
});
parser.parse(argc, argv);
+ if (debug && exported_function_to_execute.is_empty()) {
+ warnln("Debug what? (pass -e fn)");
+ return 1;
+ }
+
+ if (debug) {
+ old_signal = signal(SIGINT, sigint_handler);
+ }
+
if (!exported_function_to_execute.is_empty())
attempt_instantiate = true;
@@ -91,6 +325,12 @@ int main(int argc, char* argv[])
if (attempt_instantiate) {
Wasm::AbstractMachine machine;
+ Core::EventLoop main_loop;
+ if (debug) {
+ g_line_editor = Line::Editor::construct();
+ machine.pre_interpret_hook = pre_interpret_hook;
+ machine.post_interpret_hook = post_interpret_hook;
+ }
// First, resolve the linked modules
NonnullOwnPtrVector<Wasm::ModuleInstance> linked_instances;
Vector<Wasm::Module> linked_modules;
@@ -194,6 +434,21 @@ int main(int argc, char* argv[])
}
auto result = machine.invoke(run_address.value(), move(values));
+
+ if (debug) {
+ Wasm::Configuration config { machine.store() };
+ auto frame = make<Wasm::Frame>(
+ *module_instance,
+ Vector<Wasm::Value> {},
+ instance->get<Wasm::WasmFunction>().code().body(),
+ 1);
+ config.set_frame(move(frame));
+ const Wasm::Instruction instr { Wasm::Instructions::nop };
+ Wasm::InstructionPointer ip { 0 };
+ g_continue = false;
+ pre_interpret_hook(config, ip, instr);
+ }
+
if (result.is_trap())
warnln("Execution trapped!");
if (!result.values().is_empty())