#include "profiler.h" #include "../util/util.h" #include #include #include #include #include #include #include namespace { std::atomic g_profilingEnabled{false}; std::mutex g_histMutex; std::unordered_map g_hist; std::atomic g_totalSamples{0}; std::atomic g_dumpThreadStarted{false}; } // namespace void EnableProfiling() { g_profilingEnabled.store(true, std::memory_order_relaxed); } bool ProfilingEnabled() { return g_profilingEnabled.load(std::memory_order_relaxed); } void ProfileBlockHookCb(uc_engine*, uint64_t address, uint32_t, void*) { // thread_local, not a shared "last sample" - each real host thread has // its own uc_engine (see guest_engine.h's "Multithreading model") and // fires this independently; gating per-thread avoids one busy thread's // sampling starving another's. static thread_local std::chrono::steady_clock::time_point lastSample{}; auto now = std::chrono::steady_clock::now(); if (now - lastSample < std::chrono::milliseconds(2)) return; lastSample = now; std::lock_guard lock(g_histMutex); g_hist[address]++; g_totalSamples.fetch_add(1, std::memory_order_relaxed); } void StartProfileDumpThread() { bool expected = false; if (!g_dumpThreadStarted.compare_exchange_strong(expected, true)) return; // already running std::thread([]() { while (true) { std::this_thread::sleep_for(std::chrono::seconds(3)); std::vector> top; { std::lock_guard lock(g_histMutex); top.assign(g_hist.begin(), g_hist.end()); } if (top.empty()) continue; std::sort(top.begin(), top.end(), [](const auto& a, const auto& b) { return a.second > b.second; }); uint64_t total = g_totalSamples.load(std::memory_order_relaxed); Log("PROFILE: %llu total samples across %zu distinct block addresses - top hot PCs:", (unsigned long long)total, top.size()); for (size_t i = 0; i < top.size() && i < 15; i++) { Log("PROFILE: 0x%llx - %llu samples (%.1f%%)", (unsigned long long)top[i].first, (unsigned long long)top[i].second, total ? 100.0 * (double)top[i].second / (double)total : 0.0); } } }).detach(); }