[Devel] [PATCH hci-8.1 3/7] selftests/mm: cover large, swapped and partly marked THP ranges

Konstantin Khorenko khorenko at virtuozzo.com
Wed Oct 14 20:15:01 MSK 2026


Add cases for the kinds of memory a preserved range can hold:

- A 1 GB range touched every 2 MB, large enough for vma_dup() to split
  the copy into several padata jobs with CONFIG_PADATA.
- A range pushed to swap with MADV_PAGEOUT, so that the copy takes swap
  entries and the new image swaps the pages back in. Skipped when none
  of the pages reaches swap.
- A THP of which only the first half is marked, which splits its PMD
  and makes the copy batch PTEs of a large folio. Every page of the
  second half must be absent in the new image. Skipped unless smaps
  shows the range mapped by a THP before marking.

Both base page cases tolerate EINVAL from MADV_NOHUGEPAGE, which a
kernel without THP returns.

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 | 132 +++++++++++++++++++++++++++++++++---
 1 file changed, 124 insertions(+), 8 deletions(-)

diff --git a/tools/testing/selftests/mm/doexec.c b/tools/testing/selftests/mm/doexec.c
index 5eaf752b8413c..ab1d461de5517 100644
--- a/tools/testing/selftests/mm/doexec.c
+++ b/tools/testing/selftests/mm/doexec.c
@@ -56,6 +56,9 @@
 #ifndef MADV_WIPEONFORK
 #define MADV_WIPEONFORK 18
 #endif
+#ifndef MADV_PAGEOUT
+#define MADV_PAGEOUT 21
+#endif
 
 /* The downstream ELF loader requires this opt-in in a PT_NOTE segment. */
 asm(".pushsection .note.doexec,\"a\",%note\n"
@@ -642,29 +645,35 @@ static int doexec_noexec(void)
 	return ret;
 }
 
-static bool smaps_has_flag(char *addr, const char *flag)
+/* Copy the line of the smaps entry of the VMA at @addr that starts with @prefix. */
+static bool smaps_line(char *addr, const char *prefix, char *line, size_t len)
 {
 	FILE *file = fopen("/proc/self/smaps", "r");
-	char line[256], want[32];
 	bool in_vma = false, found = false;
+	char want[32];
 
 	if (!file)
 		return false;
 	snprintf(want, sizeof(want), "%lx-", (unsigned long)addr);
-	while (fgets(line, sizeof(line), file)) {
+	while (!found && fgets(line, len, file)) {
 		char *space = strchr(line, ' ');
 
-		if (!space)
-			continue;
-		if (memchr(line, '-', space - line) && !memchr(line, ':', space - line))
+		if (space && memchr(line, '-', space - line) && !memchr(line, ':', space - line))
 			in_vma = !strncmp(line, want, strlen(want));
-		else if (in_vma && !strncmp(line, "VmFlags:", 8))
-			found = strstr(line, flag) != NULL;
+		else if (in_vma)
+			found = !strncmp(line, prefix, strlen(prefix));
 	}
 	fclose(file);
 	return found;
 }
 
+static bool smaps_has_flag(char *addr, const char *flag)
+{
+	char line[512];
+
+	return smaps_line(addr, "VmFlags:", line, sizeof(line)) && strstr(line, flag);
+}
+
 static int after_pfnmap_exec(char *bar, size_t size)
 {
 	size_t page = sysconf(_SC_PAGESIZE), i;
@@ -1704,6 +1713,109 @@ static int stack_overlap_exec(void)
 }
 #endif
 
+/* Large enough for vma_dup() to split the copy between several threads. */
+static int sparse_exec(void)
+{
+	const size_t size = 1ul << 30, stride = 2ul << 20;
+	char *ram = mmap(NULL, size, PROT_READ | PROT_WRITE,
+			 MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
+	size_t i;
+
+	if (ram == MAP_FAILED)
+		return KSFT_SKIP;
+	/* EINVAL: no THP support, the range is mapped with base pages anyway. */
+	if (madvise(ram, size, MADV_NOHUGEPAGE) && errno != EINVAL)
+		return KSFT_FAIL;
+	for (i = 0; i < size; i += stride)
+		ram[i] = 0x31;
+	if (madvise(ram, size, MADV_DOEXEC))
+		return unsupported();
+	check_exec(ram, size, stride, false);
+	return KSFT_FAIL;
+}
+
+static bool page_swapped(char *addr)
+{
+	size_t page = sysconf(_SC_PAGESIZE);
+	int fd = open("/proc/self/pagemap", O_RDONLY);
+	uint64_t entry = 0;
+
+	if (fd < 0)
+		return false;
+	if (pread(fd, &entry, sizeof(entry), (uintptr_t)addr / page * sizeof(entry)) !=
+	    sizeof(entry))
+		entry = 0;
+	close(fd);
+	return entry & (1ull << 62);
+}
+
+/* Swap entries are copied, and the new image swaps the pages back in. */
+static int swapped_exec(void)
+{
+	size_t page = sysconf(_SC_PAGESIZE), size = 64 * page, i;
+	char *ram = mmap(NULL, size, PROT_READ | PROT_WRITE,
+			 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+	if (ram == MAP_FAILED ||
+	    (madvise(ram, size, MADV_NOHUGEPAGE) && errno != EINVAL))
+		return KSFT_FAIL;
+	memset(ram, 0x31, size);
+	if (madvise(ram, size, MADV_PAGEOUT))
+		return KSFT_SKIP;
+	for (i = 0; i < size && !page_swapped(ram + i); i += page)
+		;
+	if (i == size)
+		return KSFT_SKIP;
+	if (madvise(ram, size, MADV_DOEXEC))
+		return unsupported();
+	check_exec(ram, size, page, false);
+	return KSFT_FAIL;
+}
+
+static unsigned long smaps_kb(char *addr, const char *field)
+{
+	char line[256];
+
+	if (!smaps_line(addr, field, line, sizeof(line)))
+		return 0;
+	return strtoul(line + strlen(field), NULL, 10);
+}
+
+/* Marking half of a THP splits its PMD; the PTE-mapped half carries over. */
+static int thp_half_exec(void)
+{
+	size_t size = huge_page_size();
+	char *area, *ram;
+
+	if (!size)
+		return KSFT_SKIP;
+	area = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE,
+		    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	if (area == MAP_FAILED)
+		return KSFT_FAIL;
+	ram = (char *)(((uintptr_t)area + size - 1) & ~(size - 1));
+	if (madvise(ram, size, MADV_HUGEPAGE))
+		return KSFT_SKIP;
+	memset(ram, 0x31, size);
+	madvise(ram, size, MADV_COLLAPSE);
+	if (smaps_kb(ram, "AnonHugePages:") < size >> 10)
+		return KSFT_SKIP;
+	if (madvise(ram, size / 2, MADV_DOEXEC))
+		return unsupported();
+	exec_mode("/proc/self/exe", "--thp-half", ram, size);
+	return KSFT_FAIL;
+}
+
+static int after_thp_half(char *ram, size_t size)
+{
+	size_t page = sysconf(_SC_PAGESIZE), off;
+
+	for (off = size / 2; off < size; off += page)
+		if (range_mapped(ram + off, page))
+			return fail("unmarked half preserved");
+	return check_contents(ram, size / 2, 0x31);
+}
+
 struct after_mode {
 	const char *mode;
 	int (*fn)(char *ram, size_t size);
@@ -1719,6 +1831,7 @@ static const struct after_mode after_modes[] = {
 	{ "--wipeonfork", after_wipeonfork },
 	{ "--no-uffd", after_no_uffd },
 	{ "--mempolicy", after_mempolicy },
+	{ "--thp-half", after_thp_half },
 };
 
 struct more_case {
@@ -1742,6 +1855,9 @@ static const struct more_case more_cases[] = {
 	{ "opt-in note not in the last PT_NOTE: preserved", note_not_last_exec },
 	{ "malformed opt-in note not in the last PT_NOTE: skipped", bad_note_not_last_exec },
 	{ "preserved range under the new stack: exec kills the task", stack_overlap_exec },
+	{ "1 GB sparse range: preserved", sparse_exec },
+	{ "swapped out pages: preserved", swapped_exec },
+	{ "half of a THP: preserved", thp_half_exec },
 };
 
 int main(int argc, char **argv)

-- 
2.43.0



More information about the Devel mailing list