2
0

stoptrigger.c 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. /*
  2. * Copyright (C) 2024, Simon Hamelin <simon.hamelin@grenoble-inp.org>
  3. *
  4. * Stop execution once a given address is reached or if the
  5. * count of executed instructions reached a specified limit
  6. *
  7. * License: GNU GPL, version 2 or later.
  8. * See the COPYING file in the top-level directory.
  9. */
  10. #include <assert.h>
  11. #include <glib.h>
  12. #include <inttypes.h>
  13. #include <stdio.h>
  14. #include <stdlib.h>
  15. #include <qemu-plugin.h>
  16. QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
  17. /* Scoreboard to track executed instructions count */
  18. typedef struct {
  19. uint64_t insn_count;
  20. uint64_t current_pc;
  21. } InstructionsCount;
  22. static struct qemu_plugin_scoreboard *insn_count_sb;
  23. static qemu_plugin_u64 insn_count;
  24. static qemu_plugin_u64 current_pc;
  25. static uint64_t icount;
  26. static int icount_exit_code;
  27. static bool exit_on_icount;
  28. static bool exit_on_address;
  29. /* Map trigger addresses to exit code */
  30. static GHashTable *addrs_ht;
  31. typedef struct {
  32. uint64_t exit_addr;
  33. int exit_code;
  34. } ExitInfo;
  35. static void exit_emulation(int return_code, char *message)
  36. {
  37. qemu_plugin_outs(message);
  38. g_free(message);
  39. exit(return_code);
  40. }
  41. static void exit_icount_reached(unsigned int cpu_index, void *udata)
  42. {
  43. uint64_t insn_vaddr = qemu_plugin_u64_get(current_pc, cpu_index);
  44. char *msg = g_strdup_printf("icount reached at 0x%" PRIx64 ", exiting\n",
  45. insn_vaddr);
  46. exit_emulation(icount_exit_code, msg);
  47. }
  48. static void exit_address_reached(unsigned int cpu_index, void *udata)
  49. {
  50. ExitInfo *ei = udata;
  51. g_assert(ei);
  52. char *msg = g_strdup_printf("0x%" PRIx64 " reached, exiting\n", ei->exit_addr);
  53. exit_emulation(ei->exit_code, msg);
  54. }
  55. static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
  56. {
  57. size_t tb_n = qemu_plugin_tb_n_insns(tb);
  58. for (size_t i = 0; i < tb_n; i++) {
  59. struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
  60. uint64_t insn_vaddr = qemu_plugin_insn_vaddr(insn);
  61. if (exit_on_icount) {
  62. /* Increment and check scoreboard for each instruction */
  63. qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu(
  64. insn, QEMU_PLUGIN_INLINE_ADD_U64, insn_count, 1);
  65. qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu(
  66. insn, QEMU_PLUGIN_INLINE_STORE_U64, current_pc, insn_vaddr);
  67. qemu_plugin_register_vcpu_insn_exec_cond_cb(
  68. insn, exit_icount_reached, QEMU_PLUGIN_CB_NO_REGS,
  69. QEMU_PLUGIN_COND_EQ, insn_count, icount + 1, NULL);
  70. }
  71. if (exit_on_address) {
  72. ExitInfo *ei = g_hash_table_lookup(addrs_ht, &insn_vaddr);
  73. if (ei) {
  74. /* Exit triggered by address */
  75. qemu_plugin_register_vcpu_insn_exec_cb(
  76. insn, exit_address_reached, QEMU_PLUGIN_CB_NO_REGS, ei);
  77. }
  78. }
  79. }
  80. }
  81. static void plugin_exit(qemu_plugin_id_t id, void *p)
  82. {
  83. g_hash_table_destroy(addrs_ht);
  84. qemu_plugin_scoreboard_free(insn_count_sb);
  85. }
  86. QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
  87. const qemu_info_t *info, int argc,
  88. char **argv)
  89. {
  90. addrs_ht = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, g_free);
  91. insn_count_sb = qemu_plugin_scoreboard_new(sizeof(InstructionsCount));
  92. insn_count = qemu_plugin_scoreboard_u64_in_struct(
  93. insn_count_sb, InstructionsCount, insn_count);
  94. current_pc = qemu_plugin_scoreboard_u64_in_struct(
  95. insn_count_sb, InstructionsCount, current_pc);
  96. for (int i = 0; i < argc; i++) {
  97. char *opt = argv[i];
  98. g_auto(GStrv) tokens = g_strsplit(opt, "=", 2);
  99. if (g_strcmp0(tokens[0], "icount") == 0) {
  100. g_auto(GStrv) icount_tokens = g_strsplit(tokens[1], ":", 2);
  101. icount = g_ascii_strtoull(icount_tokens[0], NULL, 0);
  102. if (icount < 1 || g_strrstr(icount_tokens[0], "-") != NULL) {
  103. fprintf(stderr,
  104. "icount parsing failed: '%s' must be a positive "
  105. "integer\n",
  106. icount_tokens[0]);
  107. return -1;
  108. }
  109. if (icount_tokens[1]) {
  110. icount_exit_code = g_ascii_strtoull(icount_tokens[1], NULL, 0);
  111. }
  112. exit_on_icount = true;
  113. } else if (g_strcmp0(tokens[0], "addr") == 0) {
  114. g_auto(GStrv) addr_tokens = g_strsplit(tokens[1], ":", 2);
  115. ExitInfo *ei = g_malloc(sizeof(ExitInfo));
  116. ei->exit_addr = g_ascii_strtoull(addr_tokens[0], NULL, 0);
  117. ei->exit_code = 0;
  118. if (addr_tokens[1]) {
  119. ei->exit_code = g_ascii_strtoull(addr_tokens[1], NULL, 0);
  120. }
  121. g_hash_table_insert(addrs_ht, &ei->exit_addr, ei);
  122. exit_on_address = true;
  123. } else {
  124. fprintf(stderr, "option parsing failed: %s\n", opt);
  125. return -1;
  126. }
  127. }
  128. if (!exit_on_icount && !exit_on_address) {
  129. fprintf(stderr, "'icount' or 'addr' argument missing\n");
  130. return -1;
  131. }
  132. /* Register translation block and exit callbacks */
  133. qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
  134. qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
  135. return 0;
  136. }