commit 8d018c17170a3b623f48de5282955b817a6284f3
parent 4dd9fe04e1399e8629ab2a98b54db6a7dcdb0076
Author: Louis Burda <quent.burda@gmail.com>
Date: Mon, 6 Feb 2023 11:30:27 -0600
qemu-targetstep: Attempt to track guest process gfn when running in userspace
Seems like single-stepping the guest with LAPIC influences the guest scheduler behaviour, since just a single step inside the target gfn (to determine if its running in userspace), is enough to for us to never reach the GUEST_STOP_TRACK event. FWICT the single-stepping is not frequent and does not take long enough to justify never reaching the stop event.
Diffstat:
13 files changed, 329 insertions(+), 281 deletions(-)
diff --git a/Makefile b/Makefile
@@ -11,7 +11,7 @@ BINS += test/kvm-step test/kvm-step_guest
BINS += test/kvm-targetstep test/kvm-targetstep_guest
BINS += test/kvm-pagestep test/kvm-pagestep_guest
BINS += test/qemu-pagestep
-BINS += test/qemu-eviction test/qemu-eviction_guest
+BINS += test/qemu-targetstep test/qemu-targetstep_guest
# BINS += test/qemu-aes_guest test/qemu-aes
BINS += util/loglevel util/reset util/mainpfn
diff --git a/cachepc/cachepc.h b/cachepc/cachepc.h
@@ -44,21 +44,29 @@ struct cpc_fault {
struct cpc_track_pages {
bool singlestep_resolve;
uint64_t retinst;
+ uint64_t retinst_user;
bool in_step;
bool prev_avail;
uint64_t prev_gfn;
+ uint16_t prev_err;
+
bool cur_avail;
uint64_t cur_gfn;
+ uint16_t cur_err;
+
bool next_avail;
uint64_t next_gfn;
+ uint16_t next_err;
};
struct cpc_track_steps {
bool with_data;
bool use_target;
+ bool target_user;
uint64_t target_gfn;
bool stepping;
+ bool in_target;
bool use_filter;
};
@@ -118,6 +126,7 @@ extern uint64_t cpc_track_start_gfn;
extern uint64_t cpc_track_end_gfn;
extern uint64_t cpc_retinst;
+extern uint64_t cpc_retinst_user;
extern uint64_t cpc_retinst_prev;
extern uint64_t cpc_rip;
diff --git a/cachepc/const.h b/cachepc/const.h
@@ -10,6 +10,7 @@
#define CPC_L1MISS_PMC 0
#define CPC_RETINST_PMC 1
+#define CPC_RETINST_USER_PMC 2
#define CPC_VMSA_MAGIC_ADDR ((void *) 0xC0FFEE)
diff --git a/cachepc/event.c b/cachepc/event.c
@@ -158,14 +158,17 @@ cpc_send_track_step_event(struct list_head *list)
}
int
-cpc_send_track_page_event(uint64_t gfn_prev, uint64_t gfn, uint64_t retinst)
+cpc_send_track_page_event(uint64_t gfn_prev, uint64_t gfn, uint16_t err,
+ uint64_t retinst, uint64_t retinst_user)
{
struct cpc_event event = { 0 };
event.type = CPC_EVENT_TRACK_PAGE;
event.page.inst_gfn_prev = gfn_prev;
event.page.inst_gfn = gfn;
+ event.page.fault_err = err;
event.page.retinst = retinst;
+ event.page.retinst_user = retinst_user;
return cpc_send_event(event);
}
diff --git a/cachepc/event.h b/cachepc/event.h
@@ -19,7 +19,8 @@ int cpc_send_guest_event(uint64_t type, uint64_t val);
int cpc_send_pause_event(void);
int cpc_send_track_step_event(struct list_head *list);
int cpc_send_track_step_event_single(uint64_t gfn, uint32_t err, uint64_t retinst);
-int cpc_send_track_page_event(uint64_t gfn_prev, uint64_t gfn, uint64_t retinst);
+int cpc_send_track_page_event(uint64_t gfn_prev, uint64_t gfn, uint16_t err,
+ uint64_t retinst, uint64_t retinst_user);
bool cpc_event_is_done(void);
diff --git a/cachepc/kvm.c b/cachepc/kvm.c
@@ -41,8 +41,10 @@ bool cpc_prime_probe = false;
EXPORT_SYMBOL(cpc_prime_probe);
uint64_t cpc_retinst = 0;
+uint64_t cpc_retinst_user = 0;
uint64_t cpc_retinst_prev = 0;
EXPORT_SYMBOL(cpc_retinst);
+EXPORT_SYMBOL(cpc_retinst_user);
EXPORT_SYMBOL(cpc_retinst_prev);
uint64_t cpc_rip = 0;
@@ -244,6 +246,10 @@ cpc_pmc_setup(void *p)
/* retired instructions in guest */
cpc_init_pmc(CPC_RETINST_PMC, 0xC0, 0x00,
PMC_GUEST, PMC_KERNEL | PMC_USER);
+
+ /* retired instructions in guest userspace */
+ cpc_init_pmc(CPC_RETINST_USER_PMC, 0xC0, 0x00,
+ PMC_GUEST, PMC_USER);
}
void
@@ -510,9 +516,11 @@ cpc_track_mode_ioctl(void __user *arg_user)
break;
case CPC_TRACK_STEPS:
cpc_track_steps.use_target = cfg.steps.use_target;
+ cpc_track_steps.target_user = cfg.steps.target_user;
cpc_track_steps.target_gfn = cfg.steps.target_gfn;
cpc_track_steps.with_data = cfg.steps.with_data;
cpc_track_steps.use_filter = cfg.steps.use_filter;
+ cpc_track_steps.in_target = false;
if (!cpc_track_steps.use_target
&& cpc_track_steps.with_data) {
cpc_track_all(vcpu, KVM_PAGE_TRACK_ACCESS);
@@ -705,6 +713,7 @@ cpc_kvm_init(void)
cpc_ds_ul = NULL;
cpc_retinst = 0;
+ cpc_retinst_user = 0;
cpc_long_step = false;
cpc_singlestep = false;
cpc_singlestep_reset = false;
diff --git a/cachepc/macro.S b/cachepc/macro.S
@@ -4,6 +4,7 @@
.macro barrier
mfence # finish load and stores
lfence # prevent reordering
+ rdtsc # prevent reordering
.endm
# clobbers rax, rbx, rcx, rdx, (out)
diff --git a/cachepc/uapi.h b/cachepc/uapi.h
@@ -63,6 +63,7 @@ struct cpc_track_cfg {
union {
struct {
__u64 target_gfn;
+ __u8 target_user;
__u8 use_target;
__u8 use_filter;
__u8 with_data;
@@ -84,7 +85,9 @@ struct cpc_track_step_event {
struct cpc_track_page_event {
__u64 inst_gfn_prev;
__u64 inst_gfn;
+ __u16 fault_err;
__u64 retinst;
+ __u64 retinst_user;
};
struct cpc_guest_event {
diff --git a/test/.gitignore b/test/.gitignore
@@ -7,10 +7,10 @@ kvm-step
kvm-step_guest
kvm-pagestep
kvm-pagestep_guest
-qemu-eviction
-qemu-eviction_guest
qemu-pagestep
qemu-aes
qemu-aes_guest
qemu-poc
qemu-poc_guest
+qemu-targetstep
+qemu-targetstep_guest
diff --git a/test/qemu-eviction.c b/test/qemu-eviction.c
@@ -1,231 +0,0 @@
-#include "test/kvm-eviction.h"
-#include "test/kvm.h"
-#include "test/util.h"
-#include "cachepc/uapi.h"
-
-#include <sys/ioctl.h>
-#include <sys/mman.h>
-#include <signal.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <err.h>
-#include <string.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-static struct cpc_event event;
-static struct cpc_event_batch batch;
-
-int
-monitor(bool baseline)
-{
- uint8_t counts[L1_SETS];
- int ret;
-
- ret = ioctl(kvm_dev, KVM_CPC_POLL_EVENT, &event);
- if (ret && errno == EAGAIN) return 0;
- if (ret) err(1, "KVM_CPC_POLL_EVENT");
-
- switch (event.type) {
- case CPC_EVENT_GUEST:
- printf("Guest event: %i\n", event.guest.type);
- if (event.guest.type == CPC_GUEST_STOP_TRACK)
- return 2;
- break;
- case CPC_EVENT_TRACK_STEP:
- ret = ioctl(kvm_dev, KVM_CPC_READ_COUNTS, counts);
- if (ret) err(1, "KVM_CPC_READ_COUNTS");
-
- printf("Event: rip:%016llx cnt:%llu "
- "inst:%08llx ret:%llu\n",
- vm_get_rip(), event.step.fault_count,
- event.step.inst_gfn, event.step.retinst);
- print_counts(counts);
- printf("\n");
- print_counts_raw(counts);
- printf("\n");
- break;
- default:
- errx(1, "unexpected event type %i", event.type);
- }
-
- ret = ioctl(kvm_dev, KVM_CPC_ACK_EVENT, &event.id);
- if (ret) err(1, "KVM_CPC_ACK_EVENT");
-
- return 1;
-}
-
-void
-read_batch(void)
-{
- uint32_t i;
- int ret;
-
- ret = ioctl(kvm_dev, KVM_CPC_READ_BATCH, &batch);
- if (ret && errno == EAGAIN) return;
- if (ret && errno != EAGAIN) err(1, "KVM_CPC_READ_BATCH");
-
- for (i = 0; i < batch.cnt; i++) {
- if (batch.buf[i].type != CPC_EVENT_TRACK_PAGE)
- continue;
-
- printf("GFN %08llx\n", batch.buf[i].page.inst_gfn);
- }
-}
-
-void
-reset(int sig)
-{
- int ret;
-
- ret = ioctl(kvm_dev, KVM_CPC_RESET);
- if (ret) err(1, "KVM_CPC_RESET");
-
- exit(1);
-}
-
-int
-main(int argc, const char **argv)
-{
- uint8_t baseline[L1_SETS];
- struct cpc_track_cfg cfg;
- bool first_guest_event;
- uint32_t eventcnt;
- uint32_t arg;
- int ret;
-
- pin_process(0, SECONDARY_CORE, true);
-
- setvbuf(stdout, NULL, _IONBF, 0);
-
- kvm_setup_init();
-
- ret = ioctl(kvm_dev, KVM_CPC_RESET);
- if (ret) err(1, "KVM_CPC_RESET");
-
- signal(SIGINT, reset);
-
- arg = true;
- ret = ioctl(kvm_dev, KVM_CPC_CALC_BASELINE, &arg);
- if (ret) err(1, "KVM_CPC_CALC_BASELINE");
-
- memset(&cfg, 0, sizeof(cfg));
- cfg.mode = CPC_TRACK_STEPS;
- cfg.steps.with_data = true;
- cfg.steps.use_filter = true;
- ret = ioctl(kvm_dev, KVM_CPC_TRACK_MODE, &cfg);
- if (ret) err(1, "KVM_CPC_RESET");
-
- eventcnt = 0;
- while (eventcnt < 50) {
- eventcnt += monitor(true);
- }
-
- ret = ioctl(kvm_dev, KVM_CPC_VM_REQ_PAUSE);
- if (ret) err(1, "KVM_CPC_VM_REQ_PAUSE");
-
- while (1) {
- ret = ioctl(kvm_dev, KVM_CPC_POLL_EVENT, &event);
- if (ret && errno == EAGAIN) continue;
- if (ret) err(1, "KVM_CPC_POLL_EVENT");
-
- if (event.type == CPC_EVENT_PAUSE) break;
-
- ret = ioctl(kvm_dev, KVM_CPC_ACK_EVENT, &event.id);
- if (ret) err(1, "KVM_CPC_ACK_EVENT");
- }
-
- arg = false;
- ret = ioctl(kvm_dev, KVM_CPC_CALC_BASELINE, &arg);
- if (ret) err(1, "KVM_CPC_CALC_BASELINE");
-
- arg = true;
- ret = ioctl(kvm_dev, KVM_CPC_APPLY_BASELINE, &arg);
- if (ret) err(1, "KVM_CPC_APPLY_BASELINE");
-
- ret = ioctl(kvm_dev, KVM_CPC_READ_BASELINE, baseline);
- if (ret) err(1, "KVM_CPC_READ_BASELINE");
-
- printf("\nBaseline:\n");
- print_counts(baseline);
- printf("\n");
- print_counts_raw(baseline);
- printf("\n\n");
-
- memset(&cfg, 0, sizeof(&cfg));
- cfg.mode = CPC_TRACK_NONE;
- ret = ioctl(kvm_dev, KVM_CPC_TRACK_MODE, &cfg);
- if (ret) err(1, "KVM_CPC_TRACK_MODE");
-
- ret = ioctl(kvm_dev, KVM_CPC_ACK_EVENT, &event.id);
- if (ret) err(1, "KVM_CPC_ACK_EVENT");
-
- /* wait until guest program is run */
- printf("Press enter to continue..\n");
- getchar();
-
- arg = true;
- ret = ioctl(kvm_dev, KVM_CPC_BATCH_EVENTS, &arg);
- if (ret) err(1, "KVM_CPC_BATCH_EVENTS");
-
- memset(&cfg, 0, sizeof(cfg));
- cfg.mode = CPC_TRACK_PAGES;
- ret = ioctl(kvm_dev, KVM_CPC_TRACK_MODE, &cfg);
- if (ret) err(1, "KVM_CPC_TRACK_MODE");
-
- batch.cnt = 0;
- batch.maxcnt = CPC_EVENT_BATCH_MAX;
- batch.buf = malloc(sizeof(struct cpc_event) * batch.maxcnt);
- if (!batch.buf) err(1, "malloc");
-
- first_guest_event = true;
- while (1) {
- ret = ioctl(kvm_dev, KVM_CPC_POLL_EVENT, &event);
- if (ret && errno == EAGAIN) continue;
- if (ret) err(1, "KVM_CPC_POLL_EVENT");
-
- printf("EVENT %i\n", event.type);
-
- if (event.type == CPC_EVENT_GUEST
- && event.guest.type == CPC_GUEST_START_TRACK) {
- if (!first_guest_event)
- break;
- first_guest_event = false;
- }
-
- if (event.type == CPC_EVENT_BATCH)
- read_batch();
-
- ret = ioctl(kvm_dev, KVM_CPC_ACK_EVENT, &event.id);
- if (ret) err(1, "KVM_CPC_ACK_EVENT");
- }
-
- read_batch();
-
- if (!batch.cnt) errx(1, "empty batch buffer");
- memset(&cfg, 0, sizeof(cfg));
- cfg.mode = CPC_TRACK_STEPS;
- cfg.steps.target_gfn = batch.buf[batch.cnt - 3].page.inst_gfn;
- cfg.steps.use_target = true;
- cfg.steps.use_filter = true;
- cfg.steps.with_data = true;
- ret = ioctl(kvm_dev, KVM_CPC_TRACK_MODE, &cfg);
- if (ret) err(1, "KVM_CPC_TRACK_MODE");
-
- ret = ioctl(kvm_dev, KVM_CPC_ACK_EVENT, &event.id);
- if (ret) err(1, "KVM_CPC_ACK_EVENT");
-
- while (monitor(false) != 2);
-
- signal(SIGINT, NULL);
-
- ret = ioctl(kvm_dev, KVM_CPC_RESET);
- if (ret) err(1, "KVM_CPC_RESET");
-
- free(batch.buf);
-
- kvm_setup_deinit();
-}
-
diff --git a/test/qemu-eviction_guest.c b/test/qemu-eviction_guest.c
@@ -1,45 +0,0 @@
-#include "cachepc/uapi.h"
-
-#include <sys/time.h>
-#include <sys/resource.h>
-#include <err.h>
-#include <unistd.h>
-#include <stdint.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-int
-main(int argc, const char **argv)
-{
- void *buf;
-
- buf = NULL;
- if (posix_memalign(&buf, L1_LINESIZE * L1_SETS, L1_LINESIZE * L1_SETS))
- err(1, "memalign");
- memset(buf, 0, L1_LINESIZE * L1_SETS);
-
- setpriority(PRIO_PROCESS, 0, -20);
-
- while (1) {
- printf("LOOP\n");
- CPC_DO_VMMCALL(KVM_HC_CPC_VMMCALL_SIGNAL,
- CPC_GUEST_START_TRACK, 0);
- *(uint8_t *)(buf + L1_LINESIZE * 9) = 1;
- *(uint8_t *)(buf + L1_LINESIZE * 10) = 1;
- *(uint8_t *)(buf + L1_LINESIZE * 11) = 1;
- *(uint8_t *)(buf + L1_LINESIZE * 12) = 1;
- *(uint8_t *)(buf + L1_LINESIZE * 13) = 1;
- *(uint8_t *)(buf + L1_LINESIZE * 14) = 1;
- *(uint8_t *)(buf + L1_LINESIZE * 15) = 1;
- *(uint8_t *)(buf + L1_LINESIZE * 9) = 1;
- *(uint8_t *)(buf + L1_LINESIZE * 10) = 1;
- *(uint8_t *)(buf + L1_LINESIZE * 11) = 1;
- *(uint8_t *)(buf + L1_LINESIZE * 12) = 1;
- *(uint8_t *)(buf + L1_LINESIZE * 13) = 1;
- *(uint8_t *)(buf + L1_LINESIZE * 14) = 1;
- *(uint8_t *)(buf + L1_LINESIZE * 15) = 1;
- CPC_DO_VMMCALL(KVM_HC_CPC_VMMCALL_SIGNAL,
- CPC_GUEST_STOP_TRACK, 0);
- }
-}
diff --git a/test/qemu-targetstep.c b/test/qemu-targetstep.c
@@ -0,0 +1,247 @@
+#include "test/kvm-eviction.h"
+#include "test/kvm.h"
+#include "test/util.h"
+#include "cachepc/uapi.h"
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <err.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static struct cpc_event event;
+static struct cpc_event_batch batch;
+static uint64_t last_user_inst_gfn;
+
+int
+monitor(bool baseline)
+{
+ uint8_t counts[L1_SETS];
+ int ret;
+
+ ret = ioctl(kvm_dev, KVM_CPC_POLL_EVENT, &event);
+ if (ret && errno == EAGAIN) return 0;
+ if (ret) err(1, "KVM_CPC_POLL_EVENT");
+
+ switch (event.type) {
+ case CPC_EVENT_GUEST:
+ printf("Guest %s\n", !event.guest.type ? "start" : "stop");
+ if (event.guest.type == CPC_GUEST_STOP_TRACK)
+ return 2;
+ break;
+ case CPC_EVENT_TRACK_STEP:
+ ret = ioctl(kvm_dev, KVM_CPC_READ_COUNTS, counts);
+ if (ret) err(1, "KVM_CPC_READ_COUNTS");
+
+ printf("Event: rip:%016llx cnt:%llu "
+ "inst:%08llx ret:%llu\n",
+ vm_get_rip(), event.step.fault_count,
+ event.step.inst_gfn, event.step.retinst);
+ print_counts(counts);
+ printf("\n");
+ print_counts_raw(counts);
+ printf("\n");
+ break;
+ default:
+ errx(1, "unexpected event type %i", event.type);
+ }
+
+ ret = ioctl(kvm_dev, KVM_CPC_ACK_EVENT, &event.id);
+ if (ret) err(1, "KVM_CPC_ACK_EVENT");
+
+ return 1;
+}
+
+void
+read_batch(void)
+{
+ uint32_t i;
+ int ret;
+
+ ret = ioctl(kvm_dev, KVM_CPC_READ_BATCH, &batch);
+ if (ret && errno == EAGAIN) return;
+ if (ret && errno != EAGAIN) err(1, "KVM_CPC_READ_BATCH");
+
+ for (i = 0; i < batch.cnt; i++) {
+ if (batch.buf[i].type != CPC_EVENT_TRACK_PAGE)
+ continue;
+
+ if (batch.buf[i].page.retinst_user > 0) {
+ printf("GFN %08llx %04x %4llu %4llu\n",
+ batch.buf[i].page.inst_gfn,
+ batch.buf[i].page.fault_err,
+ batch.buf[i].page.retinst,
+ batch.buf[i].page.retinst_user);
+ last_user_inst_gfn = batch.buf[i].page.inst_gfn;
+ }
+ }
+}
+
+void
+reset(int sig)
+{
+ int ret;
+
+ ret = ioctl(kvm_dev, KVM_CPC_RESET);
+ if (ret) err(1, "KVM_CPC_RESET");
+
+ exit(1);
+}
+
+int
+main(int argc, const char **argv)
+{
+ uint8_t baseline[L1_SETS];
+ struct cpc_track_cfg cfg;
+ bool first_guest_event;
+ uint32_t eventcnt;
+ uint32_t arg;
+ int ret;
+
+ pin_process(0, SECONDARY_CORE, true);
+
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ kvm_setup_init();
+
+ ret = ioctl(kvm_dev, KVM_CPC_RESET);
+ if (ret) err(1, "KVM_CPC_RESET");
+
+ signal(SIGINT, reset);
+
+ arg = true;
+ ret = ioctl(kvm_dev, KVM_CPC_CALC_BASELINE, &arg);
+ if (ret) err(1, "KVM_CPC_CALC_BASELINE");
+
+ memset(&cfg, 0, sizeof(cfg));
+ cfg.mode = CPC_TRACK_STEPS;
+ cfg.steps.with_data = true;
+ cfg.steps.use_filter = true;
+ ret = ioctl(kvm_dev, KVM_CPC_TRACK_MODE, &cfg);
+ if (ret) err(1, "KVM_CPC_RESET");
+
+ eventcnt = 0;
+ while (eventcnt < 50) {
+ eventcnt += monitor(true);
+ }
+
+ ret = ioctl(kvm_dev, KVM_CPC_VM_REQ_PAUSE);
+ if (ret) err(1, "KVM_CPC_VM_REQ_PAUSE");
+
+ while (1) {
+ ret = ioctl(kvm_dev, KVM_CPC_POLL_EVENT, &event);
+ if (ret && errno == EAGAIN) continue;
+ if (ret) err(1, "KVM_CPC_POLL_EVENT");
+
+ if (event.type == CPC_EVENT_PAUSE) break;
+
+ ret = ioctl(kvm_dev, KVM_CPC_ACK_EVENT, &event.id);
+ if (ret) err(1, "KVM_CPC_ACK_EVENT");
+ }
+
+ arg = false;
+ ret = ioctl(kvm_dev, KVM_CPC_CALC_BASELINE, &arg);
+ if (ret) err(1, "KVM_CPC_CALC_BASELINE");
+
+ arg = true;
+ ret = ioctl(kvm_dev, KVM_CPC_APPLY_BASELINE, &arg);
+ if (ret) err(1, "KVM_CPC_APPLY_BASELINE");
+
+ ret = ioctl(kvm_dev, KVM_CPC_READ_BASELINE, baseline);
+ if (ret) err(1, "KVM_CPC_READ_BASELINE");
+
+ printf("\nBaseline:\n");
+ print_counts(baseline);
+ printf("\n");
+ print_counts_raw(baseline);
+ printf("\n\n");
+
+ memset(&cfg, 0, sizeof(&cfg));
+ cfg.mode = CPC_TRACK_NONE;
+ ret = ioctl(kvm_dev, KVM_CPC_TRACK_MODE, &cfg);
+ if (ret) err(1, "KVM_CPC_TRACK_MODE");
+
+ ret = ioctl(kvm_dev, KVM_CPC_ACK_EVENT, &event.id);
+ if (ret) err(1, "KVM_CPC_ACK_EVENT");
+
+ /* wait until guest program is run */
+ printf("Press enter to continue..\n");
+ getchar();
+
+ arg = true;
+ ret = ioctl(kvm_dev, KVM_CPC_BATCH_EVENTS, &arg);
+ if (ret) err(1, "KVM_CPC_BATCH_EVENTS");
+
+ memset(&cfg, 0, sizeof(cfg));
+ cfg.mode = CPC_TRACK_PAGES;
+ ret = ioctl(kvm_dev, KVM_CPC_TRACK_MODE, &cfg);
+ if (ret) err(1, "KVM_CPC_TRACK_MODE");
+
+ batch.cnt = 0;
+ batch.maxcnt = CPC_EVENT_BATCH_MAX;
+ batch.buf = malloc(sizeof(struct cpc_event) * batch.maxcnt);
+ if (!batch.buf) err(1, "malloc");
+
+ first_guest_event = true;
+ while (1) {
+ ret = ioctl(kvm_dev, KVM_CPC_POLL_EVENT, &event);
+ if (ret && errno == EAGAIN) continue;
+ if (ret) err(1, "KVM_CPC_POLL_EVENT");
+
+ if (event.type == CPC_EVENT_GUEST) {
+ read_batch();
+ printf("Guest %s\n",
+ !event.guest.type ? "start" : "stop");
+ }
+
+ if (event.type == CPC_EVENT_GUEST
+ && event.guest.type == CPC_GUEST_START_TRACK) {
+ if (!first_guest_event)
+ break;
+ first_guest_event = false;
+ }
+
+ if (event.type == CPC_EVENT_BATCH)
+ read_batch();
+
+ ret = ioctl(kvm_dev, KVM_CPC_ACK_EVENT, &event.id);
+ if (ret) err(1, "KVM_CPC_ACK_EVENT");
+ }
+
+ read_batch();
+
+ if (!batch.cnt) errx(1, "empty batch buffer");
+ memset(&cfg, 0, sizeof(cfg));
+ cfg.mode = CPC_TRACK_STEPS;
+ cfg.steps.target_gfn = last_user_inst_gfn;
+ cfg.steps.target_user = true;
+ cfg.steps.use_target = true;
+ cfg.steps.use_filter = true;
+ //cfg.steps.with_data = true;
+ ret = ioctl(kvm_dev, KVM_CPC_TRACK_MODE, &cfg);
+ if (ret) err(1, "KVM_CPC_TRACK_MODE");
+
+ ret = ioctl(kvm_dev, KVM_CPC_ACK_EVENT, &event.id);
+ if (ret) err(1, "KVM_CPC_ACK_EVENT");
+
+ printf("Target GFN: %08llx\n", cfg.steps.target_gfn);
+
+ while (monitor(false) != 2);
+ read_batch();
+
+ signal(SIGINT, NULL);
+
+ ret = ioctl(kvm_dev, KVM_CPC_RESET);
+ if (ret) err(1, "KVM_CPC_RESET");
+
+ free(batch.buf);
+
+ kvm_setup_deinit();
+}
+
diff --git a/test/qemu-targetstep_guest.c b/test/qemu-targetstep_guest.c
@@ -0,0 +1,50 @@
+#include "cachepc/uapi.h"
+
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <errno.h>
+#include <err.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main(int argc, const char **argv)
+{
+ void *buf;
+ int ret;
+
+ buf = NULL;
+ if (posix_memalign(&buf, L1_LINESIZE * L1_SETS, L1_LINESIZE * L1_SETS))
+ err(1, "memalign");
+ memset(buf, 0, L1_LINESIZE * L1_SETS);
+
+ errno = 0;
+ ret = setpriority(PRIO_PROCESS, 0, -20);
+ if (errno) err(1, "setpriority");
+ printf("NICE %i\n", ret);
+
+ while (1) {
+ printf("LOOP\n");
+ CPC_DO_VMMCALL(KVM_HC_CPC_VMMCALL_SIGNAL,
+ CPC_GUEST_START_TRACK, 0);
+ *(uint8_t *)(buf + L1_LINESIZE * 9) = 1;
+ *(uint8_t *)(buf + L1_LINESIZE * 10) = 1;
+ *(uint8_t *)(buf + L1_LINESIZE * 11) = 1;
+ *(uint8_t *)(buf + L1_LINESIZE * 12) = 1;
+ *(uint8_t *)(buf + L1_LINESIZE * 13) = 1;
+ *(uint8_t *)(buf + L1_LINESIZE * 14) = 1;
+ *(uint8_t *)(buf + L1_LINESIZE * 15) = 1;
+ *(uint8_t *)(buf + L1_LINESIZE * 9) = 1;
+ *(uint8_t *)(buf + L1_LINESIZE * 10) = 1;
+ *(uint8_t *)(buf + L1_LINESIZE * 11) = 1;
+ *(uint8_t *)(buf + L1_LINESIZE * 12) = 1;
+ *(uint8_t *)(buf + L1_LINESIZE * 13) = 1;
+ *(uint8_t *)(buf + L1_LINESIZE * 14) = 1;
+ *(uint8_t *)(buf + L1_LINESIZE * 15) = 1;
+ CPC_DO_VMMCALL(KVM_HC_CPC_VMMCALL_SIGNAL,
+ CPC_GUEST_STOP_TRACK, 0);
+ }
+}