mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 14:47:44 +00:00
UserspaceEmulator: Optionally generate a Profiler-compatible profile
`ue --profile --profile-file ~/some-file.profile id` can now generate a full profile (instruction-by-instruction, if needed), at the cost of not being able to see past the syscall boundary (a.la. callgrind). This makes it significantly easier to profile seemingly fast userspace things, like Loader.so :^)
This commit is contained in:
parent
3829bf115c
commit
521217735b
4 changed files with 91 additions and 1 deletions
|
@ -10,6 +10,7 @@
|
||||||
#include "SimpleRegion.h"
|
#include "SimpleRegion.h"
|
||||||
#include "SoftCPU.h"
|
#include "SoftCPU.h"
|
||||||
#include <AK/Debug.h>
|
#include <AK/Debug.h>
|
||||||
|
#include <AK/FileStream.h>
|
||||||
#include <AK/Format.h>
|
#include <AK/Format.h>
|
||||||
#include <AK/LexicalPath.h>
|
#include <AK/LexicalPath.h>
|
||||||
#include <AK/MappedFile.h>
|
#include <AK/MappedFile.h>
|
||||||
|
@ -26,6 +27,10 @@
|
||||||
# pragma GCC optimize("O3")
|
# pragma GCC optimize("O3")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
extern bool g_dump_profile;
|
||||||
|
extern unsigned g_profile_instruction_interval;
|
||||||
|
extern Optional<OutputFileStream> g_profile_stream;
|
||||||
|
|
||||||
namespace UserspaceEmulator {
|
namespace UserspaceEmulator {
|
||||||
|
|
||||||
static constexpr u32 stack_location = 0x10000000;
|
static constexpr u32 stack_location = 0x10000000;
|
||||||
|
@ -218,6 +223,10 @@ int Emulator::exec()
|
||||||
|
|
||||||
constexpr bool trace = false;
|
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) {
|
while (!m_shutdown) {
|
||||||
if (m_steps_til_pause) [[likely]] {
|
if (m_steps_til_pause) [[likely]] {
|
||||||
m_cpu.save_base_eip();
|
m_cpu.save_base_eip();
|
||||||
|
@ -229,6 +238,15 @@ int Emulator::exec()
|
||||||
|
|
||||||
(m_cpu.*insn.handler())(insn);
|
(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) {
|
if constexpr (trace) {
|
||||||
m_cpu.dump();
|
m_cpu.dump();
|
||||||
}
|
}
|
||||||
|
@ -445,6 +463,26 @@ void Emulator::dump_backtrace()
|
||||||
dump_backtrace(raw_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)
|
String Emulator::create_instruction_line(FlatPtr address, X86::Instruction insn)
|
||||||
{
|
{
|
||||||
auto minimal = String::formatted("{:p}: {}", (void*)address, insn.to_string(address));
|
auto minimal = String::formatted("{:p}: {}", (void*)address, insn.to_string(address));
|
||||||
|
|
|
@ -96,6 +96,9 @@ private:
|
||||||
void register_signal_handlers();
|
void register_signal_handlers();
|
||||||
void setup_signal_trampoline();
|
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$emuctl(FlatPtr, FlatPtr, FlatPtr);
|
||||||
int virt$fork();
|
int virt$fork();
|
||||||
int virt$execve(FlatPtr);
|
int virt$execve(FlatPtr);
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "MmapRegion.h"
|
#include "MmapRegion.h"
|
||||||
#include "SimpleRegion.h"
|
#include "SimpleRegion.h"
|
||||||
#include <AK/Debug.h>
|
#include <AK/Debug.h>
|
||||||
|
#include <AK/FileStream.h>
|
||||||
#include <AK/Format.h>
|
#include <AK/Format.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
|
@ -27,6 +28,9 @@
|
||||||
# pragma GCC optimize("O3")
|
# pragma GCC optimize("O3")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
extern bool g_dump_profile;
|
||||||
|
extern Optional<OutputFileStream> g_profile_stream;
|
||||||
|
|
||||||
namespace UserspaceEmulator {
|
namespace UserspaceEmulator {
|
||||||
|
|
||||||
u32 Emulator::virt_syscall(u32 function, u32 arg1, u32 arg2, u32 arg3)
|
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)
|
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);
|
round_to_page_size(address, size);
|
||||||
Vector<Region*, 4> marked_for_deletion;
|
Vector<Region*, 4> marked_for_deletion;
|
||||||
bool has_non_mmap_region = false;
|
bool has_non_mmap_region = false;
|
||||||
|
@ -870,6 +876,9 @@ u32 Emulator::virt$mmap(u32 params_addr)
|
||||||
name_str = { name.data(), name.size() };
|
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) {
|
if (params.flags & MAP_ANONYMOUS) {
|
||||||
mmu().add_region(MmapRegion::create_anonymous(final_address, final_size, params.prot, move(name_str)));
|
mmu().add_region(MmapRegion::create_anonymous(final_address, final_size, params.prot, move(name_str)));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -5,32 +5,45 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Emulator.h"
|
#include "Emulator.h"
|
||||||
|
#include <AK/FileStream.h>
|
||||||
#include <AK/Format.h>
|
#include <AK/Format.h>
|
||||||
#include <AK/LexicalPath.h>
|
#include <AK/LexicalPath.h>
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
#include <LibCore/ArgsParser.h>
|
#include <LibCore/ArgsParser.h>
|
||||||
#include <LibCore/DirIterator.h>
|
#include <LibCore/DirIterator.h>
|
||||||
#include <LibCore/File.h>
|
#include <LibCore/File.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <serenity.h>
|
#include <serenity.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
bool g_report_to_debug = false;
|
bool g_report_to_debug = false;
|
||||||
|
bool g_dump_profile = false;
|
||||||
|
unsigned g_profile_instruction_interval = 0;
|
||||||
|
Optional<OutputFileStream> g_profile_stream;
|
||||||
|
|
||||||
int main(int argc, char** argv, char** env)
|
int main(int argc, char** argv, char** env)
|
||||||
{
|
{
|
||||||
Vector<String> arguments;
|
Vector<String> arguments;
|
||||||
bool pause_on_startup { false };
|
bool pause_on_startup { false };
|
||||||
|
String profile_dump_path;
|
||||||
|
FILE* profile_output_file { nullptr };
|
||||||
|
|
||||||
Core::ArgsParser parser;
|
Core::ArgsParser parser;
|
||||||
parser.set_stop_on_first_non_option(true);
|
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(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(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.add_positional_argument(arguments, "Command to emulate", "command");
|
||||||
|
|
||||||
parser.parse(argc, argv);
|
parser.parse(argc, argv);
|
||||||
|
|
||||||
|
if (g_dump_profile && g_profile_instruction_interval == 0)
|
||||||
|
g_profile_instruction_interval = 128;
|
||||||
|
|
||||||
String executable_path;
|
String executable_path;
|
||||||
if (arguments[0].contains("/"sv))
|
if (arguments[0].contains("/"sv))
|
||||||
executable_path = Core::File::real_path_for(arguments[0]);
|
executable_path = Core::File::real_path_for(arguments[0]);
|
||||||
|
@ -41,6 +54,28 @@ int main(int argc, char** argv, char** env)
|
||||||
return 1;
|
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<String> environment;
|
Vector<String> environment;
|
||||||
for (int i = 0; env[i]; ++i) {
|
for (int i = 0; env[i]; ++i) {
|
||||||
environment.append(env[i]);
|
environment.append(env[i]);
|
||||||
|
@ -67,5 +102,10 @@ int main(int argc, char** argv, char** env)
|
||||||
if (pause_on_startup)
|
if (pause_on_startup)
|
||||||
emulator.pause();
|
emulator.pause();
|
||||||
|
|
||||||
return emulator.exec();
|
rc = emulator.exec();
|
||||||
|
|
||||||
|
if (g_dump_profile) {
|
||||||
|
g_profile_stream->write_or_error(R"(]})"sv.bytes());
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue