diff options
author | Ali Mohammad Pur <ali.mpfard@gmail.com> | 2021-05-21 21:10:44 +0430 |
---|---|---|
committer | Ali Mohammad Pur <Ali.mpfard@gmail.com> | 2021-05-26 15:34:13 +0430 |
commit | ba5da79617fed8f059bde2b4adb263e87f73c7e6 (patch) | |
tree | be8ad7bf334e029e249a42034c9efde62a1cf77e | |
parent | f740667fa11e5f4979f78635e62e8064861b6832 (diff) | |
download | serenity-ba5da79617fed8f059bde2b4adb263e87f73c7e6.zip |
LibWasm: Add execution hooks and a debugger mode to the wasm tool
This is useful for debugging *our* implementation of wasm :P
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()) |