diff options
-rw-r--r-- | hmp-commands.hx | 32 | ||||
-rw-r--r-- | include/monitor/hmp.h | 2 | ||||
-rw-r--r-- | qapi/replay.json | 36 | ||||
-rw-r--r-- | replay/replay-debugging.c | 84 | ||||
-rw-r--r-- | replay/replay-internal.h | 4 | ||||
-rw-r--r-- | replay/replay.c | 17 |
6 files changed, 175 insertions, 0 deletions
diff --git a/hmp-commands.hx b/hmp-commands.hx index 1088d64503..7680d0b380 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -1805,6 +1805,38 @@ SRST ERST { + .name = "replay_break", + .args_type = "icount:i", + .params = "icount", + .help = "set breakpoint at the specified instruction count", + .cmd = hmp_replay_break, + }, + +SRST +``replay_break`` *icount* + Set replay breakpoint at instruction count *icount*. + Execution stops when the specified instruction is reached. + There can be at most one breakpoint. When breakpoint is set, any prior + one is removed. The breakpoint may be set only in replay mode and only + "in the future", i.e. at instruction counts greater than the current one. + The current instruction count can be observed with ``info replay``. +ERST + + { + .name = "replay_delete_break", + .args_type = "", + .params = "", + .help = "remove replay breakpoint", + .cmd = hmp_replay_delete_break, + }, + +SRST +``replay_delete_break`` + Remove replay breakpoint which was previously set with ``replay_break``. + The command is ignored when there are no replay breakpoints. +ERST + + { .name = "info", .args_type = "item:s?", .params = "[subcommand]", diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h index f297fccce8..809ad638bb 100644 --- a/include/monitor/hmp.h +++ b/include/monitor/hmp.h @@ -130,5 +130,7 @@ void hmp_info_vm_generation_id(Monitor *mon, const QDict *qdict); void hmp_info_memory_size_summary(Monitor *mon, const QDict *qdict); void hmp_info_sev(Monitor *mon, const QDict *qdict); void hmp_info_replay(Monitor *mon, const QDict *qdict); +void hmp_replay_break(Monitor *mon, const QDict *qdict); +void hmp_replay_delete_break(Monitor *mon, const QDict *qdict); #endif diff --git a/qapi/replay.json b/qapi/replay.json index e6b3f6001d..173ba76107 100644 --- a/qapi/replay.json +++ b/qapi/replay.json @@ -63,3 +63,39 @@ ## { 'command': 'query-replay', 'returns': 'ReplayInfo' } + +## +# @replay-break: +# +# Set replay breakpoint at instruction count @icount. +# Execution stops when the specified instruction is reached. +# There can be at most one breakpoint. When breakpoint is set, any prior +# one is removed. The breakpoint may be set only in replay mode and only +# "in the future", i.e. at instruction counts greater than the current one. +# The current instruction count can be observed with @query-replay. +# +# @icount: instruction count to stop at +# +# Since: 5.2 +# +# Example: +# +# -> { "execute": "replay-break", "data": { "icount": 220414 } } +# +## +{ 'command': 'replay-break', 'data': { 'icount': 'int' } } + +## +# @replay-delete-break: +# +# Remove replay breakpoint which was set with @replay-break. +# The command is ignored when there are no replay breakpoints. +# +# Since: 5.2 +# +# Example: +# +# -> { "execute": "replay-delete-break" } +# +## +{ 'command': 'replay-delete-break' } diff --git a/replay/replay-debugging.c b/replay/replay-debugging.c index 51a6de4e81..3dc23b84fc 100644 --- a/replay/replay-debugging.c +++ b/replay/replay-debugging.c @@ -12,10 +12,13 @@ #include "qemu/osdep.h" #include "qapi/error.h" #include "sysemu/replay.h" +#include "sysemu/runstate.h" #include "replay-internal.h" #include "monitor/hmp.h" #include "monitor/monitor.h" #include "qapi/qapi-commands-replay.h" +#include "qapi/qmp/qdict.h" +#include "qemu/timer.h" void hmp_info_replay(Monitor *mon, const QDict *qdict) { @@ -41,3 +44,84 @@ ReplayInfo *qmp_query_replay(Error **errp) retval->icount = replay_get_current_icount(); return retval; } + +static void replay_break(uint64_t icount, QEMUTimerCB callback, void *opaque) +{ + assert(replay_mode == REPLAY_MODE_PLAY); + assert(replay_mutex_locked()); + assert(replay_break_icount >= replay_get_current_icount()); + assert(callback); + + replay_break_icount = icount; + + if (replay_break_timer) { + timer_del(replay_break_timer); + } + replay_break_timer = timer_new_ns(QEMU_CLOCK_REALTIME, + callback, opaque); +} + +static void replay_delete_break(void) +{ + assert(replay_mode == REPLAY_MODE_PLAY); + assert(replay_mutex_locked()); + + if (replay_break_timer) { + timer_del(replay_break_timer); + timer_free(replay_break_timer); + replay_break_timer = NULL; + } + replay_break_icount = -1ULL; +} + +static void replay_stop_vm(void *opaque) +{ + vm_stop(RUN_STATE_PAUSED); + replay_delete_break(); +} + +void qmp_replay_break(int64_t icount, Error **errp) +{ + if (replay_mode == REPLAY_MODE_PLAY) { + if (icount >= replay_get_current_icount()) { + replay_break(icount, replay_stop_vm, NULL); + } else { + error_setg(errp, + "cannot set breakpoint at the instruction in the past"); + } + } else { + error_setg(errp, "setting the breakpoint is allowed only in play mode"); + } +} + +void hmp_replay_break(Monitor *mon, const QDict *qdict) +{ + int64_t icount = qdict_get_try_int(qdict, "icount", -1LL); + Error *err = NULL; + + qmp_replay_break(icount, &err); + if (err) { + error_report_err(err); + return; + } +} + +void qmp_replay_delete_break(Error **errp) +{ + if (replay_mode == REPLAY_MODE_PLAY) { + replay_delete_break(); + } else { + error_setg(errp, "replay breakpoints are allowed only in play mode"); + } +} + +void hmp_replay_delete_break(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + + qmp_replay_delete_break(&err); + if (err) { + error_report_err(err); + return; + } +} diff --git a/replay/replay-internal.h b/replay/replay-internal.h index 33ac551e78..2f6145ec7c 100644 --- a/replay/replay-internal.h +++ b/replay/replay-internal.h @@ -94,6 +94,10 @@ extern ReplayState replay_state; /* File for replay writing */ extern FILE *replay_file; +/* Instruction count of the replay breakpoint */ +extern uint64_t replay_break_icount; +/* Timer for the replay breakpoint callback */ +extern QEMUTimer *replay_break_timer; void replay_put_byte(uint8_t byte); void replay_put_event(uint8_t event); diff --git a/replay/replay.c b/replay/replay.c index 797b6a5b15..d4c228ab28 100644 --- a/replay/replay.c +++ b/replay/replay.c @@ -34,6 +34,10 @@ static char *replay_filename; ReplayState replay_state; static GSList *replay_blockers; +/* Replay breakpoints */ +uint64_t replay_break_icount = -1ULL; +QEMUTimer *replay_break_timer; + bool replay_next_event_is(int event) { bool res = false; @@ -73,6 +77,13 @@ int replay_get_instructions(void) replay_mutex_lock(); if (replay_next_event_is(EVENT_INSTRUCTION)) { res = replay_state.instruction_count; + if (replay_break_icount != -1LL) { + uint64_t current = replay_get_current_icount(); + assert(replay_break_icount >= current); + if (current + res > replay_break_icount) { + res = replay_break_icount - current; + } + } } replay_mutex_unlock(); return res; @@ -99,6 +110,12 @@ void replay_account_executed_instructions(void) will be read from the log. */ qemu_notify_event(); } + /* Execution reached the break step */ + if (replay_break_icount == replay_state.current_icount) { + /* Cannot make callback directly from the vCPU thread */ + timer_mod_ns(replay_break_timer, + qemu_clock_get_ns(QEMU_CLOCK_REALTIME)); + } } } } |