mirror of
https://github.com/RGBCube/serenity
synced 2025-05-14 07:34:57 +00:00

Similar to POSIX read, the basic read and write functions of AK::Stream do not have a lower limit of how much data they read or write (apart from "none at all"). Rename the functions to "read some [data]" and "write some [data]" (with "data" being omitted, since everything here is reading and writing data) to make them sufficiently distinct from the functions that ensure to use the entire buffer (which should be the go-to function for most usages). No functional changes, just a lot of new FIXMEs.
544 lines
22 KiB
C++
544 lines
22 KiB
C++
/*
|
|
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
|
* Copyright (c) 2022, the SerenityOS developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/MemoryStream.h>
|
|
#include <LibCore/ArgsParser.h>
|
|
#include <LibCore/File.h>
|
|
#include <LibCore/MappedFile.h>
|
|
#include <LibLine/Editor.h>
|
|
#include <LibMain/Main.h>
|
|
#include <LibWasm/AbstractMachine/AbstractMachine.h>
|
|
#include <LibWasm/AbstractMachine/BytecodeInterpreter.h>
|
|
#include <LibWasm/Printer/Printer.h>
|
|
#include <LibWasm/Types.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
|
|
RefPtr<Line::Editor> g_line_editor;
|
|
static OwnPtr<Stream> g_stdout {};
|
|
static OwnPtr<Wasm::Printer> g_printer {};
|
|
static bool g_continue { false };
|
|
static void (*old_signal)(int);
|
|
static Wasm::DebuggerBytecodeInterpreter g_interpreter;
|
|
|
|
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& ip, Wasm::Instruction const& instr, Wasm::Interpreter const& interpreter)
|
|
{
|
|
if (interpreter.did_trap()) {
|
|
g_continue = false;
|
|
warnln("Trapped when executing ip={}", ip);
|
|
g_printer->print(instr);
|
|
warnln("Trap reason: {}", interpreter.trap_reason());
|
|
const_cast<Wasm::Interpreter&>(interpreter).clear_trap();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool pre_interpret_hook(Wasm::Configuration& config, Wasm::InstructionPointer& ip, Wasm::Instruction const& instr)
|
|
{
|
|
static bool always_print_stack = false;
|
|
static bool always_print_instruction = false;
|
|
if (always_print_stack)
|
|
config.dump_stack();
|
|
if (always_print_instruction) {
|
|
// FIXME: This should write the entire span.
|
|
g_stdout->write_some(DeprecatedString::formatted("{:0>4} ", ip.value()).bytes()).release_value_but_fixme_should_propagate_errors();
|
|
g_printer->print(instr);
|
|
}
|
|
if (g_continue)
|
|
return true;
|
|
// FIXME: This should write the entire span.
|
|
g_stdout->write_some(DeprecatedString::formatted("{:0>4} ", ip.value()).bytes()).release_value_but_fixme_should_propagate_errors();
|
|
g_printer->print(instr);
|
|
DeprecatedString 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("h", "help")) {
|
|
warnln("Wasm shell commands");
|
|
warnln("Toplevel:");
|
|
warnln("- [s]tep Run one instruction");
|
|
warnln("- next Alias for step");
|
|
warnln("- [c]ontinue Execute until a trap or the program exit point");
|
|
warnln("- [p]rint <args...> Print various things (see section on print)");
|
|
warnln("- call <fn> <args...> Call the function <fn> with the given arguments");
|
|
warnln("- set <args...> Set shell option (see section on settings)");
|
|
warnln("- unset <args...> Unset shell option (see section on settings)");
|
|
warnln("- [h]elp Print this help");
|
|
warnln();
|
|
warnln("Print:");
|
|
warnln("- print [s]tack Print the contents of the stack, including frames and labels");
|
|
warnln("- print [[m]em]ory <index> Print the contents of the memory identified by <index>");
|
|
warnln("- print [[i]nstr]uction Print the current instruction");
|
|
warnln("- print [[f]unc]tion <index> Print the function identified by <index>");
|
|
warnln();
|
|
warnln("Settings:");
|
|
warnln("- set print stack Make the shell print the stack on every instruction executed");
|
|
warnln("- set print [instr]uction Make the shell print the instruction that will be executed next");
|
|
warnln();
|
|
continue;
|
|
}
|
|
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;
|
|
}
|
|
warnln("{:>32hex-dump}", mem->data().bytes());
|
|
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() });
|
|
|
|
Wasm::Result result { Wasm::Trap {} };
|
|
{
|
|
Wasm::BytecodeInterpreter::CallFrameHandle handle { g_interpreter, config };
|
|
result = config.call(g_interpreter, *address, move(values)).assert_wasm_result();
|
|
}
|
|
if (result.is_trap()) {
|
|
warnln("Execution trapped: {}", result.trap().reason);
|
|
} else {
|
|
if (!result.values().is_empty())
|
|
warnln("Returned:");
|
|
for (auto& value : result.values()) {
|
|
// FIXME: This should write the entire span.
|
|
g_stdout->write_some(" -> "sv.bytes()).release_value_but_fixme_should_propagate_errors();
|
|
g_printer->print(value);
|
|
}
|
|
}
|
|
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(StringView filename)
|
|
{
|
|
auto result = Core::MappedFile::map(filename);
|
|
if (result.is_error()) {
|
|
warnln("Failed to open {}: {}", filename, result.error());
|
|
return {};
|
|
}
|
|
|
|
FixedMemoryStream stream { ReadonlyBytes { result.value()->data(), result.value()->size() } };
|
|
auto parse_result = Wasm::Module::parse(stream);
|
|
if (parse_result.is_error()) {
|
|
warnln("Something went wrong, either the file is invalid, or there's a bug with LibWasm!");
|
|
warnln("The parse error was {}", Wasm::parse_error_to_deprecated_string(parse_result.error()));
|
|
return {};
|
|
}
|
|
return parse_result.release_value();
|
|
}
|
|
|
|
static void print_link_error(Wasm::LinkError const& error)
|
|
{
|
|
for (auto const& missing : error.missing_imports)
|
|
warnln("Missing import '{}'", missing);
|
|
}
|
|
|
|
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|
{
|
|
StringView filename;
|
|
bool print = false;
|
|
bool attempt_instantiate = false;
|
|
bool debug = false;
|
|
bool export_all_imports = false;
|
|
bool shell_mode = false;
|
|
DeprecatedString exported_function_to_execute;
|
|
Vector<u64> values_to_push;
|
|
Vector<DeprecatedString> 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");
|
|
parser.add_option(export_all_imports, "Export noop functions corresponding to imports", "export-noop", 0);
|
|
parser.add_option(shell_mode, "Launch a REPL in the module's context (implies -i)", "shell", 's');
|
|
parser.add_option(Core::ArgsParser::Option {
|
|
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
|
|
.help_string = "Extra modules to link with, use to resolve imports",
|
|
.long_name = "link",
|
|
.short_name = 'l',
|
|
.value_name = "file",
|
|
.accept_value = [&](StringView str) {
|
|
if (!str.is_empty()) {
|
|
modules_to_link_in.append(str);
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
});
|
|
parser.add_option(Core::ArgsParser::Option {
|
|
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
|
|
.help_string = "Supply arguments to the function (default=0) (expects u64, casts to required type)",
|
|
.long_name = "arg",
|
|
.short_name = 0,
|
|
.value_name = "u64",
|
|
.accept_value = [&](StringView str) -> bool {
|
|
if (auto v = str.to_uint<u64>(); v.has_value()) {
|
|
values_to_push.append(v.value());
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
});
|
|
parser.parse(arguments);
|
|
|
|
if (shell_mode) {
|
|
debug = true;
|
|
attempt_instantiate = true;
|
|
}
|
|
|
|
if (!shell_mode && debug && exported_function_to_execute.is_empty()) {
|
|
warnln("Debug what? (pass -e fn)");
|
|
return 1;
|
|
}
|
|
|
|
if (debug || shell_mode) {
|
|
old_signal = signal(SIGINT, sigint_handler);
|
|
}
|
|
|
|
if (!exported_function_to_execute.is_empty())
|
|
attempt_instantiate = true;
|
|
|
|
auto parse_result = parse(filename);
|
|
if (!parse_result.has_value())
|
|
return 1;
|
|
|
|
g_stdout = TRY(Core::File::standard_output());
|
|
g_printer = TRY(try_make<Wasm::Printer>(*g_stdout));
|
|
|
|
if (print && !attempt_instantiate) {
|
|
Wasm::Printer printer(*g_stdout);
|
|
printer.print(parse_result.value());
|
|
}
|
|
|
|
if (attempt_instantiate) {
|
|
Wasm::AbstractMachine machine;
|
|
Core::EventLoop main_loop;
|
|
if (debug) {
|
|
g_line_editor = Line::Editor::construct();
|
|
g_interpreter.pre_interpret_hook = pre_interpret_hook;
|
|
g_interpreter.post_interpret_hook = post_interpret_hook;
|
|
}
|
|
|
|
// First, resolve the linked modules
|
|
Vector<NonnullOwnPtr<Wasm::ModuleInstance>> linked_instances;
|
|
Vector<Wasm::Module> linked_modules;
|
|
for (auto& name : modules_to_link_in) {
|
|
auto parse_result = parse(name);
|
|
if (!parse_result.has_value()) {
|
|
warnln("Failed to parse linked module '{}'", name);
|
|
return 1;
|
|
}
|
|
linked_modules.append(parse_result.release_value());
|
|
Wasm::Linker linker { linked_modules.last() };
|
|
for (auto& instance : linked_instances)
|
|
linker.link(*instance);
|
|
auto link_result = linker.finish();
|
|
if (link_result.is_error()) {
|
|
warnln("Linking imported module '{}' failed", name);
|
|
print_link_error(link_result.error());
|
|
return 1;
|
|
}
|
|
auto instantiation_result = machine.instantiate(linked_modules.last(), link_result.release_value());
|
|
if (instantiation_result.is_error()) {
|
|
warnln("Instantiation of imported module '{}' failed: {}", name, instantiation_result.error().error);
|
|
return 1;
|
|
}
|
|
linked_instances.append(instantiation_result.release_value());
|
|
}
|
|
|
|
Wasm::Linker linker { parse_result.value() };
|
|
for (auto& instance : linked_instances)
|
|
linker.link(*instance);
|
|
|
|
if (export_all_imports) {
|
|
HashMap<Wasm::Linker::Name, Wasm::ExternValue> exports;
|
|
for (auto& entry : linker.unresolved_imports()) {
|
|
if (!entry.type.has<Wasm::TypeIndex>())
|
|
continue;
|
|
auto type = parse_result.value().type(entry.type.get<Wasm::TypeIndex>());
|
|
auto address = machine.store().allocate(Wasm::HostFunction(
|
|
[name = entry.name, type = type](auto&, auto& arguments) -> Wasm::Result {
|
|
StringBuilder argument_builder;
|
|
bool first = true;
|
|
for (auto& argument : arguments) {
|
|
AllocatingMemoryStream stream;
|
|
Wasm::Printer { stream }.print(argument);
|
|
if (first)
|
|
first = false;
|
|
else
|
|
argument_builder.append(", "sv);
|
|
auto buffer = ByteBuffer::create_uninitialized(stream.used_buffer_size()).release_value_but_fixme_should_propagate_errors();
|
|
stream.read_entire_buffer(buffer).release_value_but_fixme_should_propagate_errors();
|
|
argument_builder.append(StringView(buffer).trim_whitespace());
|
|
}
|
|
dbgln("[wasm runtime] Stub function {} was called with the following arguments: {}", name, argument_builder.to_deprecated_string());
|
|
Vector<Wasm::Value> result;
|
|
result.ensure_capacity(type.results().size());
|
|
for (auto& result_type : type.results())
|
|
result.append(Wasm::Value { result_type, 0ull });
|
|
return Wasm::Result { move(result) };
|
|
},
|
|
type));
|
|
exports.set(entry, *address);
|
|
}
|
|
|
|
linker.link(exports);
|
|
}
|
|
|
|
auto link_result = linker.finish();
|
|
if (link_result.is_error()) {
|
|
warnln("Linking main module failed");
|
|
print_link_error(link_result.error());
|
|
return 1;
|
|
}
|
|
auto result = machine.instantiate(parse_result.value(), link_result.release_value());
|
|
if (result.is_error()) {
|
|
warnln("Module instantiation failed: {}", result.error().error);
|
|
return 1;
|
|
}
|
|
auto module_instance = result.release_value();
|
|
|
|
auto launch_repl = [&] {
|
|
Wasm::Configuration config { machine.store() };
|
|
Wasm::Expression expression { {} };
|
|
config.set_frame(Wasm::Frame {
|
|
*module_instance,
|
|
Vector<Wasm::Value> {},
|
|
expression,
|
|
0,
|
|
});
|
|
Wasm::Instruction instr { Wasm::Instructions::nop };
|
|
Wasm::InstructionPointer ip { 0 };
|
|
g_continue = false;
|
|
pre_interpret_hook(config, ip, instr);
|
|
};
|
|
|
|
auto print_func = [&](auto const& address) {
|
|
Wasm::FunctionInstance* fn = machine.store().get(address);
|
|
// FIXME: This should write the entire span.
|
|
g_stdout->write_some(DeprecatedString::formatted("- Function with address {}, ptr = {}\n", address.value(), fn).bytes()).release_value_but_fixme_should_propagate_errors();
|
|
if (fn) {
|
|
// FIXME: This should write the entire span.
|
|
g_stdout->write_some(DeprecatedString::formatted(" wasm function? {}\n", fn->has<Wasm::WasmFunction>()).bytes()).release_value_but_fixme_should_propagate_errors();
|
|
fn->visit(
|
|
[&](Wasm::WasmFunction const& func) {
|
|
Wasm::Printer printer { *g_stdout, 3 };
|
|
// FIXME: This should write the entire span.
|
|
g_stdout->write_some(" type:\n"sv.bytes()).release_value_but_fixme_should_propagate_errors();
|
|
printer.print(func.type());
|
|
g_stdout->write_some(" code:\n"sv.bytes()).release_value_but_fixme_should_propagate_errors();
|
|
printer.print(func.code());
|
|
},
|
|
[](Wasm::HostFunction const&) {});
|
|
}
|
|
};
|
|
if (print) {
|
|
// Now, let's dump the functions!
|
|
for (auto& address : module_instance->functions()) {
|
|
print_func(address);
|
|
}
|
|
}
|
|
|
|
if (shell_mode) {
|
|
launch_repl();
|
|
return 0;
|
|
}
|
|
|
|
if (!exported_function_to_execute.is_empty()) {
|
|
Optional<Wasm::FunctionAddress> run_address;
|
|
Vector<Wasm::Value> values;
|
|
for (auto& entry : module_instance->exports()) {
|
|
if (entry.name() == exported_function_to_execute) {
|
|
if (auto addr = entry.value().get_pointer<Wasm::FunctionAddress>())
|
|
run_address = *addr;
|
|
}
|
|
}
|
|
if (!run_address.has_value()) {
|
|
warnln("No such exported function, sorry :(");
|
|
return 1;
|
|
}
|
|
|
|
auto instance = machine.store().get(*run_address);
|
|
VERIFY(instance);
|
|
|
|
if (instance->has<Wasm::HostFunction>()) {
|
|
warnln("Exported function is a host function, cannot run that yet");
|
|
return 1;
|
|
}
|
|
|
|
for (auto& param : instance->get<Wasm::WasmFunction>().type().parameters()) {
|
|
if (values_to_push.is_empty())
|
|
values.append(Wasm::Value { param, 0ull });
|
|
else
|
|
values.append(Wasm::Value { param, values_to_push.take_last() });
|
|
}
|
|
|
|
if (print) {
|
|
outln("Executing ");
|
|
print_func(*run_address);
|
|
outln();
|
|
}
|
|
|
|
auto result = machine.invoke(g_interpreter, run_address.value(), move(values)).assert_wasm_result();
|
|
|
|
if (debug)
|
|
launch_repl();
|
|
|
|
if (result.is_trap()) {
|
|
warnln("Execution trapped: {}", result.trap().reason);
|
|
} else {
|
|
if (!result.values().is_empty())
|
|
warnln("Returned:");
|
|
for (auto& value : result.values()) {
|
|
// FIXME: This should write the entire span.
|
|
g_stdout->write_some(" -> "sv.bytes()).release_value_but_fixme_should_propagate_errors();
|
|
g_printer->print(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|