The memory API provides virtual memory management, page table operations, and safe access to user space memory.
System Calls
Memory Allocation
addr_t sys_brk(addr_t new_brk);
Adjusts the program break (end of the heap).
New heap end address (0 to query current break)
Current or new break address on success, or previous break on error
Example:
// Query current break
addr_t current_brk = sys_brk(0);
// Extend heap by 4KB
addr_t new_brk = sys_brk(current_brk + 0x1000);
if (new_brk != current_brk + 0x1000) {
// Error: couldn't allocate
}
Memory Mapping
addr_t sys_mmap(addr_t args_addr);
addr_t sys_mmap2(addr_t addr, dword_t len, dword_t prot, dword_t flags, fd_t fd_no, dword_t offset);
#ifdef ISH_GUEST_64BIT
addr_t sys_mmap64(addr_t addr, addr_t len, dword_t prot, dword_t flags, fd_t fd_no, addr_t offset);
#endif
Requested address (0 for kernel to choose)
Protection flags (PROT_READ, PROT_WRITE, PROT_EXEC)
Mapping flags (MMAP_SHARED, MMAP_PRIVATE, MMAP_FIXED, MMAP_ANONYMOUS)
File descriptor to map (ignored if MMAP_ANONYMOUS)
Offset into file (in pages for mmap2, bytes for mmap64)
Address of mapped region on success, negative error code on failure
Flags:
#define MMAP_SHARED 0x1 // Share mapping with other processes
#define MMAP_PRIVATE 0x2 // Create private copy-on-write mapping
#define MMAP_FIXED 0x10 // Map at exact address (or fail)
#define MMAP_ANONYMOUS 0x20 // Not backed by file
Example:
// Allocate anonymous private memory
addr_t addr = sys_mmap2(0, 0x1000, PROT_READ | PROT_WRITE,
MMAP_PRIVATE | MMAP_ANONYMOUS, -1, 0);
if ((int)addr < 0) {
// Error
}
// Map a file
fd_t fd = sys_open("/path/to/file", O_RDONLY_, 0);
addr_t mapped = sys_mmap2(0, 0x10000, PROT_READ,
MMAP_PRIVATE, fd, 0);
Memory Unmapping
int_t sys_munmap(addr_t addr, addr_t len);
Starting address to unmap (must be page-aligned)
0 on success, negative error code on failure
Example:
int err = sys_munmap(addr, 0x1000);
if (err < 0) {
// Error
}
Memory Protection
int_t sys_mprotect(addr_t addr, addr_t len, int_t prot);
Changes protection on a region of memory.
New protection flags (PROT_READ | PROT_WRITE | PROT_EXEC)
Example:
// Make region read-only
sys_mprotect(addr, 0x1000, PROT_READ);
User Space Access
These functions safely access memory in the emulated user space.
Read/Write Functions
int must_check user_read(addr_t addr, void *buf, size_t count);
int must_check user_write(addr_t addr, const void *buf, size_t count);
int must_check user_read_task(struct task *task, addr_t addr, void *buf, size_t count);
int must_check user_write_task(struct task *task, addr_t addr, const void *buf, size_t count);
int must_check user_write_task_ptrace(struct task *task, addr_t addr, const void *buf, size_t count);
int must_check user_read_string(addr_t addr, char *buf, size_t max);
int must_check user_write_string(addr_t addr, const char *buf);
Reads data from current task’s user space
Writes data to current task’s user space
Reads data from specified task’s user space
Writes data to specified task’s user space
Reads null-terminated string from user space
Writes null-terminated string to user space
0 on success, negative error code on failure (e.g., -EFAULT for invalid address)
Example:
// Read 4 bytes from user space
uint32_t value;
if (user_read(user_addr, &value, sizeof(value)) < 0) {
return -EFAULT;
}
// Write data to user space
struct my_struct data = { /* ... */ };
if (user_write(user_addr, &data, sizeof(data)) < 0) {
return -EFAULT;
}
// Read string from user space
char filename[256];
if (user_read_string(path_addr, filename, sizeof(filename)) < 0) {
return -EFAULT;
}
Convenience Macros
#define user_get(addr, var) user_read(addr, &(var), sizeof(var))
#define user_put(addr, var) user_write(addr, &(var), sizeof(var))
#define user_get_task(task, addr, var) user_read_task(task, addr, &(var), sizeof(var))
#define user_put_task(task, addr, var) user_write_task(task, addr, &(var), sizeof(var))
Example:
// Read single value
int32_t fd;
if (user_get(fd_addr, fd) < 0) {
return -EFAULT;
}
// Write single value
int32_t result = 42;
if (user_put(result_addr, result) < 0) {
return -EFAULT;
}
Memory Management Unit
MMU Structure
struct mmu {
struct mmu_ops *ops; // MMU operations
struct asbestos *asbestos; // Memory access tracking
uint64_t changes; // Change counter
};
MMU Operations
struct mmu_ops {
void *(*translate)(struct mmu *mmu, addr_t addr, int type);
};
Translates guest virtual address to host pointer. Returns NULL on fault.
Access type: MEM_READ, MEM_WRITE, or MEM_WRITE_PTRACE
#define MEM_READ 0
#define MEM_WRITE 1
#define MEM_WRITE_PTRACE 2
Example:
void *ptr = mmu_translate(cpu.mmu, guest_addr, MEM_READ);
if (ptr == NULL) {
// Page fault
}
Page Tables
Memory Structure
The mem structure manages virtual memory mappings.
32-bit:
struct mem {
struct pt_entry **pgdir; // 2-level page directory
int pgdir_used; // Number of allocated directories
struct mmu mmu; // MMU interface
wrlock_t lock; // Read/write lock
};
64-bit:
struct mem {
struct pt_hash_entry **hash_table; // Hash table for sparse pages
int pages_mapped; // Count of mapped pages
page_t mmap_cursor; // Next page for mmap allocation
struct mmu mmu; // MMU interface
wrlock_t lock; // Read/write lock
};
Page Table Entry
struct pt_entry {
struct data *data; // Backing data
size_t offset; // Offset into data
unsigned flags; // Page flags (P_READ, P_WRITE, etc.)
struct list blocks[2]; // Block lists
};
Page Flags
#define P_READ (1 << 0) // Readable
#define P_WRITE (1 << 1) // Writable
#define P_EXEC (1 << 2) // Executable
#define P_RWX (P_READ | P_WRITE | P_EXEC)
#define P_GROWSDOWN (1 << 3) // Stack grows downward
#define P_COW (1 << 4) // Copy-on-write
#define P_ANONYMOUS (1 << 6) // Anonymous mapping
#define P_SHARED (1 << 7) // Shared mapping
#define P_GUARD (1 << 8) // Guard page
#define P_WRITABLE(flags) (flags & P_WRITE && !(flags & P_COW))
Page Constants
#define PAGE_BITS 12
#define PAGE_SIZE (1 << PAGE_BITS) // 4096 bytes
#define PAGE(addr) ((addr) >> PAGE_BITS)
#define PGOFFSET(addr) ((addr) & (PAGE_SIZE - 1))
#define PAGE_ROUND_UP(bytes) (PAGE((bytes) + PAGE_SIZE - 1))
#define BYTES_ROUND_DOWN(bytes) (PAGE(bytes) << PAGE_BITS)
#define BYTES_ROUND_UP(bytes) (PAGE_ROUND_UP(bytes) << PAGE_BITS)
Example:
addr_t addr = 0x12345;
page_t page_num = PAGE(addr); // 0x12
addr_t offset = PGOFFSET(addr); // 0x345
addr_t page_start = page_num << PAGE_BITS; // 0x12000
Memory Initialization
void mem_init(struct mem *mem);
void mem_destroy(struct mem *mem);
Initializes empty address space
Frees all memory and destroys address space
Example:
struct mem mem;
mem_init(&mem);
// Use memory...
mem_destroy(&mem);
Page Table Operations
struct pt_entry *mem_pt(struct mem *mem, page_t page);
void mem_next_page(struct mem *mem, page_t *page);
Gets page table entry for given page number
Increments page number, skipping unallocated regions. For iteration.
Example:
// Iterate over all mapped pages
for (page_t page = 0; page < MEM_PAGES; mem_next_page(&mem, &page)) {
struct pt_entry *entry = mem_pt(&mem, page);
if (entry != NULL) {
// Process mapped page
}
}
Mapping Functions
bool pt_is_hole(struct mem *mem, page_t start, pages_t pages);
page_t pt_find_hole(struct mem *mem, pages_t size);
int pt_map(struct mem *mem, page_t start, pages_t pages, void *memory, size_t offset, unsigned flags);
int pt_map_nothing(struct mem *mem, page_t page, pages_t pages, unsigned flags);
int pt_unmap(struct mem *mem, page_t start, pages_t pages);
int pt_unmap_always(struct mem *mem, page_t start, pages_t pages);
int pt_set_flags(struct mem *mem, page_t start, pages_t pages, int flags);
int pt_copy_on_write(struct mem *src, struct mem *dst, page_t start, page_t pages);
Checks if region is unmapped. Returns true if unmapped.
Finds unmapped region of given size. Returns starting page number.
Maps host memory into guest address space. Takes ownership of memory.
Maps anonymous zero-filled pages
Unmaps pages. Returns -1 if any part is not mapped.
Unmaps pages. Doesn’t care if already unmapped.
Changes protection flags on mapped pages
Copies pages from src to dst using copy-on-write
Example:
// Allocate and map memory
void *memory = malloc(PAGE_SIZE * 4);
if (pt_map(&mem, 0x1000 >> PAGE_BITS, 4, memory, 0, P_READ | P_WRITE) < 0) {
free(memory);
return -ENOMEM;
}
// Map anonymous pages
if (pt_map_nothing(&mem, 0x5000 >> PAGE_BITS, 8, P_READ | P_WRITE) < 0) {
return -ENOMEM;
}
// Change protection
pt_set_flags(&mem, 0x1000 >> PAGE_BITS, 4, P_READ); // Make read-only
// Unmap
pt_unmap(&mem, 0x1000 >> PAGE_BITS, 4);
Memory Pointer Access
void *mem_ptr(struct mem *mem, addr_t addr, int type);
int mem_segv_reason(struct mem *mem, addr_t addr);
Translates guest address to host pointer. Must call with mem read-locked. Returns NULL on fault.
Gets reason for segmentation fault at address
Example:
read_wrlock(&mem.lock);
void *ptr = mem_ptr(&mem, guest_addr, MEM_WRITE);
if (ptr != NULL) {
*(uint32_t *)ptr = 0x12345678;
}
read_wrunlock(&mem.lock);
Data Backing
Pages are backed by data structures that reference host memory.
struct data {
void *data; // Host memory pointer (immutable)
size_t size; // Size in bytes (immutable)
atomic_uint refcount; // Reference count
// For /proc/pid/maps
struct fd *fd; // Backing file descriptor
size_t file_offset; // Offset into file
const char *name; // Mapping name
};
Data structures are reference counted and shared between pages with copy-on-write.