diff --git a/Userland/DevTools/UserspaceEmulator/Emulator.cpp b/Userland/DevTools/UserspaceEmulator/Emulator.cpp index 9dcfee8091..358a0605df 100644 --- a/Userland/DevTools/UserspaceEmulator/Emulator.cpp +++ b/Userland/DevTools/UserspaceEmulator/Emulator.cpp @@ -10,6 +10,7 @@ #include "SimpleRegion.h" #include "SoftCPU.h" #include +#include #include #include #include @@ -26,6 +27,10 @@ # pragma GCC optimize("O3") #endif +extern bool g_dump_profile; +extern unsigned g_profile_instruction_interval; +extern Optional g_profile_stream; + namespace UserspaceEmulator { static constexpr u32 stack_location = 0x10000000; @@ -218,6 +223,10 @@ int Emulator::exec() constexpr bool trace = false; + size_t instructions_until_next_profile_dump = g_profile_instruction_interval; + if (g_dump_profile && m_loader_text_size.has_value()) + emit_profile_event(*g_profile_stream, "mmap", String::formatted(R"("ptr": {}, "size": {}, "name": "/usr/lib/Loader.so")", *m_loader_text_base, *m_loader_text_size)); + while (!m_shutdown) { if (m_steps_til_pause) [[likely]] { m_cpu.save_base_eip(); @@ -229,6 +238,15 @@ int Emulator::exec() (m_cpu.*insn.handler())(insn); + if (g_dump_profile) { + if (instructions_until_next_profile_dump == 0) { + instructions_until_next_profile_dump = g_profile_instruction_interval; + emit_profile_sample(*g_profile_stream); + } else { + --instructions_until_next_profile_dump; + } + } + if constexpr (trace) { m_cpu.dump(); } @@ -445,6 +463,26 @@ void Emulator::dump_backtrace() dump_backtrace(raw_backtrace()); } +void Emulator::emit_profile_sample(AK::OutputStream& output) +{ + StringBuilder builder; + timeval tv {}; + gettimeofday(&tv, nullptr); + builder.appendff(R"~(, {{"type": "sample", "pid": {}, "tid": {}, "timestamp": {}, "lost_samples": 0, "stack": [)~", getpid(), gettid(), tv.tv_sec * 1000 + tv.tv_usec / 1000); + builder.join(',', raw_backtrace()); + builder.append("]}"); + output.write_or_error(builder.string_view().bytes()); +} + +void Emulator::emit_profile_event(AK::OutputStream& output, StringView event_name, String contents) +{ + StringBuilder builder; + timeval tv {}; + gettimeofday(&tv, nullptr); + builder.appendff(R"~(, {{"type": "{}", "pid": {}, "tid": {}, "timestamp": {}, "lost_samples": 0, "stack": [], {}}})~", event_name, getpid(), gettid(), tv.tv_sec * 1000 + tv.tv_usec / 1000, contents); + output.write_or_error(builder.string_view().bytes()); +} + String Emulator::create_instruction_line(FlatPtr address, X86::Instruction insn) { auto minimal = String::formatted("{:p}: {}", (void*)address, insn.to_string(address)); diff --git a/Userland/DevTools/UserspaceEmulator/Emulator.h b/Userland/DevTools/UserspaceEmulator/Emulator.h index bc3081353f..8b4e7da099 100644 --- a/Userland/DevTools/UserspaceEmulator/Emulator.h +++ b/Userland/DevTools/UserspaceEmulator/Emulator.h @@ -96,6 +96,9 @@ private: void register_signal_handlers(); void setup_signal_trampoline(); + void emit_profile_sample(AK::OutputStream&); + void emit_profile_event(AK::OutputStream&, StringView event_name, String contents); + int virt$emuctl(FlatPtr, FlatPtr, FlatPtr); int virt$fork(); int virt$execve(FlatPtr); diff --git a/Userland/DevTools/UserspaceEmulator/Emulator_syscalls.cpp b/Userland/DevTools/UserspaceEmulator/Emulator_syscalls.cpp index dea03f176b..6c8f2b6064 100644 --- a/Userland/DevTools/UserspaceEmulator/Emulator_syscalls.cpp +++ b/Userland/DevTools/UserspaceEmulator/Emulator_syscalls.cpp @@ -8,6 +8,7 @@ #include "MmapRegion.h" #include "SimpleRegion.h" #include +#include #include #include #include @@ -27,6 +28,9 @@ # pragma GCC optimize("O3") #endif +extern bool g_dump_profile; +extern Optional g_profile_stream; + namespace UserspaceEmulator { u32 Emulator::virt_syscall(u32 function, u32 arg1, u32 arg2, u32 arg3) @@ -812,6 +816,8 @@ static void round_to_page_size(FlatPtr& address, size_t& size) u32 Emulator::virt$munmap(FlatPtr address, size_t size) { + if (g_dump_profile) + emit_profile_event(*g_profile_stream, "munmap", String::formatted("\"ptr\": {}, \"size\": {}", address, size)); round_to_page_size(address, size); Vector marked_for_deletion; bool has_non_mmap_region = false; @@ -870,6 +876,9 @@ u32 Emulator::virt$mmap(u32 params_addr) name_str = { name.data(), name.size() }; } + if (g_dump_profile) + emit_profile_event(*g_profile_stream, "mmap", String::formatted(R"("ptr": {}, "size": {}, "name": "{}")", final_address, final_size, name_str)); + if (params.flags & MAP_ANONYMOUS) { mmu().add_region(MmapRegion::create_anonymous(final_address, final_size, params.prot, move(name_str))); } else { diff --git a/Userland/DevTools/UserspaceEmulator/main.cpp b/Userland/DevTools/UserspaceEmulator/main.cpp index fc7f6fefa3..07d067c7d8 100644 --- a/Userland/DevTools/UserspaceEmulator/main.cpp +++ b/Userland/DevTools/UserspaceEmulator/main.cpp @@ -5,32 +5,45 @@ */ #include "Emulator.h" +#include #include #include #include #include #include #include +#include #include #include #include bool g_report_to_debug = false; +bool g_dump_profile = false; +unsigned g_profile_instruction_interval = 0; +Optional g_profile_stream; int main(int argc, char** argv, char** env) { Vector arguments; bool pause_on_startup { false }; + String profile_dump_path; + FILE* profile_output_file { nullptr }; Core::ArgsParser parser; parser.set_stop_on_first_non_option(true); parser.add_option(g_report_to_debug, "Write reports to the debug log", "report-to-debug", 0); parser.add_option(pause_on_startup, "Pause on startup", "pause", 'p'); + parser.add_option(g_dump_profile, "Generate a ProfileViewer-compatible profile", "profile", 0); + parser.add_option(g_profile_instruction_interval, "Set the profile instruction capture interval, 128 by default", "profile-interval", 'i', "#instructions"); + parser.add_option(profile_dump_path, "File path for profile dump", "profile-file", 0, "path"); parser.add_positional_argument(arguments, "Command to emulate", "command"); parser.parse(argc, argv); + if (g_dump_profile && g_profile_instruction_interval == 0) + g_profile_instruction_interval = 128; + String executable_path; if (arguments[0].contains("/"sv)) executable_path = Core::File::real_path_for(arguments[0]); @@ -41,6 +54,28 @@ int main(int argc, char** argv, char** env) return 1; } + if (g_dump_profile && profile_dump_path.is_empty()) + profile_dump_path = String::formatted("{}.{}.profile", executable_path, getpid()); + + if (g_dump_profile) { + profile_output_file = fopen(profile_dump_path.characters(), "w+"); + if (profile_output_file == nullptr) { + auto error_string = strerror(errno); + warnln("Failed to open '{}' for writing: {}", profile_dump_path, error_string); + return 1; + } + g_profile_stream = OutputFileStream { profile_output_file }; + + g_profile_stream->write_or_error(R"({"events":[)"sv.bytes()); + timeval tv {}; + gettimeofday(&tv, nullptr); + g_profile_stream->write_or_error( + String::formatted( + R"~({{"type": "process_create", "parent_pid": 1, "executable": "{}", "pid": {}, "tid": {}, "timestamp": {}, "lost_samples": 0, "stack": []}})~", + executable_path, getpid(), gettid(), tv.tv_sec * 1000 + tv.tv_usec / 1000) + .bytes()); + } + Vector environment; for (int i = 0; env[i]; ++i) { environment.append(env[i]); @@ -67,5 +102,10 @@ int main(int argc, char** argv, char** env) if (pause_on_startup) emulator.pause(); - return emulator.exec(); + rc = emulator.exec(); + + if (g_dump_profile) { + g_profile_stream->write_or_error(R"(]})"sv.bytes()); + } + return rc; }