Serenity Operating System
1/*
2 * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
3 * Copyright (c) 2022, the SerenityOS developers.
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <AK/MemoryStream.h>
9#include <LibCore/ArgsParser.h>
10#include <LibCore/File.h>
11#include <LibCore/MappedFile.h>
12#include <LibLine/Editor.h>
13#include <LibMain/Main.h>
14#include <LibWasm/AbstractMachine/AbstractMachine.h>
15#include <LibWasm/AbstractMachine/BytecodeInterpreter.h>
16#include <LibWasm/Printer/Printer.h>
17#include <LibWasm/Types.h>
18#include <signal.h>
19#include <unistd.h>
20
21RefPtr<Line::Editor> g_line_editor;
22static OwnPtr<Stream> g_stdout {};
23static OwnPtr<Wasm::Printer> g_printer {};
24static bool g_continue { false };
25static void (*old_signal)(int);
26static Wasm::DebuggerBytecodeInterpreter g_interpreter;
27
28static void sigint_handler(int)
29{
30 if (!g_continue) {
31 signal(SIGINT, old_signal);
32 kill(getpid(), SIGINT);
33 }
34 g_continue = false;
35}
36
37static bool post_interpret_hook(Wasm::Configuration&, Wasm::InstructionPointer& ip, Wasm::Instruction const& instr, Wasm::Interpreter const& interpreter)
38{
39 if (interpreter.did_trap()) {
40 g_continue = false;
41 warnln("Trapped when executing ip={}", ip);
42 g_printer->print(instr);
43 warnln("Trap reason: {}", interpreter.trap_reason());
44 const_cast<Wasm::Interpreter&>(interpreter).clear_trap();
45 }
46 return true;
47}
48
49static bool pre_interpret_hook(Wasm::Configuration& config, Wasm::InstructionPointer& ip, Wasm::Instruction const& instr)
50{
51 static bool always_print_stack = false;
52 static bool always_print_instruction = false;
53 if (always_print_stack)
54 config.dump_stack();
55 if (always_print_instruction) {
56 g_stdout->write_until_depleted(DeprecatedString::formatted("{:0>4} ", ip.value()).bytes()).release_value_but_fixme_should_propagate_errors();
57 g_printer->print(instr);
58 }
59 if (g_continue)
60 return true;
61 g_stdout->write_until_depleted(DeprecatedString::formatted("{:0>4} ", ip.value()).bytes()).release_value_but_fixme_should_propagate_errors();
62 g_printer->print(instr);
63 DeprecatedString last_command = "";
64 for (;;) {
65 auto result = g_line_editor->get_line("> ");
66 if (result.is_error()) {
67 return false;
68 }
69 auto str = result.release_value();
70 g_line_editor->add_to_history(str);
71 if (str.is_empty())
72 str = last_command;
73 else
74 last_command = str;
75 auto args = str.split_view(' ');
76 if (args.is_empty())
77 continue;
78 auto& cmd = args[0];
79 if (cmd.is_one_of("h", "help")) {
80 warnln("Wasm shell commands");
81 warnln("Toplevel:");
82 warnln("- [s]tep Run one instruction");
83 warnln("- next Alias for step");
84 warnln("- [c]ontinue Execute until a trap or the program exit point");
85 warnln("- [p]rint <args...> Print various things (see section on print)");
86 warnln("- call <fn> <args...> Call the function <fn> with the given arguments");
87 warnln("- set <args...> Set shell option (see section on settings)");
88 warnln("- unset <args...> Unset shell option (see section on settings)");
89 warnln("- [h]elp Print this help");
90 warnln();
91 warnln("Print:");
92 warnln("- print [s]tack Print the contents of the stack, including frames and labels");
93 warnln("- print [[m]em]ory <index> Print the contents of the memory identified by <index>");
94 warnln("- print [[i]nstr]uction Print the current instruction");
95 warnln("- print [[f]unc]tion <index> Print the function identified by <index>");
96 warnln();
97 warnln("Settings:");
98 warnln("- set print stack Make the shell print the stack on every instruction executed");
99 warnln("- set print [instr]uction Make the shell print the instruction that will be executed next");
100 warnln();
101 continue;
102 }
103 if (cmd.is_one_of("s", "step", "next")) {
104 return true;
105 }
106 if (cmd.is_one_of("p", "print")) {
107 if (args.size() < 2) {
108 warnln("Print what?");
109 continue;
110 }
111 auto& what = args[1];
112 if (what.is_one_of("s", "stack")) {
113 config.dump_stack();
114 continue;
115 }
116 if (what.is_one_of("m", "mem", "memory")) {
117 if (args.size() < 3) {
118 warnln("print what memory?");
119 continue;
120 }
121 auto value = args[2].to_uint<u64>();
122 if (!value.has_value()) {
123 warnln("invalid memory index {}", args[2]);
124 continue;
125 }
126 auto mem = config.store().get(Wasm::MemoryAddress(value.value()));
127 if (!mem) {
128 warnln("invalid memory index {} (not found)", args[2]);
129 continue;
130 }
131 warnln("{:>32hex-dump}", mem->data().bytes());
132 continue;
133 }
134 if (what.is_one_of("i", "instr", "instruction")) {
135 g_printer->print(instr);
136 continue;
137 }
138 if (what.is_one_of("f", "func", "function")) {
139 if (args.size() < 3) {
140 warnln("print what function?");
141 continue;
142 }
143 auto value = args[2].to_uint<u64>();
144 if (!value.has_value()) {
145 warnln("invalid function index {}", args[2]);
146 continue;
147 }
148 auto fn = config.store().get(Wasm::FunctionAddress(value.value()));
149 if (!fn) {
150 warnln("invalid function index {} (not found)", args[2]);
151 continue;
152 }
153 if (auto* fn_value = fn->get_pointer<Wasm::HostFunction>()) {
154 warnln("Host function at {:p}", &fn_value->function());
155 continue;
156 }
157 if (auto* fn_value = fn->get_pointer<Wasm::WasmFunction>()) {
158 g_printer->print(fn_value->code());
159 continue;
160 }
161 }
162 }
163 if (cmd == "call"sv) {
164 if (args.size() < 2) {
165 warnln("call what?");
166 continue;
167 }
168 Optional<Wasm::FunctionAddress> address;
169 auto index = args[1].to_uint<u64>();
170 if (index.has_value()) {
171 address = config.frame().module().functions()[index.value()];
172 } else {
173 auto& name = args[1];
174 for (auto& export_ : config.frame().module().exports()) {
175 if (export_.name() == name) {
176 if (auto addr = export_.value().get_pointer<Wasm::FunctionAddress>()) {
177 address = *addr;
178 break;
179 }
180 }
181 }
182 }
183
184 if (!address.has_value()) {
185 failed_to_find:;
186 warnln("Could not find a function {}", args[1]);
187 continue;
188 }
189
190 auto fn = config.store().get(*address);
191 if (!fn)
192 goto failed_to_find;
193
194 auto type = fn->visit([&](auto& value) { return value.type(); });
195 if (type.parameters().size() + 2 != args.size()) {
196 warnln("Expected {} arguments for call, but found only {}", type.parameters().size(), args.size() - 2);
197 continue;
198 }
199 Vector<u64> values_to_push;
200 Vector<Wasm::Value> values;
201 for (size_t index = 2; index < args.size(); ++index)
202 values_to_push.append(args[index].to_uint().value_or(0));
203 for (auto& param : type.parameters())
204 values.append(Wasm::Value { param, values_to_push.take_last() });
205
206 Wasm::Result result { Wasm::Trap {} };
207 {
208 Wasm::BytecodeInterpreter::CallFrameHandle handle { g_interpreter, config };
209 result = config.call(g_interpreter, *address, move(values)).assert_wasm_result();
210 }
211 if (result.is_trap()) {
212 warnln("Execution trapped: {}", result.trap().reason);
213 } else {
214 if (!result.values().is_empty())
215 warnln("Returned:");
216 for (auto& value : result.values()) {
217 g_stdout->write_until_depleted(" -> "sv.bytes()).release_value_but_fixme_should_propagate_errors();
218 g_printer->print(value);
219 }
220 }
221 continue;
222 }
223 if (cmd.is_one_of("set", "unset")) {
224 auto value = !cmd.starts_with('u');
225 if (args.size() < 3) {
226 warnln("(un)set what (to what)?");
227 continue;
228 }
229 if (args[1] == "print"sv) {
230 if (args[2] == "stack"sv)
231 always_print_stack = value;
232 else if (args[2].is_one_of("instr", "instruction"))
233 always_print_instruction = value;
234 else
235 warnln("Unknown print category '{}'", args[2]);
236 continue;
237 }
238 warnln("Unknown set category '{}'", args[1]);
239 continue;
240 }
241 if (cmd.is_one_of("c", "continue")) {
242 g_continue = true;
243 return true;
244 }
245 warnln("Command not understood: {}", cmd);
246 }
247}
248
249static Optional<Wasm::Module> parse(StringView filename)
250{
251 auto result = Core::MappedFile::map(filename);
252 if (result.is_error()) {
253 warnln("Failed to open {}: {}", filename, result.error());
254 return {};
255 }
256
257 FixedMemoryStream stream { ReadonlyBytes { result.value()->data(), result.value()->size() } };
258 auto parse_result = Wasm::Module::parse(stream);
259 if (parse_result.is_error()) {
260 warnln("Something went wrong, either the file is invalid, or there's a bug with LibWasm!");
261 warnln("The parse error was {}", Wasm::parse_error_to_deprecated_string(parse_result.error()));
262 return {};
263 }
264 return parse_result.release_value();
265}
266
267static void print_link_error(Wasm::LinkError const& error)
268{
269 for (auto const& missing : error.missing_imports)
270 warnln("Missing import '{}'", missing);
271}
272
273ErrorOr<int> serenity_main(Main::Arguments arguments)
274{
275 StringView filename;
276 bool print = false;
277 bool attempt_instantiate = false;
278 bool debug = false;
279 bool export_all_imports = false;
280 bool shell_mode = false;
281 DeprecatedString exported_function_to_execute;
282 Vector<u64> values_to_push;
283 Vector<DeprecatedString> modules_to_link_in;
284
285 Core::ArgsParser parser;
286 parser.add_positional_argument(filename, "File name to parse", "file");
287 parser.add_option(debug, "Open a debugger", "debug", 'd');
288 parser.add_option(print, "Print the parsed module", "print", 'p');
289 parser.add_option(attempt_instantiate, "Attempt to instantiate the module", "instantiate", 'i');
290 parser.add_option(exported_function_to_execute, "Attempt to execute the named exported function from the module (implies -i)", "execute", 'e', "name");
291 parser.add_option(export_all_imports, "Export noop functions corresponding to imports", "export-noop", 0);
292 parser.add_option(shell_mode, "Launch a REPL in the module's context (implies -i)", "shell", 's');
293 parser.add_option(Core::ArgsParser::Option {
294 .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
295 .help_string = "Extra modules to link with, use to resolve imports",
296 .long_name = "link",
297 .short_name = 'l',
298 .value_name = "file",
299 .accept_value = [&](StringView str) {
300 if (!str.is_empty()) {
301 modules_to_link_in.append(str);
302 return true;
303 }
304 return false;
305 },
306 });
307 parser.add_option(Core::ArgsParser::Option {
308 .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
309 .help_string = "Supply arguments to the function (default=0) (expects u64, casts to required type)",
310 .long_name = "arg",
311 .short_name = 0,
312 .value_name = "u64",
313 .accept_value = [&](StringView str) -> bool {
314 if (auto v = str.to_uint<u64>(); v.has_value()) {
315 values_to_push.append(v.value());
316 return true;
317 }
318 return false;
319 },
320 });
321 parser.parse(arguments);
322
323 if (shell_mode) {
324 debug = true;
325 attempt_instantiate = true;
326 }
327
328 if (!shell_mode && debug && exported_function_to_execute.is_empty()) {
329 warnln("Debug what? (pass -e fn)");
330 return 1;
331 }
332
333 if (debug || shell_mode) {
334 old_signal = signal(SIGINT, sigint_handler);
335 }
336
337 if (!exported_function_to_execute.is_empty())
338 attempt_instantiate = true;
339
340 auto parse_result = parse(filename);
341 if (!parse_result.has_value())
342 return 1;
343
344 g_stdout = TRY(Core::File::standard_output());
345 g_printer = TRY(try_make<Wasm::Printer>(*g_stdout));
346
347 if (print && !attempt_instantiate) {
348 Wasm::Printer printer(*g_stdout);
349 printer.print(parse_result.value());
350 }
351
352 if (attempt_instantiate) {
353 Wasm::AbstractMachine machine;
354 Core::EventLoop main_loop;
355 if (debug) {
356 g_line_editor = Line::Editor::construct();
357 g_interpreter.pre_interpret_hook = pre_interpret_hook;
358 g_interpreter.post_interpret_hook = post_interpret_hook;
359 }
360
361 // First, resolve the linked modules
362 Vector<NonnullOwnPtr<Wasm::ModuleInstance>> linked_instances;
363 Vector<Wasm::Module> linked_modules;
364 for (auto& name : modules_to_link_in) {
365 auto parse_result = parse(name);
366 if (!parse_result.has_value()) {
367 warnln("Failed to parse linked module '{}'", name);
368 return 1;
369 }
370 linked_modules.append(parse_result.release_value());
371 Wasm::Linker linker { linked_modules.last() };
372 for (auto& instance : linked_instances)
373 linker.link(*instance);
374 auto link_result = linker.finish();
375 if (link_result.is_error()) {
376 warnln("Linking imported module '{}' failed", name);
377 print_link_error(link_result.error());
378 return 1;
379 }
380 auto instantiation_result = machine.instantiate(linked_modules.last(), link_result.release_value());
381 if (instantiation_result.is_error()) {
382 warnln("Instantiation of imported module '{}' failed: {}", name, instantiation_result.error().error);
383 return 1;
384 }
385 linked_instances.append(instantiation_result.release_value());
386 }
387
388 Wasm::Linker linker { parse_result.value() };
389 for (auto& instance : linked_instances)
390 linker.link(*instance);
391
392 if (export_all_imports) {
393 HashMap<Wasm::Linker::Name, Wasm::ExternValue> exports;
394 for (auto& entry : linker.unresolved_imports()) {
395 if (!entry.type.has<Wasm::TypeIndex>())
396 continue;
397 auto type = parse_result.value().type(entry.type.get<Wasm::TypeIndex>());
398 auto address = machine.store().allocate(Wasm::HostFunction(
399 [name = entry.name, type = type](auto&, auto& arguments) -> Wasm::Result {
400 StringBuilder argument_builder;
401 bool first = true;
402 for (auto& argument : arguments) {
403 AllocatingMemoryStream stream;
404 Wasm::Printer { stream }.print(argument);
405 if (first)
406 first = false;
407 else
408 argument_builder.append(", "sv);
409 auto buffer = ByteBuffer::create_uninitialized(stream.used_buffer_size()).release_value_but_fixme_should_propagate_errors();
410 stream.read_until_filled(buffer).release_value_but_fixme_should_propagate_errors();
411 argument_builder.append(StringView(buffer).trim_whitespace());
412 }
413 dbgln("[wasm runtime] Stub function {} was called with the following arguments: {}", name, argument_builder.to_deprecated_string());
414 Vector<Wasm::Value> result;
415 result.ensure_capacity(type.results().size());
416 for (auto& result_type : type.results())
417 result.append(Wasm::Value { result_type, 0ull });
418 return Wasm::Result { move(result) };
419 },
420 type));
421 exports.set(entry, *address);
422 }
423
424 linker.link(exports);
425 }
426
427 auto link_result = linker.finish();
428 if (link_result.is_error()) {
429 warnln("Linking main module failed");
430 print_link_error(link_result.error());
431 return 1;
432 }
433 auto result = machine.instantiate(parse_result.value(), link_result.release_value());
434 if (result.is_error()) {
435 warnln("Module instantiation failed: {}", result.error().error);
436 return 1;
437 }
438 auto module_instance = result.release_value();
439
440 auto launch_repl = [&] {
441 Wasm::Configuration config { machine.store() };
442 Wasm::Expression expression { {} };
443 config.set_frame(Wasm::Frame {
444 *module_instance,
445 Vector<Wasm::Value> {},
446 expression,
447 0,
448 });
449 Wasm::Instruction instr { Wasm::Instructions::nop };
450 Wasm::InstructionPointer ip { 0 };
451 g_continue = false;
452 pre_interpret_hook(config, ip, instr);
453 };
454
455 auto print_func = [&](auto const& address) {
456 Wasm::FunctionInstance* fn = machine.store().get(address);
457 g_stdout->write_until_depleted(DeprecatedString::formatted("- Function with address {}, ptr = {}\n", address.value(), fn).bytes()).release_value_but_fixme_should_propagate_errors();
458 if (fn) {
459 g_stdout->write_until_depleted(DeprecatedString::formatted(" wasm function? {}\n", fn->has<Wasm::WasmFunction>()).bytes()).release_value_but_fixme_should_propagate_errors();
460 fn->visit(
461 [&](Wasm::WasmFunction const& func) {
462 Wasm::Printer printer { *g_stdout, 3 };
463 g_stdout->write_until_depleted(" type:\n"sv.bytes()).release_value_but_fixme_should_propagate_errors();
464 printer.print(func.type());
465 g_stdout->write_until_depleted(" code:\n"sv.bytes()).release_value_but_fixme_should_propagate_errors();
466 printer.print(func.code());
467 },
468 [](Wasm::HostFunction const&) {});
469 }
470 };
471 if (print) {
472 // Now, let's dump the functions!
473 for (auto& address : module_instance->functions()) {
474 print_func(address);
475 }
476 }
477
478 if (shell_mode) {
479 launch_repl();
480 return 0;
481 }
482
483 if (!exported_function_to_execute.is_empty()) {
484 Optional<Wasm::FunctionAddress> run_address;
485 Vector<Wasm::Value> values;
486 for (auto& entry : module_instance->exports()) {
487 if (entry.name() == exported_function_to_execute) {
488 if (auto addr = entry.value().get_pointer<Wasm::FunctionAddress>())
489 run_address = *addr;
490 }
491 }
492 if (!run_address.has_value()) {
493 warnln("No such exported function, sorry :(");
494 return 1;
495 }
496
497 auto instance = machine.store().get(*run_address);
498 VERIFY(instance);
499
500 if (instance->has<Wasm::HostFunction>()) {
501 warnln("Exported function is a host function, cannot run that yet");
502 return 1;
503 }
504
505 for (auto& param : instance->get<Wasm::WasmFunction>().type().parameters()) {
506 if (values_to_push.is_empty())
507 values.append(Wasm::Value { param, 0ull });
508 else
509 values.append(Wasm::Value { param, values_to_push.take_last() });
510 }
511
512 if (print) {
513 outln("Executing ");
514 print_func(*run_address);
515 outln();
516 }
517
518 auto result = machine.invoke(g_interpreter, run_address.value(), move(values)).assert_wasm_result();
519
520 if (debug)
521 launch_repl();
522
523 if (result.is_trap()) {
524 warnln("Execution trapped: {}", result.trap().reason);
525 } else {
526 if (!result.values().is_empty())
527 warnln("Returned:");
528 for (auto& value : result.values()) {
529 g_stdout->write_until_depleted(" -> "sv.bytes()).release_value_but_fixme_should_propagate_errors();
530 g_printer->print(value);
531 }
532 }
533 }
534 }
535
536 return 0;
537}