From 76b70dcb8f425363cf4c767cd84761e219349bdd Mon Sep 17 00:00:00 2001 From: Pierrick Bouvier Date: Mon, 15 Jun 2026 12:35:25 -0700 Subject: [PATCH] plugins/cpp: register callbacks using captureless lambda We can now demonstrate what previous changes allow us to do. Since all callbacks have a userdata pointer, we can use that mechanism to move an object through all of them. In other words, we can now have stateful plugins without resorting to any global variable. As an example, we implement tb counting plugin with our cpp plugin. It produces an output similar to hotblocks, with same performance. Reviewed-by: Manos Pitsidianakis Link: https://lore.kernel.org/qemu-devel/20260615193526.2883349-27-pierrick.bouvier@oss.qualcomm.com Signed-off-by: Pierrick Bouvier --- contrib/plugins/cpp.cpp | 90 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/contrib/plugins/cpp.cpp b/contrib/plugins/cpp.cpp index a0bb261fbe..006fa6de24 100644 --- a/contrib/plugins/cpp.cpp +++ b/contrib/plugins/cpp.cpp @@ -363,14 +363,98 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; -static void vcpu_tb_trans(struct qemu_plugin_tb *tb, void *userdata) +class Plugin { -} + public: + void tb_exec(std::atomic &count) + { + count++; + } + + void tb_trans(struct qemu_plugin_tb *tb) + { + auto vaddr = qemu_plugin_tb_vaddr(tb); + + TbData *data = [&]() { + std::lock_guard l(tb_trans_lock); + auto [it, is_new] = tb_data.try_emplace(vaddr); + TbData &in = it->second; + if (is_new) { + in.first = 0; + in.second = this; + } + return ∈ + } (); + + qemu_plugin_register_vcpu_tb_exec_cb( + tb, + [](unsigned int cpu_index, void *udata) { + auto &[counter, p] = *static_cast(udata); + p->tb_exec(counter); + }, + QEMU_PLUGIN_CB_NO_REGS, + data); + } + + void at_exit() + { + std::vector> v; + for (auto &[vaddr, data] : tb_data) { + uint64_t count = data.first; + v.push_back({vaddr, count}); + } + std::sort(v.begin(), v.end(), + [](const auto &a, const auto &b) { + return a.second > b.second; + }); + std::stringstream ss; + ss << "Top 10 executed TB are:\n"; + size_t idx = 0; + for (auto &tb : v) { + if (idx >= 10) { + break; + } + ss << std::hex << "0x" << tb.first << std::dec << ": " + << tb.second << '\n'; + ++idx; + } + qemu_plugin_outs(ss.str().c_str()); + } + + private: + using Vaddr = uint64_t; + using Counter = std::atomic; + using TbData = std::pair; + std::unordered_map tb_data; + std::mutex tb_trans_lock; +}; QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, int argc, char **argv) { - qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans, NULL); + Plugin *plugin = new Plugin; + + /* + * We can register a cb with any function, which may be a free + * function, a static class function, or a captureless lambda. + */ + qemu_plugin_register_vcpu_tb_trans_cb( + id, + [](struct qemu_plugin_tb *tb, void *userdata) { + Plugin *p = static_cast(userdata); + p->tb_trans(tb); + }, + plugin); + + qemu_plugin_register_atexit_cb( + id, + [](void *userdata) { + Plugin *p = static_cast(userdata); + p->at_exit(); + delete p; + }, + plugin); + return 0; }