1001 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1001 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #define _GNU_SOURCE
 | |
| #include <sys/types.h>
 | |
| #include <sys/stat.h>
 | |
| #include <sys/mman.h>
 | |
| #include <unistd.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <errno.h>
 | |
| #include <assert.h>
 | |
| #include <string.h>
 | |
| #include <fcntl.h>
 | |
| #include "test.h"
 | |
| 
 | |
| // ============================================================================
 | |
| // Helper macros
 | |
| // ============================================================================
 | |
| 
 | |
| #define KB                      (1024UL)
 | |
| #define MB                      (1024 * 1024UL)
 | |
| #define PAGE_SIZE               (4 * KB)
 | |
| 
 | |
| #define ALIGN_DOWN(x, a)        ((x) & ~(a-1)) // a must be a power of two
 | |
| #define ALIGN_UP(x, a)          ALIGN_DOWN((x+(a-1)), (a))
 | |
| 
 | |
| #define MAX_MMAP_USED_MEMORY    (4 * MB)
 | |
| 
 | |
| // ============================================================================
 | |
| // Helper functions
 | |
| // ============================================================================
 | |
| 
 | |
| static int fill_file_with_repeated_bytes(int fd, size_t len, int byte_val) {
 | |
|     char buf[PAGE_SIZE];
 | |
|     memset(buf, byte_val, sizeof(buf));
 | |
| 
 | |
|     size_t remain_bytes = len;
 | |
|     while (remain_bytes > 0) {
 | |
|         int to_write_bytes = MIN(sizeof(buf), remain_bytes);
 | |
|         int written_bytes = write(fd, buf, to_write_bytes);
 | |
|         if (written_bytes != to_write_bytes) {
 | |
|             THROW_ERROR("file write failed");
 | |
|         }
 | |
|         remain_bytes -= written_bytes;
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static int check_bytes_in_buf(char *buf, size_t len, int expected_byte_val) {
 | |
|     for (size_t bi = 0; bi < len; bi++) {
 | |
|         if (buf[bi] != (char)expected_byte_val) {
 | |
|             printf("check_bytes_in_buf: expect %02X, but found %02X, at offset %lu\n",
 | |
|                    (unsigned char)expected_byte_val, (unsigned char)buf[bi], bi);
 | |
|             return -1;
 | |
|         }
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static void *get_a_stack_ptr() {
 | |
|     volatile int a = 0;
 | |
|     return (void *) &a;
 | |
| }
 | |
| 
 | |
| // ============================================================================
 | |
| // Test suite initialization
 | |
| // ============================================================================
 | |
| 
 | |
| // Get a valid range of address hints for mmap
 | |
| static int get_a_valid_range_of_hints(size_t *hint_begin, size_t *hint_end) {
 | |
|     size_t big_buf_len = MAX_MMAP_USED_MEMORY;
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
|     void *big_buf = mmap(NULL, big_buf_len, prot, flags, -1, 0);
 | |
|     if (big_buf == MAP_FAILED) {
 | |
|         THROW_ERROR("mmap failed");
 | |
|     }
 | |
|     int ret = munmap(big_buf, big_buf_len);
 | |
|     if (ret < 0) {
 | |
|         THROW_ERROR("munmap failed");
 | |
|     }
 | |
|     *hint_begin = (size_t)big_buf;
 | |
|     *hint_end = *hint_begin + big_buf_len;
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static size_t HINT_BEGIN, HINT_END;
 | |
| 
 | |
| int test_suite_init() {
 | |
|     if (get_a_valid_range_of_hints(&HINT_BEGIN, &HINT_END) < 0) {
 | |
|         THROW_ERROR("get_a_valid_range_of_hints failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| // ============================================================================
 | |
| // Test cases for anonymous mmap
 | |
| // ============================================================================
 | |
| 
 | |
| int test_anonymous_mmap() {
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
| 
 | |
|     for (size_t len = PAGE_SIZE; len <= MAX_MMAP_USED_MEMORY; len *= 2) {
 | |
|         void *buf = mmap(NULL, len, prot, flags, -1, 0);
 | |
|         if (buf == MAP_FAILED) {
 | |
|             THROW_ERROR("mmap failed");
 | |
|         }
 | |
| 
 | |
|         if (check_bytes_in_buf(buf, len, 0) < 0) {
 | |
|             THROW_ERROR("the buffer is not initialized to zeros");
 | |
|         }
 | |
| 
 | |
|         int ret = munmap(buf, len);
 | |
|         if (ret < 0) {
 | |
|             THROW_ERROR("munmap failed");
 | |
|         }
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_anonymous_mmap_randomly() {
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
| 
 | |
|     void *bufs[16] = {NULL};
 | |
|     size_t lens[16];
 | |
|     size_t num_bufs = 0;
 | |
|     size_t used_memory = 0;
 | |
| 
 | |
|     for (int i = 0; i < 5; i++) {
 | |
|         // Phrase 1: do mmap with random sizes until no more buffers or memory
 | |
|         for (num_bufs = 0;
 | |
|                 num_bufs < ARRAY_SIZE(bufs) && used_memory < MAX_MMAP_USED_MEMORY;
 | |
|                 num_bufs++) {
 | |
|             // Choose the mmap size randomly
 | |
|             size_t len = rand() % (MAX_MMAP_USED_MEMORY - used_memory) + 1;
 | |
|             len = ALIGN_UP(len, PAGE_SIZE);
 | |
| 
 | |
|             // Do mmap
 | |
|             void *buf = mmap(NULL, len, prot, flags, -1, 0);
 | |
|             if (buf == MAP_FAILED) {
 | |
|                 THROW_ERROR("mmap failed");
 | |
|             }
 | |
|             bufs[num_bufs] = buf;
 | |
|             lens[num_bufs] = len;
 | |
| 
 | |
|             // Update memory usage
 | |
|             used_memory += len;
 | |
|         }
 | |
| 
 | |
|         // Phrase 2: do munmap to free all memory mapped memory
 | |
|         for (int bi = 0; bi < num_bufs; bi++) {
 | |
|             void *buf = bufs[bi];
 | |
|             size_t len = lens[bi];
 | |
|             int ret = munmap(buf, len);
 | |
|             if (ret < 0) {
 | |
|                 THROW_ERROR("munmap failed");
 | |
|             }
 | |
| 
 | |
|             bufs[bi] = NULL;
 | |
|             lens[bi] = 0;
 | |
|         }
 | |
| 
 | |
|         num_bufs = 0;
 | |
|         used_memory = 0;
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_anonymous_mmap_randomly_with_good_hints() {
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
|     for (int i = 0; i < 10; i++) {
 | |
|         size_t hint = HINT_BEGIN + rand() % (HINT_END - HINT_BEGIN);
 | |
|         hint = ALIGN_DOWN(hint, PAGE_SIZE);
 | |
| 
 | |
|         size_t len = rand() % (HINT_END - (size_t)hint);
 | |
|         len = ALIGN_UP(len + 1, PAGE_SIZE);
 | |
| 
 | |
|         void *addr = mmap((void *)hint, len, prot, flags, -1, 0);
 | |
|         if (addr != (void *)hint) {
 | |
|             THROW_ERROR("mmap with hint failed");
 | |
|         }
 | |
| 
 | |
|         int ret = munmap(addr, len);
 | |
|         if (ret < 0) {
 | |
|             THROW_ERROR("munmap failed");
 | |
|         }
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_anonymous_mmap_with_bad_hints() {
 | |
|     size_t bad_hints[] = {
 | |
|         PAGE_SIZE, // too low!
 | |
|         0xffff800000000000UL, // too high!
 | |
|         ALIGN_DOWN((size_t)get_a_stack_ptr(), PAGE_SIZE), // overlapped with stack!
 | |
|         HINT_BEGIN + 123, // within the valid range, not page aligned!
 | |
|     };
 | |
|     int len = PAGE_SIZE;
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
|     for (int hi = 0; hi < ARRAY_SIZE(bad_hints); hi++) {
 | |
|         void *bad_hint = (void *)bad_hints[hi];
 | |
|         void *addr = mmap(bad_hint, len, prot, flags, -1, 0);
 | |
|         if (addr == MAP_FAILED) {
 | |
|             THROW_ERROR("mmap should have tolerated a bad hint");
 | |
|         }
 | |
|         if (addr == bad_hint) {
 | |
|             THROW_ERROR("mmap should not have accepted a bad hint");
 | |
|         }
 | |
|         int ret = munmap(addr, len);
 | |
|         if (ret < 0) {
 | |
|             THROW_ERROR("munmap failed");
 | |
|         }
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_anonymous_mmap_with_zero_len() {
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
|     int len = 0; // invalid!
 | |
|     void *buf = mmap(NULL, len, prot, flags, -1, 0);
 | |
|     if (buf != MAP_FAILED) {
 | |
|         THROW_ERROR("mmap with zero len should have been failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_anonymous_mmap_with_non_page_aligned_len() {
 | |
|     int len = PAGE_SIZE + 17; // length need not to be page aligned!
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
|     void *buf = mmap(NULL, len, prot, flags, -1, 0);
 | |
|     if (buf == MAP_FAILED) {
 | |
|         THROW_ERROR("mmap with non-page aligned len should have worked");
 | |
|     }
 | |
| 
 | |
|     // Even the length is not page aligned, the page mmaping is done in pages
 | |
|     if (check_bytes_in_buf(buf, ALIGN_UP(len, PAGE_SIZE), 0) < 0) {
 | |
|         THROW_ERROR("the buffer is not initialized to zeros");
 | |
|     }
 | |
| 
 | |
|     int ret = munmap(buf, len);
 | |
|     if (ret < 0) {
 | |
|         THROW_ERROR("munmap failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| // ============================================================================
 | |
| // Test cases for file-backed mmap
 | |
| // ============================================================================
 | |
| 
 | |
| int test_file_mmap() {
 | |
|     const char *file_path = "/root/mmap_file.data";
 | |
|     int fd = open(file_path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
 | |
|     if (fd < 0) {
 | |
|         THROW_ERROR("file creation failed");
 | |
|     }
 | |
|     int file_len = 12 * KB + 128;
 | |
|     int byte_val = 0xab;
 | |
|     fill_file_with_repeated_bytes(fd, file_len, byte_val);
 | |
|     close(fd);
 | |
| 
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE;
 | |
|     fd = open(file_path, O_RDONLY);
 | |
|     if (fd < 0) {
 | |
|         THROW_ERROR("file open failed");
 | |
|     }
 | |
|     off_t offset = 0;
 | |
|     for (size_t len = PAGE_SIZE; len <= file_len; len *= 2) {
 | |
|         char *buf = mmap(NULL, len, prot, flags, fd, offset);
 | |
|         if (buf == MAP_FAILED) {
 | |
|             THROW_ERROR("mmap failed");
 | |
|         }
 | |
| 
 | |
|         if (check_bytes_in_buf(buf, len, byte_val) < 0) {
 | |
|             THROW_ERROR("the buffer is not initialized according to the file");
 | |
|         }
 | |
| 
 | |
|         int ret = munmap(buf, len);
 | |
|         if (ret < 0) {
 | |
|             THROW_ERROR("munmap failed");
 | |
|         }
 | |
|     }
 | |
|     close(fd);
 | |
|     unlink(file_path);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_file_mmap_with_offset() {
 | |
|     const char *file_path = "/root/mmap_file.data";
 | |
|     int fd = open(file_path, O_CREAT | O_TRUNC | O_RDWR, 0644);
 | |
|     if (fd < 0) {
 | |
|         THROW_ERROR("file creation failed");
 | |
|     }
 | |
|     size_t first_len = 4 * KB + 47;
 | |
|     int first_val = 0xab;
 | |
|     fill_file_with_repeated_bytes(fd, first_len, first_val);
 | |
|     size_t second_len = 9 * KB - 47;
 | |
|     int second_val = 0xcd;
 | |
|     fill_file_with_repeated_bytes(fd, second_len, second_val);
 | |
|     size_t file_len = first_len + second_len;
 | |
| 
 | |
|     off_t offset = 4 * KB;
 | |
|     int len = file_len - offset + 1 * KB;
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE;
 | |
|     assert(offset <= first_len);
 | |
|     char *buf = mmap(NULL, len, prot, flags, fd, offset);
 | |
|     if (buf == MAP_FAILED) {
 | |
|         THROW_ERROR("mmap failed");
 | |
|     }
 | |
| 
 | |
|     char *buf_cursor = buf;
 | |
|     if (check_bytes_in_buf(buf_cursor, first_len - offset, first_val) < 0) {
 | |
|         THROW_ERROR("the buffer is not initialized according to the file");
 | |
|     }
 | |
|     buf_cursor += first_len - offset;
 | |
|     if (check_bytes_in_buf(buf_cursor, second_len, second_val) < 0) {
 | |
|         THROW_ERROR("the buffer is not initialized according to the file");
 | |
|     }
 | |
|     buf_cursor += second_len;
 | |
|     if (check_bytes_in_buf(buf_cursor, ALIGN_UP(len, PAGE_SIZE) - (buf_cursor - buf),
 | |
|                            0) < 0) {
 | |
|         THROW_ERROR("the remaining of the last page occupied by the buffer is not initialized to zeros");
 | |
|     }
 | |
| 
 | |
|     int ret = munmap(buf, len);
 | |
|     if (ret < 0) {
 | |
|         THROW_ERROR("munmap failed");
 | |
|     }
 | |
| 
 | |
|     close(fd);
 | |
|     unlink(file_path);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_file_mmap_with_invalid_fd() {
 | |
|     size_t len = PAGE_SIZE;
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE;
 | |
|     int fd = 1234; // invalid!
 | |
|     off_t offset = 0;
 | |
|     void *buf = mmap(NULL, len, prot, flags, fd, offset);
 | |
|     if (buf != MAP_FAILED) {
 | |
|         THROW_ERROR("file mmap with an invalid fd should have been failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_file_mmap_with_non_page_aligned_offset() {
 | |
|     const char *file_path = "/root/mmap_file.data";
 | |
|     int fd = open(file_path, O_CREAT | O_TRUNC | O_RDWR, 0644);
 | |
|     if (fd < 0) {
 | |
|         THROW_ERROR("file creation failed");
 | |
|     }
 | |
|     int file_len = 12 * KB + 128;
 | |
|     int byte_val = 0xab;
 | |
|     fill_file_with_repeated_bytes(fd, file_len, byte_val);
 | |
| 
 | |
|     size_t len = PAGE_SIZE;
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
|     off_t  offset = PAGE_SIZE + 127; // Invalid!
 | |
|     void *buf = mmap(NULL, len, prot, flags, fd, offset);
 | |
|     if (buf != MAP_FAILED) {
 | |
|         THROW_ERROR("mmap with non-page aligned len should have been failed");
 | |
|     }
 | |
| 
 | |
|     close(fd);
 | |
|     unlink(file_path);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| // TODO: what if offset > file size or offset + len > file size?
 | |
| 
 | |
| // ============================================================================
 | |
| // Test cases for fixed mmap
 | |
| // ============================================================================
 | |
| 
 | |
| int test_fixed_mmap_that_does_not_override_any_mmaping() {
 | |
|     size_t hint = HINT_BEGIN + (HINT_END - HINT_BEGIN) / 3;
 | |
|     hint = ALIGN_DOWN(hint, PAGE_SIZE);
 | |
|     size_t len = (HINT_END - HINT_BEGIN) / 3;
 | |
|     len = ALIGN_UP(len, PAGE_SIZE);
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED;
 | |
|     void *addr = mmap((void *)hint, len, prot, flags, -1, 0);
 | |
|     if (addr != (void *)hint) {
 | |
|         THROW_ERROR("mmap with fixed address failed");
 | |
|     }
 | |
| 
 | |
|     int ret = munmap(addr, len);
 | |
|     if (ret < 0) {
 | |
|         THROW_ERROR("munmap failed");
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_fixed_mmap_that_overrides_existing_mmaping() {
 | |
|     // We're about to allocate two buffers: parent_buf and child_buf.
 | |
|     // The child_buf will override a range of memory that has already
 | |
|     // been allocated to the parent_buf.
 | |
|     size_t parent_len = 10 * PAGE_SIZE;
 | |
|     size_t pre_child_len = 2 * PAGE_SIZE, post_child_len = 3 * PAGE_SIZE;
 | |
|     size_t child_len = parent_len - pre_child_len - post_child_len;
 | |
| 
 | |
|     // Allocate parent_buf
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
|     void *parent_buf = mmap(NULL, parent_len, prot, flags, -1, 0);
 | |
|     if (parent_buf == MAP_FAILED) {
 | |
|         THROW_ERROR("mmap for parent failed");
 | |
|     }
 | |
|     int parent_val = 0xab;
 | |
|     memset(parent_buf, parent_val, parent_len);
 | |
| 
 | |
|     // Allocate child_buf
 | |
|     void *child_buf = (char *)parent_buf + pre_child_len;
 | |
|     if (mmap(child_buf, child_len, prot, flags | MAP_FIXED, -1, 0) != child_buf) {
 | |
|         THROW_ERROR("mmap with fixed address failed");
 | |
|     }
 | |
| 
 | |
|     // Check that child_buf, which overrides parent_buf, is initialized to zeros
 | |
|     if (check_bytes_in_buf(child_buf, child_len, 0) < 0) {
 | |
|         THROW_ERROR("the content of child mmap memory is not initialized");
 | |
|     }
 | |
|     // Check that the rest of parent_buf are kept intact
 | |
|     if (check_bytes_in_buf((char *)child_buf - pre_child_len,
 | |
|                            pre_child_len, parent_val) < 0 ||
 | |
|             check_bytes_in_buf((char *)child_buf + child_len,
 | |
|                                post_child_len, parent_val) < 0) {
 | |
|         THROW_ERROR("the content of parent mmap memory is broken");
 | |
|     }
 | |
| 
 | |
|     // Deallocate parent_buf along with child_buf
 | |
|     int ret = munmap(parent_buf, parent_len);
 | |
|     if (ret < 0) {
 | |
|         THROW_ERROR("munmap failed");
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_fixed_mmap_with_non_page_aligned_addr() {
 | |
|     size_t hint = HINT_BEGIN + 123; // Not aligned!
 | |
|     size_t len = 1 * PAGE_SIZE;
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED;
 | |
|     void *addr = mmap((void *)hint, len, prot, flags, -1, 0);
 | |
|     if (addr != MAP_FAILED) {
 | |
|         THROW_ERROR("fixed mmap with non-page aligned hint should have failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| // ============================================================================
 | |
| // Test cases for munmap
 | |
| // ============================================================================
 | |
| 
 | |
| static int check_buf_is_munmapped(void *target_addr, size_t len) {
 | |
|     // The trivial case of zero-len meory region is considered as unmapped
 | |
|     if (len == 0) { return 0; }
 | |
| 
 | |
|     // If the target_addr is not already mmaped, it should succeed to use it as
 | |
|     // a hint for mmap.
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
|     void *real_addr = mmap(target_addr, len, prot, flags, -1, 0);
 | |
|     if (real_addr != target_addr) {
 | |
|         THROW_ERROR("address is already mmaped");
 | |
|     }
 | |
|     munmap(target_addr, len);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static int mmap_then_munmap(size_t mmap_len, ssize_t munmap_offset, size_t munmap_len) {
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED;
 | |
|     // Make sure that we are manipulating memory between [HINT_BEGIN, HINT_END)
 | |
|     void *mmap_addr = (void *)(munmap_offset >= 0 ? HINT_BEGIN : HINT_BEGIN - munmap_offset);
 | |
|     if (mmap(mmap_addr, mmap_len, prot, flags, -1, 0) != mmap_addr) {
 | |
|         THROW_ERROR("mmap failed");
 | |
|     }
 | |
| 
 | |
|     void *munmap_addr = (char *)mmap_addr + munmap_offset;
 | |
|     if (munmap(munmap_addr, munmap_len) < 0) {
 | |
|         THROW_ERROR("munmap failed");
 | |
|     }
 | |
|     if (check_buf_is_munmapped(munmap_addr, munmap_len) < 0) {
 | |
|         THROW_ERROR("munmap does not really free the memory");
 | |
|     }
 | |
| 
 | |
|     // Make sure that when this function returns, there are no memory mappings
 | |
|     // within [HINT_BEGIN, HINT_END)
 | |
|     if (munmap((void *)HINT_BEGIN, HINT_END - HINT_BEGIN) < 0) {
 | |
|         THROW_ERROR("munmap failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_munmap_whose_range_is_a_subset_of_a_mmap_region() {
 | |
|     size_t mmap_len = 4 * PAGE_SIZE;
 | |
|     ssize_t munmap_offset = 1 * PAGE_SIZE;
 | |
|     size_t munmap_len = 2 * PAGE_SIZE;
 | |
|     if (mmap_then_munmap(mmap_len, munmap_offset, munmap_len) < 0) {
 | |
|         THROW_ERROR("first mmap and then munmap failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_munmap_whose_range_is_a_superset_of_a_mmap_region() {
 | |
|     size_t mmap_len = 4 * PAGE_SIZE;
 | |
|     ssize_t munmap_offset = -2 * PAGE_SIZE;
 | |
|     size_t munmap_len = 7 * PAGE_SIZE;
 | |
|     if (mmap_then_munmap(mmap_len, munmap_offset, munmap_len) < 0) {
 | |
|         THROW_ERROR("first mmap and then munmap failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_munmap_whose_range_intersects_with_a_mmap_region() {
 | |
|     size_t mmap_len = 200 * PAGE_SIZE;
 | |
|     size_t munmap_offset = 100 * PAGE_SIZE + 10 * PAGE_SIZE;
 | |
|     size_t munmap_len = 4 * PAGE_SIZE;
 | |
|     if (mmap_then_munmap(mmap_len, munmap_offset, munmap_len) < 0) {
 | |
|         THROW_ERROR("first mmap and then munmap failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_munmap_whose_range_intersects_with_no_mmap_regions() {
 | |
|     size_t mmap_len = 1 * PAGE_SIZE;
 | |
|     size_t munmap_offset = 1 * PAGE_SIZE;
 | |
|     size_t munmap_len = 1 * PAGE_SIZE;
 | |
|     if (mmap_then_munmap(mmap_len, munmap_offset, munmap_len) < 0) {
 | |
|         THROW_ERROR("first mmap and then munmap failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_munmap_whose_range_intersects_with_multiple_mmap_regions() {
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
| 
 | |
|     size_t mmap_len1 = 100 * PAGE_SIZE;
 | |
|     void *mmap_addr1 = mmap(NULL, mmap_len1, prot, flags, -1, 0);
 | |
|     if (mmap_addr1 == MAP_FAILED) {
 | |
|         THROW_ERROR("mmap failed");
 | |
|     }
 | |
| 
 | |
|     size_t mmap_len2 = 12 * PAGE_SIZE;
 | |
|     void *mmap_addr2 = mmap(NULL, mmap_len2, prot, flags, -1, 0);
 | |
|     if (mmap_addr2 == MAP_FAILED) {
 | |
|         THROW_ERROR("mmap failed");
 | |
|     }
 | |
| 
 | |
|     size_t mmap_min = MIN((size_t)mmap_addr1, (size_t)mmap_addr2);
 | |
|     size_t mmap_max = MAX((size_t)mmap_addr1 + mmap_len1,
 | |
|                           (size_t)mmap_addr2 + mmap_len2);
 | |
| 
 | |
|     void *munmap_addr = (void *)mmap_min;
 | |
|     size_t munmap_len = mmap_max - mmap_min;
 | |
|     if (munmap(munmap_addr, munmap_len) < 0) {
 | |
|         THROW_ERROR("munmap failed");
 | |
|     }
 | |
|     if (check_buf_is_munmapped(munmap_addr, munmap_len) < 0) {
 | |
|         THROW_ERROR("munmap does not really free the memory");
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_munmap_with_null_addr() {
 | |
|     // Set the address for munmap to NULL!
 | |
|     //
 | |
|     // The man page of munmap states that "it is not an error if the indicated
 | |
|     // range does not contain any mapped pages". This is not considered as
 | |
|     // an error!
 | |
|     void *munmap_addr = NULL;
 | |
|     size_t munmap_len = PAGE_SIZE;
 | |
|     if (munmap(munmap_addr, munmap_len) < 0) {
 | |
|         THROW_ERROR("munmap failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_munmap_with_zero_len() {
 | |
|     void *munmap_addr = (void *)HINT_BEGIN;
 | |
|     // Set the length for munmap to 0! This is invalid!
 | |
|     size_t munmap_len = 0;
 | |
|     if (munmap(munmap_addr, munmap_len) == 0) {
 | |
|         THROW_ERROR("munmap with zero length should have failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_munmap_with_non_page_aligned_len() {
 | |
|     size_t mmap_len = 2 * PAGE_SIZE;
 | |
|     size_t munmap_offset = 0;
 | |
|     // Set the length for munmap to a non-page aligned value!
 | |
|     //
 | |
|     // The man page of munmap states that "the address addr must be a
 | |
|     // multiple of the page size (but length need not be). All pages
 | |
|     // containing a part of the indicated range are unmapped". So this is
 | |
|     // not considered as an error!
 | |
|     size_t munmap_len = 1 * PAGE_SIZE + 123;
 | |
|     if (mmap_then_munmap(mmap_len, munmap_offset, munmap_len) < 0) {
 | |
|         THROW_ERROR("first mmap and then munmap failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| // ============================================================================
 | |
| // Test cases for mremap
 | |
| // ============================================================================
 | |
| 
 | |
| int test_mremap() {
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
| 
 | |
|     for (size_t len = PAGE_SIZE; len < MAX_MMAP_USED_MEMORY; len *= 2) {
 | |
|         void *buf = mmap(NULL, len, prot, flags, -1, 0);
 | |
|         if (buf == MAP_FAILED) {
 | |
|             THROW_ERROR("mmap failed");
 | |
|         }
 | |
|         if (check_bytes_in_buf(buf, len, 0) < 0) {
 | |
|             THROW_ERROR("the buffer is not initialized to zeros");
 | |
|         }
 | |
| 
 | |
|         void *expand_buf = mremap(buf, len, 2 * len, MREMAP_MAYMOVE);
 | |
|         if (expand_buf == MAP_FAILED) {
 | |
|             THROW_ERROR("mremap with big size failed");
 | |
|         }
 | |
|         if (check_bytes_in_buf(expand_buf, len, 0) < 0) {
 | |
|             THROW_ERROR("the old part of expand buffer is not zero");
 | |
|         }
 | |
|         memset(expand_buf, 'a', len * 2);
 | |
| 
 | |
|         void *shrink_buf = mremap(expand_buf, 2 * len, len, 0);
 | |
|         if (shrink_buf == MAP_FAILED) {
 | |
|             THROW_ERROR("mmap with small size failed");
 | |
|         }
 | |
|         if (check_bytes_in_buf(shrink_buf, len, 'a') < 0) {
 | |
|             THROW_ERROR("the shrink buffer is not correct");
 | |
|         }
 | |
| 
 | |
|         int ret = munmap(shrink_buf, len);
 | |
|         if (ret < 0) {
 | |
|             THROW_ERROR("munmap failed");
 | |
|         }
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_mremap_subrange() {
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
| 
 | |
|     size_t len = PAGE_SIZE * 4;
 | |
|     void *buf = mmap(NULL, len, prot, flags, -1, 0);
 | |
|     if (buf == MAP_FAILED) {
 | |
|         THROW_ERROR("mmap failed");
 | |
|     }
 | |
|     if (check_bytes_in_buf(buf, len, 0) < 0) {
 | |
|         THROW_ERROR("the buffer is not initialized to zeros");
 | |
|     }
 | |
|     /* remap a subrange in the buffer */
 | |
|     void *new_part_buf = mremap(buf + len / 4, len / 4, len, MREMAP_MAYMOVE);
 | |
|     if (new_part_buf == MAP_FAILED) {
 | |
|         THROW_ERROR("mremap with subrange failed");
 | |
|     }
 | |
|     if (check_bytes_in_buf(new_part_buf, len / 4, 0) < 0) {
 | |
|         THROW_ERROR("the old part of buffer is not zero");
 | |
|     }
 | |
|     void *rear_buf = buf + len / 2;
 | |
|     /* now the length of rear buffer is (len / 2), remap the second part */
 | |
|     void *new_part_rear_buf = mremap(rear_buf + len / 4, len / 4, len, MREMAP_MAYMOVE);
 | |
|     if (new_part_rear_buf == MAP_FAILED) {
 | |
|         THROW_ERROR("mremap with rear subrange failed");
 | |
|     }
 | |
|     if (check_bytes_in_buf(new_part_rear_buf, len / 4, 0) < 0) {
 | |
|         THROW_ERROR("the old part of rear buffer is not zero");
 | |
|     }
 | |
|     int ret = munmap(buf, len / 4) || munmap(new_part_buf, len) ||
 | |
|               munmap(rear_buf, len / 4) || munmap(new_part_rear_buf, len);
 | |
|     if (ret < 0) {
 | |
|         THROW_ERROR("munmap failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| // FIXME: may cause segfault on Linux
 | |
| int test_mremap_with_fixed_addr() {
 | |
|     int prot = PROT_READ | PROT_WRITE;
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
| 
 | |
|     size_t len = PAGE_SIZE * 2;
 | |
|     void *buf = mmap(NULL, len, prot, flags, -1, 0);
 | |
|     if (buf == MAP_FAILED) {
 | |
|         THROW_ERROR("mmap failed");
 | |
|     }
 | |
|     if (check_bytes_in_buf(buf, len, 0) < 0) {
 | |
|         THROW_ERROR("the buffer is not initialized to zeros");
 | |
|     }
 | |
| 
 | |
|     void *new_addr = buf + len * 2;
 | |
|     void *new_buf = mremap(buf, len, len, MREMAP_FIXED, new_addr);
 | |
|     if (new_buf != MAP_FAILED || errno != EINVAL) {
 | |
|         THROW_ERROR("check mremap with invalid flags failed");
 | |
|     }
 | |
|     new_buf = mremap(buf, len, len, MREMAP_FIXED | MREMAP_MAYMOVE, buf);
 | |
|     if (new_buf != MAP_FAILED || errno != EINVAL) {
 | |
|         THROW_ERROR("check mremap with overlap addr failed");
 | |
|     }
 | |
|     new_buf = mremap(buf, len, len, MREMAP_FIXED | MREMAP_MAYMOVE, new_addr);
 | |
|     if (new_buf == MAP_FAILED) {
 | |
|         THROW_ERROR("mmap with a fixed address failed");
 | |
|     }
 | |
|     if (check_bytes_in_buf(new_buf, len, 0) < 0) {
 | |
|         THROW_ERROR("the new buffer is not zero");
 | |
|     }
 | |
|     int ret = munmap(new_buf, len);
 | |
|     if (ret < 0) {
 | |
|         THROW_ERROR("munmap failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| // ============================================================================
 | |
| // Test cases for mprotect
 | |
| // ============================================================================
 | |
| 
 | |
| int test_mprotect_once() {
 | |
|     // The memory permissions initially looks like below:
 | |
|     //
 | |
|     // Pages:            #0   #1   #2   #3
 | |
|     // -------------------------------------
 | |
|     // Memory perms:     [   ][   ][   ][   ]
 | |
|     size_t total_len = 4; // in pages
 | |
|     int init_prot = PROT_NONE;
 | |
| 
 | |
|     // The four settings for mprotect and its resulting memory perms.
 | |
|     //
 | |
|     // Pages:            #0   #1   #2   #3
 | |
|     // -------------------------------------
 | |
|     // Setting (i = 0):
 | |
|     //  mprotect:        [RW ][RW ][RW ][RW ]
 | |
|     //  result:          [RW ][RW ][RW ][RW ]
 | |
|     // Setting (i = 1):
 | |
|     //  mprotect:        [RW ]
 | |
|     //  result:          [RW ][   ][   ][   ]
 | |
|     // Setting (i = 2):
 | |
|     //  mprotect:                  [RW ][RW ]
 | |
|     //  result:          [   ][   ][RW ][RW ]
 | |
|     // Setting (i = 3):
 | |
|     //  mprotect:             [RW ][RW ]
 | |
|     //  result:          [   ][RW ][RW ][   ]
 | |
|     size_t lens[] = { 4, 1, 2, 2}; // in pages
 | |
|     size_t offsets[] = { 0, 0, 2, 1}; // in pages
 | |
|     for (int i = 0; i < ARRAY_SIZE(lens); i++) {
 | |
|         int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
|         void *buf = mmap(NULL, total_len * PAGE_SIZE, init_prot, flags, -1, 0);
 | |
|         if (buf == MAP_FAILED) {
 | |
|             THROW_ERROR("mmap failed");
 | |
|         }
 | |
| 
 | |
|         size_t len = lens[i] * PAGE_SIZE;
 | |
|         size_t offset = offsets[i] * PAGE_SIZE;
 | |
|         int prot = PROT_READ | PROT_WRITE;
 | |
|         void *tmp_buf = (char *)buf + offset;
 | |
|         int ret = mprotect(tmp_buf, len, prot);
 | |
|         if (ret < 0) {
 | |
|             THROW_ERROR("mprotect failed");
 | |
|         }
 | |
| 
 | |
|         ret = munmap(buf, total_len * PAGE_SIZE);
 | |
|         if (ret < 0) {
 | |
|             THROW_ERROR("munmap failed");
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_mprotect_twice() {
 | |
|     // The memory permissions initially looks like below:
 | |
|     //
 | |
|     // Pages:              #0   #1   #2   #3
 | |
|     // -------------------------------------
 | |
|     // Memory perms:       [   ][   ][   ][   ]
 | |
|     size_t total_len = 4; // in pages
 | |
|     int init_prot = PROT_NONE;
 | |
| 
 | |
|     // The four settings for mprotects and their results
 | |
|     //
 | |
|     // Pages:              #0   #1   #2   #3
 | |
|     // -------------------------------------
 | |
|     // Setting (i = 0):
 | |
|     //  mprotect (j = 0):  [RW ][RW ]
 | |
|     //  mprotect (j = 1):            [RW ][RW ]
 | |
|     //  result:            [RW ][RW ][RW ][RW ]
 | |
|     // Setting (i = 1):
 | |
|     //  mprotect (j = 0):       [RW ]
 | |
|     //  mprotect (j = 1):                 [RW ]
 | |
|     //  result:            [   ][RW ][   ][RW ]
 | |
|     // Setting (i = 2):
 | |
|     //  mprotect (j = 0):       [RW ][RW ]
 | |
|     //  mprotect (j = 1):       [ WX][ WX]
 | |
|     //  result:            [   ][ WX][ WX][  ]
 | |
|     // Setting (i = 3):
 | |
|     //  mprotect (j = 0):       [RW ][RW ]
 | |
|     //  mprotect (j = 1):       [   ]
 | |
|     //  result:            [   ][   ][RW ][   ]
 | |
|     size_t lens[][2] = {
 | |
|         { 2, 2 },
 | |
|         { 1, 1 },
 | |
|         { 2, 2 },
 | |
|         { 2, 1 }
 | |
|     }; // in pages
 | |
|     size_t offsets[][2] = {
 | |
|         { 0, 2 },
 | |
|         { 1, 3 },
 | |
|         { 1, 1 },
 | |
|         { 1, 1 }
 | |
|     }; // in pages
 | |
|     int prots[][2] = {
 | |
|         { PROT_READ | PROT_WRITE, PROT_READ | PROT_WRITE },
 | |
|         { PROT_READ | PROT_WRITE, PROT_READ | PROT_WRITE },
 | |
|         { PROT_READ | PROT_WRITE, PROT_WRITE | PROT_EXEC },
 | |
|         { PROT_READ | PROT_WRITE, PROT_NONE }
 | |
|     };
 | |
|     for (int i = 0; i < ARRAY_SIZE(lens); i++) {
 | |
|         int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
|         void *buf = mmap(NULL, total_len * PAGE_SIZE, init_prot, flags, -1, 0);
 | |
|         if (buf == MAP_FAILED) {
 | |
|             THROW_ERROR("mmap failed");
 | |
|         }
 | |
| 
 | |
|         for (int j = 0; j < 2; j++) {
 | |
|             size_t len = lens[i][j] * PAGE_SIZE;
 | |
|             size_t offset = offsets[i][j] * PAGE_SIZE;
 | |
|             int prot = prots[i][j];
 | |
|             void *tmp_buf = (char *)buf + offset;
 | |
|             int ret = mprotect(tmp_buf, len, prot);
 | |
|             if (ret < 0) {
 | |
|                 THROW_ERROR("mprotect failed");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         int ret = munmap(buf, total_len * PAGE_SIZE);
 | |
|         if (ret < 0) {
 | |
|             THROW_ERROR("munmap failed");
 | |
|         }
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_mprotect_triple() {
 | |
|     // The memory permissions initially looks like below:
 | |
|     //
 | |
|     // Pages:              #0   #1   #2   #3
 | |
|     // -------------------------------------
 | |
|     // Memory perms:       [RWX][RWX][RWX][RWX]
 | |
|     size_t total_len = 4; // in pages
 | |
|     int init_prot = PROT_READ | PROT_WRITE | PROT_EXEC;
 | |
| 
 | |
|     // The four settings for mprotects and their results
 | |
|     //
 | |
|     // Pages:              #0   #1   #2   #3
 | |
|     // -------------------------------------
 | |
|     // Setting (i = 0):
 | |
|     //  mprotect (j = 0):  [   ][   ]
 | |
|     //  mprotect (j = 1):                 [   ]
 | |
|     //  mprotect (j = 2):            [   ]
 | |
|     //  result:            [   ][   ][   ][   ]
 | |
|     size_t lens[][3] = {
 | |
|         { 2, 1, 1 },
 | |
|     }; // in pages
 | |
|     size_t offsets[][3] = {
 | |
|         { 0, 3, 2 },
 | |
|     }; // in pages
 | |
|     int prots[][3] = {
 | |
|         { PROT_NONE, PROT_NONE, PROT_NONE },
 | |
|     };
 | |
|     for (int i = 0; i < ARRAY_SIZE(lens); i++) {
 | |
|         int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
|         void *buf = mmap(NULL, total_len * PAGE_SIZE, init_prot, flags, -1, 0);
 | |
|         if (buf == MAP_FAILED) {
 | |
|             THROW_ERROR("mmap failed");
 | |
|         }
 | |
| 
 | |
|         for (int j = 0; j < 3; j++) {
 | |
|             size_t len = lens[i][j] * PAGE_SIZE;
 | |
|             size_t offset = offsets[i][j] * PAGE_SIZE;
 | |
|             int prot = prots[i][j];
 | |
|             void *tmp_buf = (char *)buf + offset;
 | |
|             int ret = mprotect(tmp_buf, len, prot);
 | |
|             if (ret < 0) {
 | |
|                 THROW_ERROR("mprotect failed");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         int ret = munmap(buf, total_len * PAGE_SIZE);
 | |
|         if (ret < 0) {
 | |
|             THROW_ERROR("munmap failed");
 | |
|         }
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_mprotect_with_zero_len() {
 | |
|     int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 | |
|     void *buf = mmap(NULL, PAGE_SIZE, PROT_NONE, flags, -1, 0);
 | |
|     if (buf == MAP_FAILED) {
 | |
|         THROW_ERROR("mmap failed");
 | |
|     }
 | |
| 
 | |
|     int ret = mprotect(buf, 0, PROT_NONE);
 | |
|     if (ret < 0) {
 | |
|         THROW_ERROR("mprotect failed");
 | |
|     }
 | |
| 
 | |
|     ret = munmap(buf, PAGE_SIZE);
 | |
|     if (ret < 0) {
 | |
|         THROW_ERROR("munmap failed");
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_mprotect_with_invalid_addr() {
 | |
|     int ret = mprotect(NULL, PAGE_SIZE, PROT_NONE);
 | |
|     if (ret == 0 || errno != ENOMEM) {
 | |
|         THROW_ERROR("using invalid addr should have failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int test_mprotect_with_invalid_prot() {
 | |
|     int invalid_prot = 0x1234; // invalid protection bits
 | |
|     void *valid_addr = &invalid_prot;
 | |
|     size_t valid_len = PAGE_SIZE;
 | |
|     int ret = mprotect(valid_addr, valid_len, invalid_prot);
 | |
|     if (ret == 0 || errno != EINVAL) {
 | |
|         THROW_ERROR("using invalid addr should have failed");
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| // ============================================================================
 | |
| // Test suite main
 | |
| // ============================================================================
 | |
| 
 | |
| static test_case_t test_cases[] = {
 | |
|     TEST_CASE(test_anonymous_mmap),
 | |
|     TEST_CASE(test_anonymous_mmap_randomly),
 | |
|     TEST_CASE(test_anonymous_mmap_randomly_with_good_hints),
 | |
|     TEST_CASE(test_anonymous_mmap_with_bad_hints),
 | |
|     TEST_CASE(test_anonymous_mmap_with_zero_len),
 | |
|     TEST_CASE(test_anonymous_mmap_with_non_page_aligned_len),
 | |
|     TEST_CASE(test_file_mmap),
 | |
|     TEST_CASE(test_file_mmap_with_offset),
 | |
|     TEST_CASE(test_file_mmap_with_invalid_fd),
 | |
|     TEST_CASE(test_file_mmap_with_non_page_aligned_offset),
 | |
|     TEST_CASE(test_fixed_mmap_that_does_not_override_any_mmaping),
 | |
|     TEST_CASE(test_fixed_mmap_that_overrides_existing_mmaping),
 | |
|     TEST_CASE(test_fixed_mmap_with_non_page_aligned_addr),
 | |
|     TEST_CASE(test_munmap_whose_range_is_a_subset_of_a_mmap_region),
 | |
|     TEST_CASE(test_munmap_whose_range_is_a_superset_of_a_mmap_region),
 | |
|     TEST_CASE(test_munmap_whose_range_intersects_with_a_mmap_region),
 | |
|     TEST_CASE(test_munmap_whose_range_intersects_with_no_mmap_regions),
 | |
|     TEST_CASE(test_munmap_whose_range_intersects_with_multiple_mmap_regions),
 | |
|     TEST_CASE(test_munmap_with_null_addr),
 | |
|     TEST_CASE(test_munmap_with_zero_len),
 | |
|     TEST_CASE(test_munmap_with_non_page_aligned_len),
 | |
|     TEST_CASE(test_mremap),
 | |
|     TEST_CASE(test_mremap_subrange),
 | |
|     TEST_CASE(test_mremap_with_fixed_addr),
 | |
|     TEST_CASE(test_mprotect_once),
 | |
|     TEST_CASE(test_mprotect_twice),
 | |
|     TEST_CASE(test_mprotect_triple),
 | |
|     TEST_CASE(test_mprotect_with_zero_len),
 | |
|     TEST_CASE(test_mprotect_with_invalid_addr),
 | |
|     TEST_CASE(test_mprotect_with_invalid_prot),
 | |
| };
 | |
| 
 | |
| int main() {
 | |
|     if (test_suite_init() < 0) {
 | |
|         THROW_ERROR("test_suite_init failed");
 | |
|     }
 | |
| 
 | |
|     return test_suite_run(test_cases, ARRAY_SIZE(test_cases));
 | |
| }
 |