|
@@ -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())
|