Commit 6aacab30 authored by Lorenzo Stoakes's avatar Lorenzo Stoakes Committed by Andrew Morton
Browse files

tools/testing/vma: separate VMA userland tests into separate files

So far the userland VMA tests have been established as a rough expression
of what's been possible.

Adapt it into a more usable form by separating out tests and shared
helper functions.

Since we test functions that are declared statically in mm/vma.c, we make
use of the trick of #include'ing kernel C files directly.

In order for the tests to continue to function, we must therefore also
this way into the tests/ directory.

We try to keep as much shared logic actually modularised into a separate
compilation unit in shared.c, however the merge_existing() and
attach_vma() helpers rely on statically declared mm/vma.c functions so
these must be declared in main.c.

Link: https://lkml.kernel.org/r/a0455ccfe4fdcd1c962c64f76304f612e5662a4e.1769097829.git.lorenzo.stoakes@oracle.com


Signed-off-by: default avatarLorenzo Stoakes <lorenzo.stoakes@oracle.com>
Reviewed-by: default avatarLiam R. Howlett <Liam.Howlett@oracle.com>
Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
Cc: Barry Song <baohua@kernel.org>
Cc: David Hildenbrand <david@kernel.org>
Cc: Dev Jain <dev.jain@arm.com>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Zi Yan <ziy@nvidia.com>
Cc: Damien Le Moal <dlemoal@kernel.org>
Cc: "Darrick J. Wong" <djwong@kernel.org>
Cc: Jarkko Sakkinen <jarkko@kernel.org>
Cc: Yury Norov <ynorov@nvidia.com>
Cc: Chris Mason <clm@fb.com>
Cc: Pedro Falcato <pfalcato@suse.de>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
parent 53f1d936
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -6,10 +6,10 @@ default: vma

include ../shared/shared.mk

OFILES = $(SHARED_OFILES) vma.o maple-shim.o
OFILES = $(SHARED_OFILES) main.o shared.o maple-shim.o
TARGETS = vma

vma.o: vma.c vma_internal.h ../../../mm/vma.c ../../../mm/vma_init.c ../../../mm/vma_exec.c ../../../mm/vma.h
main.o: main.c shared.c shared.h vma_internal.h tests/merge.c tests/mmap.c tests/vma.c ../../../mm/vma.c ../../../mm/vma_init.c ../../../mm/vma_exec.c ../../../mm/vma.h

vma:	$(OFILES)
	$(CC) $(CFLAGS) -o $@ $(OFILES) $(LDLIBS)
+55 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-or-later

#include "shared.h"
/*
 * Directly import the VMA implementation here. Our vma_internal.h wrapper
 * provides userland-equivalent functionality for everything vma.c uses.
 */
#include "../../../mm/vma_init.c"
#include "../../../mm/vma_exec.c"
#include "../../../mm/vma.c"

/* Tests are included directly so they can test static functions in mm/vma.c. */
#include "tests/merge.c"
#include "tests/mmap.c"
#include "tests/vma.c"

/* Helper functions which utilise static kernel functions. */

struct vm_area_struct *merge_existing(struct vma_merge_struct *vmg)
{
	struct vm_area_struct *vma;

	vma = vma_merge_existing_range(vmg);
	if (vma)
		vma_assert_attached(vma);
	return vma;
}

int attach_vma(struct mm_struct *mm, struct vm_area_struct *vma)
{
	int res;

	res = vma_link(mm, vma);
	if (!res)
		vma_assert_attached(vma);
	return res;
}

/* Main test running which invokes tests/ *.c runners. */
int main(void)
{
	int num_tests = 0, num_fail = 0;

	maple_tree_init();
	vma_state_init();

	run_merge_tests(&num_tests, &num_fail);
	run_mmap_tests(&num_tests, &num_fail);
	run_vma_tests(&num_tests, &num_fail);

	printf("%d tests run, %d passed, %d failed.\n",
	       num_tests, num_tests - num_fail, num_fail);

	return num_fail == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
+131 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-or-later

#include "shared.h"


bool fail_prealloc;
unsigned long mmap_min_addr = CONFIG_DEFAULT_MMAP_MIN_ADDR;
unsigned long dac_mmap_min_addr = CONFIG_DEFAULT_MMAP_MIN_ADDR;
unsigned long stack_guard_gap = 256UL<<PAGE_SHIFT;

const struct vm_operations_struct vma_dummy_vm_ops;
struct anon_vma dummy_anon_vma;
struct task_struct __current;

struct vm_area_struct *alloc_vma(struct mm_struct *mm,
		unsigned long start, unsigned long end,
		pgoff_t pgoff, vm_flags_t vm_flags)
{
	struct vm_area_struct *vma = vm_area_alloc(mm);

	if (vma == NULL)
		return NULL;

	vma->vm_start = start;
	vma->vm_end = end;
	vma->vm_pgoff = pgoff;
	vm_flags_reset(vma, vm_flags);
	vma_assert_detached(vma);

	return vma;
}

void detach_free_vma(struct vm_area_struct *vma)
{
	vma_mark_detached(vma);
	vm_area_free(vma);
}

struct vm_area_struct *alloc_and_link_vma(struct mm_struct *mm,
		unsigned long start, unsigned long end,
		pgoff_t pgoff, vm_flags_t vm_flags)
{
	struct vm_area_struct *vma = alloc_vma(mm, start, end, pgoff, vm_flags);

	if (vma == NULL)
		return NULL;

	if (attach_vma(mm, vma)) {
		detach_free_vma(vma);
		return NULL;
	}

	/*
	 * Reset this counter which we use to track whether writes have
	 * begun. Linking to the tree will have caused this to be incremented,
	 * which means we will get a false positive otherwise.
	 */
	vma->vm_lock_seq = UINT_MAX;

	return vma;
}

void reset_dummy_anon_vma(void)
{
	dummy_anon_vma.was_cloned = false;
	dummy_anon_vma.was_unlinked = false;
}

int cleanup_mm(struct mm_struct *mm, struct vma_iterator *vmi)
{
	struct vm_area_struct *vma;
	int count = 0;

	fail_prealloc = false;
	reset_dummy_anon_vma();

	vma_iter_set(vmi, 0);
	for_each_vma(*vmi, vma) {
		detach_free_vma(vma);
		count++;
	}

	mtree_destroy(&mm->mm_mt);
	mm->map_count = 0;
	return count;
}

bool vma_write_started(struct vm_area_struct *vma)
{
	int seq = vma->vm_lock_seq;

	/* We reset after each check. */
	vma->vm_lock_seq = UINT_MAX;

	/* The vma_start_write() stub simply increments this value. */
	return seq > -1;
}

void __vma_set_dummy_anon_vma(struct vm_area_struct *vma,
		struct anon_vma_chain *avc, struct anon_vma *anon_vma)
{
	vma->anon_vma = anon_vma;
	INIT_LIST_HEAD(&vma->anon_vma_chain);
	list_add(&avc->same_vma, &vma->anon_vma_chain);
	avc->anon_vma = vma->anon_vma;
}

void vma_set_dummy_anon_vma(struct vm_area_struct *vma,
		struct anon_vma_chain *avc)
{
	__vma_set_dummy_anon_vma(vma, avc, &dummy_anon_vma);
}

struct task_struct *get_current(void)
{
	return &__current;
}

unsigned long rlimit(unsigned int limit)
{
	return (unsigned long)-1;
}

void vma_set_range(struct vm_area_struct *vma,
		   unsigned long start, unsigned long end,
		   pgoff_t pgoff)
{
	vma->vm_start = start;
	vma->vm_end = end;
	vma->vm_pgoff = pgoff;
}
+114 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

#include "generated/bit-length.h"
#include "maple-shared.h"
#include "vma_internal.h"
#include "../../../mm/vma.h"

/* Simple test runner. Assumes local num_[fail, tests] counters. */
#define TEST(name)							\
	do {								\
		(*num_tests)++;						\
		if (!test_##name()) {					\
			(*num_fail)++;					\
			fprintf(stderr, "Test " #name " FAILED\n");	\
		}							\
	} while (0)

#define ASSERT_TRUE(_expr)						\
	do {								\
		if (!(_expr)) {						\
			fprintf(stderr,					\
				"Assert FAILED at %s:%d:%s(): %s is FALSE.\n", \
				__FILE__, __LINE__, __FUNCTION__, #_expr); \
			return false;					\
		}							\
	} while (0)

#define ASSERT_FALSE(_expr) ASSERT_TRUE(!(_expr))
#define ASSERT_EQ(_val1, _val2) ASSERT_TRUE((_val1) == (_val2))
#define ASSERT_NE(_val1, _val2) ASSERT_TRUE((_val1) != (_val2))

#define IS_SET(_val, _flags) ((_val & _flags) == _flags)

extern bool fail_prealloc;

/* Override vma_iter_prealloc() so we can choose to fail it. */
#define vma_iter_prealloc(vmi, vma)					\
	(fail_prealloc ? -ENOMEM : mas_preallocate(&(vmi)->mas, (vma), GFP_KERNEL))

#define CONFIG_DEFAULT_MMAP_MIN_ADDR 65536

extern unsigned long mmap_min_addr;
extern unsigned long dac_mmap_min_addr;
extern unsigned long stack_guard_gap;

extern const struct vm_operations_struct vma_dummy_vm_ops;
extern struct anon_vma dummy_anon_vma;
extern struct task_struct __current;

/*
 * Helper function which provides a wrapper around a merge existing VMA
 * operation.
 *
 * Declared in main.c as uses static VMA function.
 */
struct vm_area_struct *merge_existing(struct vma_merge_struct *vmg);

/*
 * Helper function to allocate a VMA and link it to the tree.
 *
 * Declared in main.c as uses static VMA function.
 */
int attach_vma(struct mm_struct *mm, struct vm_area_struct *vma);

/* Helper function providing a dummy vm_ops->close() method.*/
static inline void dummy_close(struct vm_area_struct *)
{
}

/* Helper function to simply allocate a VMA. */
struct vm_area_struct *alloc_vma(struct mm_struct *mm,
		unsigned long start, unsigned long end,
		pgoff_t pgoff, vm_flags_t vm_flags);

/* Helper function to detach and free a VMA. */
void detach_free_vma(struct vm_area_struct *vma);

/* Helper function to allocate a VMA and link it to the tree. */
struct vm_area_struct *alloc_and_link_vma(struct mm_struct *mm,
		unsigned long start, unsigned long end,
		pgoff_t pgoff, vm_flags_t vm_flags);

/*
 * Helper function to reset the dummy anon_vma to indicate it has not been
 * duplicated.
 */
void reset_dummy_anon_vma(void);

/*
 * Helper function to remove all VMAs and destroy the maple tree associated with
 * a virtual address space. Returns a count of VMAs in the tree.
 */
int cleanup_mm(struct mm_struct *mm, struct vma_iterator *vmi);

/* Helper function to determine if VMA has had vma_start_write() performed. */
bool vma_write_started(struct vm_area_struct *vma);

void __vma_set_dummy_anon_vma(struct vm_area_struct *vma,
		struct anon_vma_chain *avc, struct anon_vma *anon_vma);

/* Provide a simple dummy VMA/anon_vma dummy setup for testing. */
void vma_set_dummy_anon_vma(struct vm_area_struct *vma,
			    struct anon_vma_chain *avc);

/* Helper function to specify a VMA's range. */
void vma_set_range(struct vm_area_struct *vma,
		   unsigned long start, unsigned long end,
		   pgoff_t pgoff);
+8 −324
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-or-later

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

#include "generated/bit-length.h"

#include "maple-shared.h"
#include "vma_internal.h"

/* Include so header guard set. */
#include "../../../mm/vma.h"

static bool fail_prealloc;

/* Then override vma_iter_prealloc() so we can choose to fail it. */
#define vma_iter_prealloc(vmi, vma)					\
	(fail_prealloc ? -ENOMEM : mas_preallocate(&(vmi)->mas, (vma), GFP_KERNEL))

#define CONFIG_DEFAULT_MMAP_MIN_ADDR 65536

unsigned long mmap_min_addr = CONFIG_DEFAULT_MMAP_MIN_ADDR;
unsigned long dac_mmap_min_addr = CONFIG_DEFAULT_MMAP_MIN_ADDR;
unsigned long stack_guard_gap = 256UL<<PAGE_SHIFT;

/*
 * Directly import the VMA implementation here. Our vma_internal.h wrapper
 * provides userland-equivalent functionality for everything vma.c uses.
 */
#include "../../../mm/vma_init.c"
#include "../../../mm/vma_exec.c"
#include "../../../mm/vma.c"

const struct vm_operations_struct vma_dummy_vm_ops;
static struct anon_vma dummy_anon_vma;

#define ASSERT_TRUE(_expr)						\
	do {								\
		if (!(_expr)) {						\
			fprintf(stderr,					\
				"Assert FAILED at %s:%d:%s(): %s is FALSE.\n", \
				__FILE__, __LINE__, __FUNCTION__, #_expr); \
			return false;					\
		}							\
	} while (0)
#define ASSERT_FALSE(_expr) ASSERT_TRUE(!(_expr))
#define ASSERT_EQ(_val1, _val2) ASSERT_TRUE((_val1) == (_val2))
#define ASSERT_NE(_val1, _val2) ASSERT_TRUE((_val1) != (_val2))

#define IS_SET(_val, _flags) ((_val & _flags) == _flags)

static struct task_struct __current;

struct task_struct *get_current(void)
{
	return &__current;
}

unsigned long rlimit(unsigned int limit)
{
	return (unsigned long)-1;
}

/* Helper function to simply allocate a VMA. */
static struct vm_area_struct *alloc_vma(struct mm_struct *mm,
					unsigned long start,
					unsigned long end,
					pgoff_t pgoff,
					vm_flags_t vm_flags)
{
	struct vm_area_struct *vma = vm_area_alloc(mm);

	if (vma == NULL)
		return NULL;

	vma->vm_start = start;
	vma->vm_end = end;
	vma->vm_pgoff = pgoff;
	vm_flags_reset(vma, vm_flags);
	vma_assert_detached(vma);

	return vma;
}

/* Helper function to allocate a VMA and link it to the tree. */
static int attach_vma(struct mm_struct *mm, struct vm_area_struct *vma)
{
	int res;

	res = vma_link(mm, vma);
	if (!res)
		vma_assert_attached(vma);
	return res;
}

static void detach_free_vma(struct vm_area_struct *vma)
{
	vma_mark_detached(vma);
	vm_area_free(vma);
}

/* Helper function to allocate a VMA and link it to the tree. */
static struct vm_area_struct *alloc_and_link_vma(struct mm_struct *mm,
						 unsigned long start,
						 unsigned long end,
						 pgoff_t pgoff,
						 vm_flags_t vm_flags)
{
	struct vm_area_struct *vma = alloc_vma(mm, start, end, pgoff, vm_flags);

	if (vma == NULL)
		return NULL;

	if (attach_vma(mm, vma)) {
		detach_free_vma(vma);
		return NULL;
	}

	/*
	 * Reset this counter which we use to track whether writes have
	 * begun. Linking to the tree will have caused this to be incremented,
	 * which means we will get a false positive otherwise.
	 */
	vma->vm_lock_seq = UINT_MAX;

	return vma;
}

/* Helper function which provides a wrapper around a merge new VMA operation. */
static struct vm_area_struct *merge_new(struct vma_merge_struct *vmg)
{
@@ -146,20 +19,6 @@ static struct vm_area_struct *merge_new(struct vma_merge_struct *vmg)
	return vma;
}

/*
 * Helper function which provides a wrapper around a merge existing VMA
 * operation.
 */
static struct vm_area_struct *merge_existing(struct vma_merge_struct *vmg)
{
	struct vm_area_struct *vma;

	vma = vma_merge_existing_range(vmg);
	if (vma)
		vma_assert_attached(vma);
	return vma;
}

/*
 * Helper function which provides a wrapper around the expansion of an existing
 * VMA.
@@ -173,7 +32,7 @@ static int expand_existing(struct vma_merge_struct *vmg)
 * Helper function to reset merge state the associated VMA iterator to a
 * specified new range.
 */
static void vmg_set_range(struct vma_merge_struct *vmg, unsigned long start,
void vmg_set_range(struct vma_merge_struct *vmg, unsigned long start,
		   unsigned long end, pgoff_t pgoff, vm_flags_t vm_flags)
{
	vma_iter_set(vmg->vmi, start);
@@ -211,9 +70,8 @@ static void vmg_set_range_anon_vma(struct vma_merge_struct *vmg, unsigned long s
 * VMA, link it to the maple tree and return it.
 */
static struct vm_area_struct *try_merge_new_vma(struct mm_struct *mm,
						struct vma_merge_struct *vmg,
						unsigned long start, unsigned long end,
						pgoff_t pgoff, vm_flags_t vm_flags,
		struct vma_merge_struct *vmg, unsigned long start,
		unsigned long end, pgoff_t pgoff, vm_flags_t vm_flags,
		bool *was_merged)
{
	struct vm_area_struct *merged;
@@ -234,72 +92,6 @@ static struct vm_area_struct *try_merge_new_vma(struct mm_struct *mm,
	return alloc_and_link_vma(mm, start, end, pgoff, vm_flags);
}

/*
 * Helper function to reset the dummy anon_vma to indicate it has not been
 * duplicated.
 */
static void reset_dummy_anon_vma(void)
{
	dummy_anon_vma.was_cloned = false;
	dummy_anon_vma.was_unlinked = false;
}

/*
 * Helper function to remove all VMAs and destroy the maple tree associated with
 * a virtual address space. Returns a count of VMAs in the tree.
 */
static int cleanup_mm(struct mm_struct *mm, struct vma_iterator *vmi)
{
	struct vm_area_struct *vma;
	int count = 0;

	fail_prealloc = false;
	reset_dummy_anon_vma();

	vma_iter_set(vmi, 0);
	for_each_vma(*vmi, vma) {
		detach_free_vma(vma);
		count++;
	}

	mtree_destroy(&mm->mm_mt);
	mm->map_count = 0;
	return count;
}

/* Helper function to determine if VMA has had vma_start_write() performed. */
static bool vma_write_started(struct vm_area_struct *vma)
{
	int seq = vma->vm_lock_seq;

	/* We reset after each check. */
	vma->vm_lock_seq = UINT_MAX;

	/* The vma_start_write() stub simply increments this value. */
	return seq > -1;
}

/* Helper function providing a dummy vm_ops->close() method.*/
static void dummy_close(struct vm_area_struct *)
{
}

static void __vma_set_dummy_anon_vma(struct vm_area_struct *vma,
				     struct anon_vma_chain *avc,
				     struct anon_vma *anon_vma)
{
	vma->anon_vma = anon_vma;
	INIT_LIST_HEAD(&vma->anon_vma_chain);
	list_add(&avc->same_vma, &vma->anon_vma_chain);
	avc->anon_vma = vma->anon_vma;
}

static void vma_set_dummy_anon_vma(struct vm_area_struct *vma,
				   struct anon_vma_chain *avc)
{
	__vma_set_dummy_anon_vma(vma, avc, &dummy_anon_vma);
}

static bool test_simple_merge(void)
{
	struct vm_area_struct *vma;
@@ -1616,39 +1408,6 @@ static bool test_merge_extend(void)
	return true;
}

static bool test_copy_vma(void)
{
	vm_flags_t vm_flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE;
	struct mm_struct mm = {};
	bool need_locks = false;
	VMA_ITERATOR(vmi, &mm, 0);
	struct vm_area_struct *vma, *vma_new, *vma_next;

	/* Move backwards and do not merge. */

	vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, vm_flags);
	vma_new = copy_vma(&vma, 0, 0x2000, 0, &need_locks);
	ASSERT_NE(vma_new, vma);
	ASSERT_EQ(vma_new->vm_start, 0);
	ASSERT_EQ(vma_new->vm_end, 0x2000);
	ASSERT_EQ(vma_new->vm_pgoff, 0);
	vma_assert_attached(vma_new);

	cleanup_mm(&mm, &vmi);

	/* Move a VMA into position next to another and merge the two. */

	vma = alloc_and_link_vma(&mm, 0, 0x2000, 0, vm_flags);
	vma_next = alloc_and_link_vma(&mm, 0x6000, 0x8000, 6, vm_flags);
	vma_new = copy_vma(&vma, 0x4000, 0x2000, 4, &need_locks);
	vma_assert_attached(vma_new);

	ASSERT_EQ(vma_new, vma_next);

	cleanup_mm(&mm, &vmi);
	return true;
}

static bool test_expand_only_mode(void)
{
	vm_flags_t vm_flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE;
@@ -1689,73 +1448,8 @@ static bool test_expand_only_mode(void)
	return true;
}

static bool test_mmap_region_basic(void)
{
	struct mm_struct mm = {};
	unsigned long addr;
	struct vm_area_struct *vma;
	VMA_ITERATOR(vmi, &mm, 0);

	current->mm = &mm;

	/* Map at 0x300000, length 0x3000. */
	addr = __mmap_region(NULL, 0x300000, 0x3000,
			     VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE,
			     0x300, NULL);
	ASSERT_EQ(addr, 0x300000);

	/* Map at 0x250000, length 0x3000. */
	addr = __mmap_region(NULL, 0x250000, 0x3000,
			     VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE,
			     0x250, NULL);
	ASSERT_EQ(addr, 0x250000);

	/* Map at 0x303000, merging to 0x300000 of length 0x6000. */
	addr = __mmap_region(NULL, 0x303000, 0x3000,
			     VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE,
			     0x303, NULL);
	ASSERT_EQ(addr, 0x303000);

	/* Map at 0x24d000, merging to 0x250000 of length 0x6000. */
	addr = __mmap_region(NULL, 0x24d000, 0x3000,
			     VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE,
			     0x24d, NULL);
	ASSERT_EQ(addr, 0x24d000);

	ASSERT_EQ(mm.map_count, 2);

	for_each_vma(vmi, vma) {
		if (vma->vm_start == 0x300000) {
			ASSERT_EQ(vma->vm_end, 0x306000);
			ASSERT_EQ(vma->vm_pgoff, 0x300);
		} else if (vma->vm_start == 0x24d000) {
			ASSERT_EQ(vma->vm_end, 0x253000);
			ASSERT_EQ(vma->vm_pgoff, 0x24d);
		} else {
			ASSERT_FALSE(true);
		}
	}

	cleanup_mm(&mm, &vmi);
	return true;
}

int main(void)
static void run_merge_tests(int *num_tests, int *num_fail)
{
	int num_tests = 0, num_fail = 0;

	maple_tree_init();
	vma_state_init();

#define TEST(name)							\
	do {								\
		num_tests++;						\
		if (!test_##name()) {					\
			num_fail++;					\
			fprintf(stderr, "Test " #name " FAILED\n");	\
		}							\
	} while (0)

	/* Very simple tests to kick the tyres. */
	TEST(simple_merge);
	TEST(simple_modify);
@@ -1771,15 +1465,5 @@ int main(void)
	TEST(dup_anon_vma);
	TEST(vmi_prealloc_fail);
	TEST(merge_extend);
	TEST(copy_vma);
	TEST(expand_only_mode);

	TEST(mmap_region_basic);

#undef TEST

	printf("%d tests run, %d passed, %d failed.\n",
	       num_tests, num_tests - num_fail, num_fail);

	return num_fail == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
Loading