Просмотр исходного кода

LibWasm: Add execution hooks and a debugger mode to the wasm tool

This is useful for debugging *our* implementation of wasm :P
Ali Mohammad Pur 4 лет назад
Родитель
Сommit
ba5da79617

+ 8 - 1
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)

+ 4 - 0
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;

+ 3 - 0
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 {};

+ 3 - 0
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 };

+ 21 - 2
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;

+ 4 - 0
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&);

+ 1 - 1
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)

+ 255 - 0
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())