replay: introduce breakpoint at the specified step

This patch introduces replay_break, replay_delete_break
qmp and hmp commands.
These commands allow stopping at the specified instruction.
It may be useful for debugging when there are some known
events that should be investigated.
replay_break command has one argument - number of instructions
executed since the start of the replay.
replay_delete_break removes previously set breakpoint.

Signed-off-by: Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru>
Acked-by: Markus Armbruster <armbru@redhat.com>

--

v4 changes:
 - removed useless error_free call
Message-Id: <160174520606.12451.7056879546045599378.stgit@pasha-ThinkPad-X280>

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Pavel Dovgalyuk 2020-10-03 20:13:26 +03:00 committed by Paolo Bonzini
parent e3b09ad2b6
commit e751067179
6 changed files with 175 additions and 0 deletions

View File

@ -1804,6 +1804,38 @@ SRST
Set QOM property *property* of object at location *path* to value *value* Set QOM property *property* of object at location *path* to value *value*
ERST 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", .name = "info",
.args_type = "item:s?", .args_type = "item:s?",

View File

@ -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_memory_size_summary(Monitor *mon, const QDict *qdict);
void hmp_info_sev(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_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 #endif

View File

@ -63,3 +63,39 @@
## ##
{ 'command': 'query-replay', { 'command': 'query-replay',
'returns': 'ReplayInfo' } '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' }

View File

@ -12,10 +12,13 @@
#include "qemu/osdep.h" #include "qemu/osdep.h"
#include "qapi/error.h" #include "qapi/error.h"
#include "sysemu/replay.h" #include "sysemu/replay.h"
#include "sysemu/runstate.h"
#include "replay-internal.h" #include "replay-internal.h"
#include "monitor/hmp.h" #include "monitor/hmp.h"
#include "monitor/monitor.h" #include "monitor/monitor.h"
#include "qapi/qapi-commands-replay.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) 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(); retval->icount = replay_get_current_icount();
return retval; 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;
}
}

View File

@ -94,6 +94,10 @@ extern ReplayState replay_state;
/* File for replay writing */ /* File for replay writing */
extern FILE *replay_file; 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_byte(uint8_t byte);
void replay_put_event(uint8_t event); void replay_put_event(uint8_t event);

View File

@ -34,6 +34,10 @@ static char *replay_filename;
ReplayState replay_state; ReplayState replay_state;
static GSList *replay_blockers; static GSList *replay_blockers;
/* Replay breakpoints */
uint64_t replay_break_icount = -1ULL;
QEMUTimer *replay_break_timer;
bool replay_next_event_is(int event) bool replay_next_event_is(int event)
{ {
bool res = false; bool res = false;
@ -73,6 +77,13 @@ int replay_get_instructions(void)
replay_mutex_lock(); replay_mutex_lock();
if (replay_next_event_is(EVENT_INSTRUCTION)) { if (replay_next_event_is(EVENT_INSTRUCTION)) {
res = replay_state.instruction_count; 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(); replay_mutex_unlock();
return res; return res;
@ -99,6 +110,12 @@ void replay_account_executed_instructions(void)
will be read from the log. */ will be read from the log. */
qemu_notify_event(); 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));
}
} }
} }
} }