[Devel] [PATCH hci-8.1 v2 8/9] selftests/mm: run repeated live updates through MADV_DOEXEC

Konstantin Khorenko khorenko at virtuozzo.com
Wed Oct 21 19:57:21 MSK 2026


Each doexec case preserves memory across one or two execs. A QEMU live
update repeats: the guest runs, QEMU execs itself keeping guest RAM, DMA
pins and device BARs, the guest runs on, and the next update follows.

Add a case that goes through a number of such generations. Guest RAM is
backed by THP, part of it has a long-term pin from gup_test, and BAR 0
of the DOEXEC_VFIO_DEVICE device is mapped when one is named.

In every generation the new image checks what the previous one wrote,
through the CPU and through the pin. Then several threads write a new
pattern into their stripes of the RAM and the BAR, the image marks both
again and execs with an idle thread still running, as QEMU does with its
paused vCPU threads. The last generation drops the pin.

Without gup_test (release kernels) or without a VFIO device the case
runs with the parts that are there.

https://virtuozzo.atlassian.net/browse/VSTOR-150234

Feature: oracle/mm: MADV_DOEXEC madvise() flag
Assisted-by: LLM
Signed-off-by: Konstantin Khorenko <khorenko at virtuozzo.com>
---
 tools/testing/selftests/mm/doexec.c | 178 ++++++++++++++++++++++++++++++++++++
 1 file changed, 178 insertions(+)

diff --git a/tools/testing/selftests/mm/doexec.c b/tools/testing/selftests/mm/doexec.c
index 25005e98fdd1d..7e73a98e4ec2a 100644
--- a/tools/testing/selftests/mm/doexec.c
+++ b/tools/testing/selftests/mm/doexec.c
@@ -2118,6 +2118,170 @@ static int vhost_kthread_exec(void)
 #endif
 }
 
+#define CHAIN_GENERATIONS	16
+#define CHAIN_THREADS		4
+
+/* What a live-updated QEMU hands to its next image: guest RAM, a DMA pin, a BAR. */
+struct chain {
+	char *ram, *bar;
+	size_t size, pin_size, bar_size;
+	int pin, gen;
+};
+
+struct chain_stripe {
+	struct chain *chain;
+	int index;
+};
+
+static unsigned char chain_byte(int gen)
+{
+	return 0x20 + gen;
+}
+
+static uint32_t chain_word(int gen, size_t page)
+{
+	return 0xc0000000 | gen << 16 | page;
+}
+
+/* One vCPU of the guest: rewrites its stripe of the RAM and of the BAR. */
+static void *chain_writer(void *arg)
+{
+	struct chain_stripe *stripe = arg;
+	struct chain *c = stripe->chain;
+	size_t page = sysconf(_SC_PAGESIZE), len = c->size / CHAIN_THREADS, i;
+
+	memset(c->ram + stripe->index * len, chain_byte(c->gen), len);
+	for (i = stripe->index; i < c->bar_size / page; i += CHAIN_THREADS)
+		__atomic_store_n((uint32_t *)(c->bar + i * page), chain_word(c->gen, i),
+				 __ATOMIC_RELAXED);
+	return NULL;
+}
+
+static int chain_check(struct chain *c, int gen)
+{
+	size_t page = sysconf(_SC_PAGESIZE), i;
+
+	if (c->pin >= 0 && check_pin(c->pin, c->ram, c->pin_size, chain_byte(gen)))
+		return fail("pinned part of the RAM differs");
+	if (check_bytes(c->ram, c->size, chain_byte(gen)))
+		return fail("RAM contents lost");
+	if (c->bar_size && (!smaps_has_flag(c->bar, " pf ") || !smaps_has_flag(c->bar, " sh ")))
+		return fail("BAR not a shared PFN map");
+	for (i = 0; i < c->bar_size / page; i++)
+		if (__atomic_load_n((uint32_t *)(c->bar + i * page), __ATOMIC_RELAXED) !=
+		    chain_word(gen, i))
+			return fail("BAR contents lost");
+	return KSFT_PASS;
+}
+
+static int chain_exec(struct chain *c)
+{
+	char args[7][32];
+	int pipe_fd[2];
+	pthread_t thread;
+
+	if (madvise(c->ram, c->size, MADV_DOEXEC))
+		return fail_errno("MADV_DOEXEC");
+	if (c->bar_size && madvise(c->bar, c->bar_size, MADV_DOEXEC))
+		return fail_errno("MADV_DOEXEC on the BAR");
+	snprintf(args[0], 32, "%llu", (unsigned long long)(uintptr_t)c->ram);
+	snprintf(args[1], 32, "%zu", c->size);
+	snprintf(args[2], 32, "%d", c->pin);
+	snprintf(args[3], 32, "%zu", c->pin_size);
+	snprintf(args[4], 32, "%llu", (unsigned long long)(uintptr_t)c->bar);
+	snprintf(args[5], 32, "%zu", c->bar_size);
+	snprintf(args[6], 32, "%d", c->gen);
+	/* Like a paused vCPU thread, it is still there when QEMU execs. */
+	if (pipe(pipe_fd) || pthread_create(&thread, NULL, blocked_thread, &pipe_fd[0]))
+		return fail("idle thread");
+	execl("/proc/self/exe", "doexec", "--chain", args[0], args[1], args[2], args[3],
+	      args[4], args[5], args[6], NULL);
+	return fail_errno("exec");
+}
+
+/* One generation: check what the last one left, work, then update again. */
+static int after_chain(struct chain *c)
+{
+	struct chain_stripe stripes[CHAIN_THREADS];
+	pthread_t threads[CHAIN_THREADS];
+	int i;
+
+	if (chain_check(c, c->gen - 1))
+		return KSFT_FAIL;
+	if (c->gen == CHAIN_GENERATIONS)
+		return c->pin >= 0 && ioctl(c->pin, PIN_LONGTERM_TEST_STOP) ?
+		       fail_errno("PIN_LONGTERM_TEST_STOP") : KSFT_PASS;
+	for (i = 0; i < CHAIN_THREADS; i++) {
+		stripes[i] = (struct chain_stripe) { c, i };
+		if (pthread_create(&threads[i], NULL, chain_writer, &stripes[i]))
+			return fail("pthread_create failed");
+	}
+	for (i = 0; i < CHAIN_THREADS; i++)
+		pthread_join(threads[i], NULL);
+	if (chain_check(c, c->gen))
+		return KSFT_FAIL;
+	c->gen++;
+	return chain_exec(c);
+}
+
+/* Guest RAM with THP, a long-term pin on it and a VFIO BAR if one is named. */
+static int live_update_chain(void)
+{
+	struct vfio_region_info info = {
+		.argsz = sizeof(info),
+		.index = VFIO_PCI_BAR0_REGION_INDEX,
+	};
+	size_t page = sysconf(_SC_PAGESIZE), huge = huge_page_size(), i;
+	const char *bdf = getenv("DOEXEC_VFIO_DEVICE");
+	struct chain c = { .pin = -1 };
+	struct pin_longterm_test pin;
+	char *area;
+	int dev;
+
+	c.size = huge ? 4 * huge : 64 * page;
+	area = mmap(NULL, 2 * c.size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	if (area == MAP_FAILED)
+		return fail_errno("mmap");
+	c.ram = (char *)(((uintptr_t)area + c.size - 1) & ~(c.size - 1));
+	if (huge && madvise(c.ram, c.size, MADV_HUGEPAGE))
+		return fail_errno("MADV_HUGEPAGE");
+	memset(c.ram, chain_byte(0), c.size);
+	if (madvise(c.ram, c.size, MADV_DOEXEC))
+		return unsupported();
+	c.pin = open("/sys/kernel/debug/gup_test", O_RDWR);
+	if (c.pin >= 0) {
+		c.pin_size = huge ? huge : 16 * page;
+		pin = (struct pin_longterm_test) {
+			.addr = (uintptr_t)c.ram,
+			.size = c.pin_size,
+			.flags = PIN_LONGTERM_TEST_FLAG_USE_WRITE,
+		};
+		if (ioctl(c.pin, PIN_LONGTERM_TEST_START, &pin))
+			return fail_errno("PIN_LONGTERM_TEST_START");
+	}
+	if (bdf) {
+		dev = vfio_device_fd(bdf);
+		if (dev < 0 || ioctl(dev, VFIO_DEVICE_GET_REGION_INFO, &info)) {
+			ksft_print_msg("VFIO device %s: %s\n", bdf, strerror(errno));
+			return KSFT_FAIL;
+		}
+		if (!(info.flags & VFIO_REGION_INFO_FLAG_MMAP) || info.size < page) {
+			ksft_print_msg("VFIO device %s: BAR 0 cannot be mapped\n", bdf);
+			return KSFT_FAIL;
+		}
+		c.bar_size = info.size < (4ul << 20) ? info.size : 4ul << 20;
+		c.bar = mmap(NULL, c.bar_size, PROT_READ | PROT_WRITE, MAP_SHARED, dev,
+			     info.offset);
+		if (c.bar == MAP_FAILED)
+			return fail_errno("mmap of the BAR");
+		for (i = 0; i < c.bar_size / page; i++)
+			__atomic_store_n((uint32_t *)(c.bar + i * page), chain_word(0, i),
+					 __ATOMIC_RELAXED);
+	}
+	c.gen = 1;
+	return chain_exec(&c);
+}
+
 struct more_case {
 	const char *name;
 	int (*fn)(void);
@@ -2173,6 +2337,7 @@ static const struct more_case more_cases[] = {
 	{ "exec permission back in a fork child and the new image", mayexec_exec },
 	{ "/proc/pid/mem write stalled across exec: lands in the old mm", old_mm_write_exec },
 	{ "vhost owner in kthread mode: preserving exec refused", vhost_kthread_exec },
+	{ "repeated live updates: RAM, DMA pin and BAR kept", live_update_chain },
 };
 
 int main(int argc, char **argv)
@@ -2199,6 +2364,19 @@ int main(int argc, char **argv)
 						    strtoull(argv[3], NULL, 10));
 	if (argc == 2 && !strcmp(argv[1], "--euid"))
 		return geteuid() ? KSFT_SKIP : KSFT_PASS;
+	if (argc == 9 && !strcmp(argv[1], "--chain")) {
+		struct chain c = {
+			.ram = (char *)(uintptr_t)strtoull(argv[2], NULL, 10),
+			.size = strtoull(argv[3], NULL, 10),
+			.pin = atoi(argv[4]),
+			.pin_size = strtoull(argv[5], NULL, 10),
+			.bar = (char *)(uintptr_t)strtoull(argv[6], NULL, 10),
+			.bar_size = strtoull(argv[7], NULL, 10),
+			.gen = atoi(argv[8]),
+		};
+
+		return after_chain(&c);
+	}
 	if (argc == 3 && !strcmp(argv[1], "--pfnmap-gone"))
 		return after_pfnmap_gone((char *)(uintptr_t)strtoull(argv[2], NULL, 10));
 	if (argc == 4 && !strcmp(argv[1], "--pfnmap"))

-- 
2.43.0



More information about the Devel mailing list