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 <manos.pitsidianakis@linaro.org> Link: https://lore.kernel.org/qemu-devel/20260615193526.2883349-27-pierrick.bouvier@oss.qualcomm.com Signed-off-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Pierrick Bouvier committed
Jun 15, 2026 at 12:35 UTC
76b70dcb8f425363cf4c767cd84761e219349bdd
1 file changed
+87
-3
contrib/plugins/cpp.cpp
+87
-3
@@ -363,14 +363,98 @@
363
364
QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
365
366
-static void vcpu_tb_trans(struct qemu_plugin_tb *tb, void *userdata)
366
+class Plugin
367
{
368
-}
368
+ public:
369
+ void tb_exec(std::atomic<uint64_t> &count)
370
+ {
371
+ count++;
372
+ }
373
+
374
+ void tb_trans(struct qemu_plugin_tb *tb)
375
+ {
376
+ auto vaddr = qemu_plugin_tb_vaddr(tb);
377
+
378
+ TbData *data = [&]() {
379
+ std::lock_guard l(tb_trans_lock);
380
+ auto [it, is_new] = tb_data.try_emplace(vaddr);
381
+ TbData &in = it->second;
382
+ if (is_new) {
383
+ in.first = 0;
384
+ in.second = this;
385
+ }
386
+ return ∈
387
+ } ();
388
+
389
+ qemu_plugin_register_vcpu_tb_exec_cb(
390
+ tb,
391
+ [](unsigned int cpu_index, void *udata) {
392
+ auto &[counter, p] = *static_cast<TbData*>(udata);
393
+ p->tb_exec(counter);
394
+ },
395
+ QEMU_PLUGIN_CB_NO_REGS,
396
+ data);
397
+ }
398
+
399
+ void at_exit()
400
+ {
401
+ std::vector<std::pair<Vaddr, uint64_t>> v;
402
+ for (auto &[vaddr, data] : tb_data) {
403
+ uint64_t count = data.first;
404
+ v.push_back({vaddr, count});
405
+ }
406
+ std::sort(v.begin(), v.end(),
407
+ [](const auto &a, const auto &b) {
408
+ return a.second > b.second;
409
+ });
410
+ std::stringstream ss;
411
+ ss << "Top 10 executed TB are:\n";
412
+ size_t idx = 0;
413
+ for (auto &tb : v) {
414
+ if (idx >= 10) {
415
+ break;
416
+ }
417
+ ss << std::hex << "0x" << tb.first << std::dec << ": "
418
+ << tb.second << '\n';
419
+ ++idx;
420
+ }
421
+ qemu_plugin_outs(ss.str().c_str());
422
+ }
423
+
424
+ private:
425
+ using Vaddr = uint64_t;
426
+ using Counter = std::atomic<uint64_t>;
427
+ using TbData = std::pair<Counter, Plugin*>;
428
+ std::unordered_map<Vaddr, TbData> tb_data;
429
+ std::mutex tb_trans_lock;
430
+};
431
432
QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
433
const qemu_info_t *info,
434
int argc, char **argv)
435
{
374
- qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans, NULL);
436
+ Plugin *plugin = new Plugin;
437
+
438
+ /*
439
+ * We can register a cb with any function, which may be a free
440
+ * function, a static class function, or a captureless lambda.
441
+ */
442
+ qemu_plugin_register_vcpu_tb_trans_cb(
443
+ id,
444
+ [](struct qemu_plugin_tb *tb, void *userdata) {
445
+ Plugin *p = static_cast<Plugin*>(userdata);
446
+ p->tb_trans(tb);
447
+ },
448
+ plugin);
449
+
450
+ qemu_plugin_register_atexit_cb(
451
+ id,
452
+ [](void *userdata) {
453
+ Plugin *p = static_cast<Plugin*>(userdata);
454
+ p->at_exit();
455
+ delete p;
456
+ },
457
+ plugin);
458
+
459
return 0;
460
}