Serenity Operating System
at master 537 lines 22 kB view raw
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}