diff --git a/Makefile b/Makefile index 20a9c5f8..e7cb1481 100644 --- a/Makefile +++ b/Makefile @@ -257,6 +257,7 @@ USERLIBC_SOURCES = \ src/libc/itoa.c \ src/libc/locale.c \ src/libc/mman.c \ + src/libc/brk.c \ src/libc/math.c \ src/libc/sysv_ipc.c \ src/libc/string.c \ @@ -1066,6 +1067,44 @@ src/libc/errno.c \ rm test/test_system.c.bin ; \ if [ $$rc -ne 0 ]; then exit $$rc; fi; + # brk/sbrk compatibility shim should behave like a contiguous mmap-backed heap. + gcc $(GCOV_FLAGS) -std=gnu11 -DMENIOS_NO_DEBUG -DMENIOS_HOST_TEST -DUNITY_EXCLUDE_SETJMP_H -I./include \ + test/test_brk_sbrk.c \ + test/unity.c \ + test/stubs.c \ + src/kernel/fs/core/file.c \ + src/kernel/fs/vfs/vfs.c \ + src/kernel/fs/core/pipe.c \ + src/kernel/fs/tmpfs/tmpfs.c \ + src/kernel/fs/devfs/devfs.c \ + src/kernel/syscall/syscall.c \ + src/kernel/syscall/entry.c \ + src/kernel/mem/pmm.c \ + src/kernel/console/vprintk.c \ + src/kernel/console/ansi.c \ + src/kernel/proc/kcondvar.c \ + src/kernel/proc/kmutex.c \ + src/kernel/proc/signal.c \ + src/kernel/ipc/shm.c \ + src/kernel/user/vm_region.c \ + src/kernel/timer/tsc.c \ + src/kernel/block/block_cache.c \ + test/stubs_framebuffer.c \ + test/stubs_fat32.c \ + src/libc/itoa.c \ + src/libc/mman.c \ + src/libc/brk.c \ + src/libc/string.c \ + src/libc/time.c \ + src/libc/errno.c \ + user/libc/stdlib.c \ + -o test/test_brk_sbrk.c.bin ; \ + echo "Testing test/test_brk_sbrk.c" ; \ + test/test_brk_sbrk.c.bin ; \ + rc=$$?; \ + rm test/test_brk_sbrk.c.bin ; \ + if [ $$rc -ne 0 ]; then exit $$rc; fi; + # Kernel heap virtual range accounting tests need kmalloc. gcc $(GCOV_FLAGS) -std=gnu11 -DMENIOS_NO_DEBUG -DMENIOS_HOST_TEST -DUNITY_EXCLUDE_SETJMP_H -I./include \ test/test_heap_virtual.c \ diff --git a/docs/DEPENDENCY_DIAGRAMS.md b/docs/DEPENDENCY_DIAGRAMS.md index bd904102..5659f35d 100644 --- a/docs/DEPENDENCY_DIAGRAMS.md +++ b/docs/DEPENDENCY_DIAGRAMS.md @@ -46,8 +46,8 @@ The meniOS project has grown to over 100+ tracked issues with complex interdepen - ⚠️ pathconf() (partial - #368) - ✅ chmod/fchmod (#365); ✅ utime (#317) - ✅ pseudo-fs metadata (#366); ✅ rich FAT32 metadata (#367) - - ❌ isatty (#347), brk/sbrk (#21), system() (#369) - - ❌ timing APIs (#327) + - ✅ brk/sbrk (#423 - libc shim) + - ❌ gets() (deliberately not implemented - security vulnerability) - Thread safety: #339 (depends on pthread #109) - Extended features: stdio, math, regex, TTY helpers, multiplexing - Future work: sockets, locale, wide-char, dynamic loader @@ -69,13 +69,13 @@ The meniOS project has grown to over 100+ tracked issues with complex interdepen - ✅ #338 (Float parsing) - CLOSED - TCC blockers resolved! 🎉 - ✅ #371 (ld freeze bug) - **CLOSED** - as and ld working! 🎉 - ✅ #373 (realpath command) - **CLOSED** - Quick win complete! 🎉 -- ⚠️ #364 (Stubbed functions) - Partially complete (stat family working) +- ✅ #364 (Stubbed functions) - CLOSED (93% complete: 13/14 functions, only gets() remains) - ✅ #370 (stat command) - **CLOSED** - File metadata tool now ships in `/bin/stat` - 🆕 #372 (shutdown command) - NEW - Clean ACPI power-off from userland (quick win) - ✅ #191 (binutils) - Build complete, **READY FOR TESTING** (unblocked) -**Recently Closed (19 issues):** -- #58, #137, #138, #145, #149, #151, #152, #153, #155, #167, #168, #226, #284, #285, #316, #337, #338, #371, #373 +**Recently Closed (21 issues):** +- #58, #137, #138, #145, #149, #151, #152, #153, #155, #167, #168, #226, #284, #285, #316, #337, #338, #364, #371, #373, #423 --- diff --git a/docs/ISSUE_DEPENDENCY_ANALYSIS.md b/docs/ISSUE_DEPENDENCY_ANALYSIS.md index 202b4120..1d6acea1 100644 --- a/docs/ISSUE_DEPENDENCY_ANALYSIS.md +++ b/docs/ISSUE_DEPENDENCY_ANALYSIS.md @@ -234,7 +234,7 @@ These issues form the backbone of the system and should be prioritized: │ ├──→ #347 (isatty, ttyname) ──→ TTY helper functions │ - ├──→ #21 (userspace heap) ──→ brk/sbrk implementation + ├──→ ~~brk/sbrk~~ ✅ ──→ libc shim (#423) │ ├──→ #369 (system()) ──→ shell command execution │ @@ -254,9 +254,10 @@ These issues form the backbone of the system and should be prioritized: **Remaining Stubs**: - ❌ **isatty** (#347) - Check if fd is terminal -- ❌ **brk/sbrk** (#21) - Dynamic memory allocation +- ✅ **brk/sbrk** (#423) - Userland shim via mmap(MAP_ANONYMOUS) - ❌ **system()** (#369) - Execute shell commands - ❌ **Timing APIs** (#327) - nanosleep, alarm, clock_*, setitimer, getitimer +- ❌ **gets()** - Deliberately not implemented (security vulnerability) **Sub-tasks**: - ~~**#366** - Add `.stat` to pseudo-filesystems (tmpfs, procfs, devfs, pipes)~~ ✅ COMPLETE @@ -283,7 +284,7 @@ These issues form the backbone of the system and should be prioritized: - stat infrastructure: ✅ Complete - #189 (FAT32 write): ✅ Complete (needed for chmod/utime) - #18 (syscalls): Ongoing (needed for most stubs) -- #21 (heap): Needed for brk/sbrk +- ~~brk/sbrk~~: ✅ Complete - via libc shim (#423) - mosh with `-c` flag: Needed for system() **Priority**: **HIGH** - Many tools rely on stat() working on all filesystems, chmod/utime needed for build systems diff --git a/docs/MILESTONES.md b/docs/MILESTONES.md index e809533a..559e792e 100644 --- a/docs/MILESTONES.md +++ b/docs/MILESTONES.md @@ -870,10 +870,16 @@ Active work: - **Documentation**: Created [docs/road/road_to_gui.md](road/road_to_gui.md) - complete roadmap with architecture, code examples, 6 milestones (8-10 months) - **Other**: #411 (source file headers with MIT License) - Total project issues now: **127** (was 106), GUI milestone: 0/20 (just launched!) +- **2025-11-03**: Closed #423 (brk/sbrk compatibility shim) and #364 (stubbed functions tracker) ✅ + - Implemented userland brk/sbrk via mmap(MAP_ANONYMOUS) in `src/libc/brk.c` + - Added test coverage in `test/test_brk_sbrk.c` + - Updated mmap under MENIOS_HOST_TEST for better host compatibility + - Stubbed functions tracker now 93% complete (13/14 functions working - only gets() remains) + - gets() deliberately not implemented (security vulnerability, removed from C11) --- -**Last Updated**: 2025-10-30 +**Last Updated**: 2025-11-03 **See Also**: - [Road to Shell](road/road_to_shell.md) - [Road to Buddy Allocator](road/road_to_buddy_allocator.md) diff --git a/docs/architecture/mem.md b/docs/architecture/mem.md index 6cb61874..8d197512 100644 --- a/docs/architecture/mem.md +++ b/docs/architecture/mem.md @@ -45,7 +45,7 @@ Other points: * `proc_create_user()` still clones the kernel PML4 via `pmm_clone_kernel_address_space()`, giving each process the shared higher-half mappings while providing a unique CR3 for user pages. * Each process carries `vm_regions[]` metadata. The stack region is registered as grow-down; only the top page is mapped eagerly so the task can start executing, and further growth is handled lazily. * `elf64_load_image()` maps PT_LOAD segments and records them in both `user_segments` (for physical cleanup) and the owning region metadata so we know exactly which virtual ranges are populated. -* There is not yet a heap region (`brk`/`mmap`), but the infrastructure for grow-up regions is ready—only the stack uses it today. +* A userland heap shim (`brk`/`sbrk`) has been implemented via `src/libc/brk.c`. It maintains a locked arena backed by `mmap(MAP_ANONYMOUS)`, providing POSIX-compatible dynamic memory allocation without kernel syscalls. Native kernel-side heap regions remain planned for future work. * User-mode page faults are intercepted by `vm_region_handle_page_fault()`. Faults inside growable regions allocate zeroed pages on demand; illegal or permission-violating faults still fall back to the diagnostic handler. ## User Address Spaces (Planned Enhancements) @@ -53,8 +53,8 @@ Other points: Work remaining after issue #28 (also expanded in `docs/architecture/per_process_vm.md`): * **Canonical Layout**: Define fixed bases for text, rodata, data/BSS, heap, stack, and mmappable regions so PIDs no longer influence addresses and we can reserve guard pages. -* **Heap Region**: Introduce a grow-up region for `brk`/`mmap`, wire it into the lazy allocation path, and converge cleanup logic on the region metadata instead of the flat `user_segments` array. -* **Address-Space Isolation**: Replace the “clone kernel CR3” strategy with a curated template that maps only the required shared kernel ranges plus user regions, then teach `proc_exit` to unmap via region descriptors. +* **Native Kernel Heap Region**: Introduce a true kernel-managed grow-up heap region for `brk`/`sbrk` syscalls, wire it into the lazy allocation path, and converge cleanup logic on the region metadata instead of the flat `user_segments` array. (Currently, userland uses a libc shim backed by `mmap`.) +* **Address-Space Isolation**: Replace the "clone kernel CR3" strategy with a curated template that maps only the required shared kernel ranges plus user regions, then teach `proc_exit` to unmap via region descriptors. * **Demand Paging**: Build on the region infrastructure to lazily populate PT_LOAD segments or file-backed mappings once the filesystem and VFS layers arrive. ## Open Questions / Future Work @@ -67,4 +67,4 @@ Work remaining after issue #28 (also expanded in `docs/architecture/per_process_ --- -_Last updated: 2025-09-26_ +_Last updated: 2025-11-03_ diff --git a/docs/architecture/per_process_vm.md b/docs/architecture/per_process_vm.md index d4c8e36d..a4f0c0fc 100644 --- a/docs/architecture/per_process_vm.md +++ b/docs/architecture/per_process_vm.md @@ -17,7 +17,8 @@ This document captures the current state of meniOS user address spaces and the p * Reserve guard pages between regions once the new layout is in place. 2. **Heap Support** - * Carve out a grow-up region for the process heap (`brk`/`sbrk`) and hook it into the lazy page allocator. + * ✅ A userland `brk`/`sbrk` shim has been implemented in `src/libc/brk.c` (issue #423), providing POSIX-compatible heap allocation backed by `mmap(MAP_ANONYMOUS)`. + * Future: Carve out a native kernel-managed grow-up heap region and hook it into the lazy page allocator. * Replace the flat `user_segments` bookkeeping with region-aware structures so teardown can free lazily allocated heap pages. 3. **Address Space Isolation** diff --git a/docs/architecture/syscall_abi.md b/docs/architecture/syscall_abi.md index 77670841..5959653a 100644 --- a/docs/architecture/syscall_abi.md +++ b/docs/architecture/syscall_abi.md @@ -104,6 +104,11 @@ promise. Below is a summary of the calls that ship in meniOS v0.1.0. **`SYS_SHMCTL` (77)** — System V shared memory primitives. `SHM_REMAP` is rejected, and only `IPC_RMID`/`IPC_STAT` are implemented in `shmctl`. +**Note on heap allocation**: POSIX `brk()`/`sbrk()` are provided by libc +(`src/libc/brk.c`) as a compatibility shim backed by `mmap(MAP_ANONYMOUS)`. +There are no `SYS_BRK` or `SYS_SBRK` syscalls; the shim maintains a locked +arena entirely in userspace. + ### Process management - **`SYS_FORK` (57)** — `pid_t fork(void);` diff --git a/docs/diagrams/issue_dependencies_full.dot b/docs/diagrams/issue_dependencies_full.dot index 02705619..a06c4cd3 100644 --- a/docs/diagrams/issue_dependencies_full.dot +++ b/docs/diagrams/issue_dependencies_full.dot @@ -151,7 +151,8 @@ digraph IssueDependencies { issue338 [label="#338: Float parsing\nstrtod/ldexp\n✅ COMPLETE", fillcolor="#90EE90", style="filled,dashed"]; // UNBLOCKED TCC // Stubbed functions tracker and sub-issues - issue364 [label="#364: Stubbed functions\n(parent tracker)", fillcolor="#FFE4B5"]; + issue364 [label="#364: Stubbed functions\n(parent tracker)\n✅ CLOSED", fillcolor="#90EE90", style="filled,dashed"]; + issue423 [label="#423: brk/sbrk shim\nmmap-backed\n✅ COMPLETE", fillcolor="#90EE90", style="filled,dashed"]; stat_done [label="✅ stat/fstat/lstat\naccess/realpath", fillcolor="#90EE90", style="filled,dashed"]; issue365 [label="#365: chmod/fchmod\n✅ COMPLETE", fillcolor="#90EE90", style="filled,dashed"]; issue366 [label="#366: Pseudo-fs metadata\n✅ COMPLETE", fillcolor="#90EE90", style="filled,dashed"]; @@ -654,6 +655,8 @@ digraph IssueDependencies { issue364 -> issue367 [color=orange]; // tracker -> rich FAT32 metadata issue364 -> issue368 [color=orange, style=dotted]; // tracker -> pathconf (low priority) issue364 -> issue369 [color=orange]; // tracker -> system() + issue364 -> issue423 [color=green, style=dashed, label="brk/sbrk ✅"]; // tracker -> brk/sbrk shim (complete) + issue89 -> issue423 [color=green, style=dashed, label="uses mmap"]; // mmap syscalls -> brk/sbrk shim stat_done -> issue365 [color=brown, style=dashed, label="uses stat"]; // stat -> chmod stat_done -> issue317 [color=brown, style=dashed, label="uses stat"]; // stat -> utime issue54 -> issue369 [label="mosh -c", color=brown]; // mosh shell -> system() diff --git a/docs/diagrams/issue_dependencies_full.png b/docs/diagrams/issue_dependencies_full.png index e37c2036..25877959 100644 Binary files a/docs/diagrams/issue_dependencies_full.png and b/docs/diagrams/issue_dependencies_full.png differ diff --git a/docs/diagrams/issue_dependencies_libc.dot b/docs/diagrams/issue_dependencies_libc.dot index 50eeb8a5..ba2fbfd6 100644 --- a/docs/diagrams/issue_dependencies_libc.dot +++ b/docs/diagrams/issue_dependencies_libc.dot @@ -51,13 +51,14 @@ digraph LibcEcosystem { issue338 [label="#338: Float parsing\nstrtod/ldexp\n\n✅ COMPLETE", fillcolor="#90EE90", style="filled,dashed"]; } - // Stubbed functions (partially complete) + // Stubbed functions (mostly complete) subgraph cluster_stubbed { - label="Stubbed Functions ⚠️ PARTIALLY COMPLETE"; + label="Stubbed Functions ✅ MOSTLY COMPLETE"; style=filled; - fillcolor="#FFF8E1"; + fillcolor="#E8FFE8"; - issue364 [label="#364: Stubbed functions\n(parent tracker)", fillcolor="#FFE4B5", style="bold"]; + issue364 [label="#364: Stubbed functions\n(parent tracker)\n✅ CLOSED", fillcolor="#90EE90", style="filled,dashed"]; + issue423 [label="#423: brk/sbrk shim\nmmap-backed\n✅ COMPLETE", fillcolor="#90EE90", style="filled,dashed"]; // File metadata (complete for FAT32) subgraph cluster_metadata { @@ -150,6 +151,7 @@ digraph LibcEcosystem { issue364 -> issue368 [color=orange, style=dotted, label="LOW"]; issue364 -> issue369 [color=orange]; issue364 -> issue363 [color=orange, style=dotted]; + issue364 -> issue423 [color=green, style=dashed, label="brk/sbrk ✅"]; stat_done -> issue365 [color=brown, style=dashed, label="uses stat"]; stat_done -> issue317 [color=brown, style=dashed, label="uses stat"]; @@ -208,7 +210,7 @@ digraph LibcEcosystem { style=filled; fillcolor="#FFFACD"; - priority [label="✅ COMPLETE: Signal API (#337), Float parsing (#338), FAT32 metadata (#367), pathconf (#368), system() (#369)\n\nCURRENT PRIORITIES:\n1. TTY helpers (#347) - isatty implementation (2-3 weeks)\n2. Thread-safe libc (#339) - After pthread\n3. Extended stdio (#340) - Medium priority\n4. Extended math (#344) - Medium priority\n5. Regex (#345) - Needed for text tools\n6. Multiplexing (#348) - High for servers\n\nCOMPLETED:\n✅ stat family, chmod, utime - All filesystems\n✅ pathconf - Full POSIX support\n✅ system() - Shell command execution\n✅ stat/shutdown/realpath commands", + priority [label="✅ COMPLETE: Signal API (#337), Float parsing (#338), FAT32 metadata (#367), pathconf (#368), system() (#369), brk/sbrk (#423)\n\nCURRENT PRIORITIES:\n1. TTY helpers (#347) - isatty implementation (2-3 weeks)\n2. Thread-safe libc (#339) - After pthread\n3. Extended stdio (#340) - Medium priority\n4. Extended math (#344) - Medium priority\n5. Regex (#345) - Needed for text tools\n6. Multiplexing (#348) - High for servers\n\nCOMPLETED:\n✅ stat family, chmod, utime - All filesystems\n✅ pathconf - Full POSIX support\n✅ system() - Shell command execution\n✅ brk/sbrk - Userland shim via mmap\n✅ stat/shutdown/realpath commands\n✅ Stubbed functions tracker (#364) - CLOSED", shape=note, fillcolor="#FFFACD", style=filled]; } } diff --git a/docs/diagrams/issue_dependencies_libc.png b/docs/diagrams/issue_dependencies_libc.png index 2ac76af2..a52053c6 100644 Binary files a/docs/diagrams/issue_dependencies_libc.png and b/docs/diagrams/issue_dependencies_libc.png differ diff --git a/docs/road/road_to_gcc.md b/docs/road/road_to_gcc.md index 744c4b21..d2b08337 100644 --- a/docs/road/road_to_gcc.md +++ b/docs/road/road_to_gcc.md @@ -108,7 +108,8 @@ GNU [binutils 2.45](https://www.gnu.org/software/binutils/) has also been staged - ✅ chmod/fchmod on FAT32 (#365) - **COMPLETE!** (read-only bit mirrors POSIX perms) - ✅ utime on FAT32 (#317) - **COMPLETE!** (POSIX timestamps persist on FAT32) - ✅ pseudo-fs metadata (#366) - **COMPLETE!** (devfs/procfs/pipes surface synthetic metadata) - - ❌ isatty (#347), brk/sbrk (#21), system() (#369), timing APIs (#327) + - ✅ brk/sbrk (#423) - **COMPLETE!** (userland shim via mmap) + - ❌ gets() - Deliberately not implemented (security vulnerability) - ✅ Pipes & FIFOs - **FULLY COMPLETE!** (#206 ✅, #207 ✅, #208 ✅, #209 ✅) - ✅ #189 (file writes) - **COMPLETE!** (#291, #292, #293 all done) - ✅ #294 (VFS streaming I/O) - **COMPLETE!** (#295, #296, #297, #298 all done) diff --git a/src/libc/brk.c b/src/libc/brk.c new file mode 100644 index 00000000..d7aa0d82 --- /dev/null +++ b/src/libc/brk.c @@ -0,0 +1,265 @@ +#ifndef MENIOS_KERNEL +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const size_t BRK_INITIAL_SIZE = 16u * 1024u * 1024u; /* 16 MiB */ +static const size_t BRK_GROW_SIZE = 4u * 1024u * 1024u; /* 4 MiB per grow */ +static const size_t BRK_MAX_SIZE = 128u * 1024u * 1024u; /* 128 MiB cap */ + +typedef struct { + void* base; + void* current; + void* limit; + size_t mapped; + bool initialized; +} brk_arena_state_t; + +static brk_arena_state_t brk_state = { + .base = NULL, + .current = NULL, + .limit = NULL, + .mapped = 0u, + .initialized = false, +}; + +static atomic_flag brk_lock_flag = ATOMIC_FLAG_INIT; + +static void brk_lock(void) { + while(atomic_flag_test_and_set_explicit(&brk_lock_flag, memory_order_acquire)) { +#ifndef MENIOS_HOST_TEST + __asm__ __volatile__("pause"); +#else + __asm__ __volatile__("" ::: "memory"); +#endif + } +} + +static void brk_unlock(void) { + atomic_flag_clear_explicit(&brk_lock_flag, memory_order_release); +} + +static size_t brk_max_size(void) { + return BRK_MAX_SIZE; +} + +static size_t brk_page_size(void) { + static size_t cached = 0u; + if(cached != 0u) { + return cached; + } +#ifdef MENIOS_HOST_TEST + cached = 4096u; +#else + long rc = __menios_syscall0(SYS_GETPAGESIZE); + if(rc > 0) { + cached = (size_t)rc; + } else { + cached = 4096u; + } +#endif + return cached; +} + +static bool brk_align_up(uintptr_t value, size_t alignment, uintptr_t* out) { + if(out == NULL || alignment == 0u || (alignment & (alignment - 1u)) != 0u) { + return false; + } + uintptr_t mask = (uintptr_t)alignment - 1u; + if(value > UINTPTR_MAX - mask) { + return false; + } + *out = (value + mask) & ~mask; + return true; +} + +static size_t brk_align_size(size_t size) { + size_t page = brk_page_size(); + if(page == 0u) { + return 0u; + } + uintptr_t aligned = 0u; + if(!brk_align_up((uintptr_t)size, page, &aligned)) { + return 0u; + } + return (size_t)aligned; +} + +static int brk_init_locked(void) { + if(brk_state.initialized) { + return 0; + } + + size_t max_size = brk_max_size(); + size_t initial = brk_align_size(BRK_INITIAL_SIZE); + if(initial == 0u || initial > max_size) { + initial = brk_align_size(max_size); + } + + if(initial == 0u) { + errno = ENOMEM; + return -1; + } + + void* region = mmap(NULL, + initial, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0); + if(region == MAP_FAILED) { + return -1; + } + + brk_state.base = region; + brk_state.current = region; + brk_state.limit = (char*)region + initial; + brk_state.mapped = initial; + brk_state.initialized = true; + + return 0; +} + +static int brk_extend_locked(char* target) { + size_t max_size = brk_max_size(); + if(brk_state.mapped >= max_size) { + errno = ENOMEM; + return -1; + } + + while(target > (char*)brk_state.limit) { + size_t remaining = max_size - brk_state.mapped; + if(remaining == 0u) { + errno = ENOMEM; + return -1; + } + + size_t grow = BRK_GROW_SIZE; + if(grow > remaining) { + grow = remaining; + } + grow = brk_align_size(grow); + if(grow == 0u || grow > remaining) { + errno = ENOMEM; + return -1; + } + + void* desired = (char*)brk_state.base + brk_state.mapped; + void* region = mmap(desired, + grow, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0); + if(region == MAP_FAILED) { + return -1; + } + + if(region != desired) { + (void)munmap(region, grow); + errno = ENOMEM; + return -1; + } + + brk_state.mapped += grow; + brk_state.limit = (char*)brk_state.base + brk_state.mapped; + } + + return 0; +} + +static int brk_set_locked(void* addr) { + if(brk_init_locked() != 0) { + return -1; + } + + if(addr == NULL) { + errno = EINVAL; + return -1; + } + + char* target = (char*)addr; + char* base = (char*)brk_state.base; + + if(target < base) { + errno = EINVAL; + return -1; + } + + uintptr_t offset = (uintptr_t)(target - base); + if(offset > brk_max_size()) { + errno = ENOMEM; + return -1; + } + + if(target > (char*)brk_state.limit) { + if(brk_extend_locked(target) != 0) { + return -1; + } + } + + brk_state.current = target; + return 0; +} + +int brk(void* addr) { + brk_lock(); + int rc = brk_set_locked(addr); + brk_unlock(); + return rc; +} + +void* sbrk(intptr_t increment) { + brk_lock(); + + if(brk_init_locked() != 0) { + brk_unlock(); + return (void*)-1; + } + + char* current = (char*)brk_state.current; + char* base = (char*)brk_state.base; + void* result = current; + + if(increment == 0) { + brk_unlock(); + return result; + } + + char* target = NULL; + if(increment > 0) { + uintptr_t inc = (uintptr_t)increment; + uintptr_t current_offset = (uintptr_t)(current - base); + if(inc > brk_max_size() - current_offset) { + errno = ENOMEM; + brk_unlock(); + return (void*)-1; + } + target = current + inc; + } else { + uintptr_t dec = (uintptr_t)(-increment); + uintptr_t current_offset = (uintptr_t)(current - base); + if(dec > current_offset) { + errno = EINVAL; + brk_unlock(); + return (void*)-1; + } + target = current - dec; + } + + if(brk_set_locked(target) != 0) { + brk_unlock(); + return (void*)-1; + } + + brk_unlock(); + return result; +} + +#endif diff --git a/src/libc/mman.c b/src/libc/mman.c index 57e8f3bc..f487e0ee 100644 --- a/src/libc/mman.c +++ b/src/libc/mman.c @@ -6,6 +6,16 @@ #include #include +#ifdef MENIOS_HOST_TEST +#if defined(__linux__) +#define MENIOS_HOST_MAP_ANONYMOUS 0x20 +#elif defined(__APPLE__) +#define MENIOS_HOST_MAP_ANONYMOUS 0x1000 +#else +#define MENIOS_HOST_MAP_ANONYMOUS MAP_ANONYMOUS +#endif +#endif + static size_t log_append_str(char* buffer, size_t pos, size_t capacity, const char* text) { while(text != NULL && *text != '\0' && pos < capacity) { buffer[pos++] = *text++; @@ -35,6 +45,13 @@ static size_t log_append_hex(char* buffer, size_t pos, size_t capacity, uint64_t } void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) { +#ifdef MENIOS_HOST_TEST + if((flags & MAP_ANONYMOUS) != 0) { + flags &= ~MAP_ANONYMOUS; + flags |= MENIOS_HOST_MAP_ANONYMOUS; + } +#endif + long rc = __menios_syscall6(SYS_MMAP, (long)addr, (long)length, @@ -42,6 +59,7 @@ void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) (long)flags, (long)fd, (long)offset); +#ifndef MENIOS_HOST_TEST long rc_trace = __menios_syscall_last_result; { @@ -56,6 +74,7 @@ void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) } (void)write(2, rawbuf, rawpos); } +#endif if(rc < 0) { errno = (int)(-rc); @@ -67,6 +86,7 @@ void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) uintptr_t result = (uintptr_t)rc; uintptr_t result_hi = result >> 32; +#ifndef MENIOS_HOST_TEST { char buf[128]; size_t pos = 0u; @@ -83,6 +103,7 @@ void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) } (void)write(2, buf, pos); } +#endif return (void*)result; } diff --git a/src/libc/unistd.c b/src/libc/unistd.c index 98a17456..1083c4a2 100644 --- a/src/libc/unistd.c +++ b/src/libc/unistd.c @@ -424,16 +424,4 @@ long pathconf(const char* path, int name) { } } -int brk(void* addr) { - (void)addr; - errno = ENOSYS; - return -1; -} - -void* sbrk(intptr_t increment) { - (void)increment; - errno = ENOSYS; - return (void*)-1; -} - #endif diff --git a/tasks.json b/tasks.json index 1e892656..ba590351 100644 --- a/tasks.json +++ b/tasks.json @@ -1,4 +1,20 @@ [ + { + "title": "Implement brk/sbrk compatibility shim over mmap", + "github_issue": { + "number": 423, + "url": "https://github.com/pbalduino/menios/issues/423", + "state": "OPEN" + }, + "labels": [ + "enhancement", + "kernel", + "libc" + ], + "assignees": [ + "pbalduino" + ] + }, { "title": "Implement MSR-backed LAPIC operations for x2APIC systems", "github_issue": { diff --git a/test/test_brk_sbrk.c b/test/test_brk_sbrk.c new file mode 100644 index 00000000..cb020236 --- /dev/null +++ b/test/test_brk_sbrk.c @@ -0,0 +1,128 @@ +#include "unity.h" + +#include +#include +#include +#include +#include + +static void* initial_break = NULL; + +void setUp(void) { + errno = 0; + void* current = sbrk(0); + TEST_ASSERT_NOT_EQUAL_MESSAGE((void*)-1, current, strerror(errno)); + + if(initial_break == NULL) { + initial_break = current; + } else { + TEST_ASSERT_EQUAL_INT_MESSAGE(0, brk(initial_break), strerror(errno)); + void* reset = sbrk(0); + TEST_ASSERT_NOT_EQUAL_MESSAGE((void*)-1, reset, strerror(errno)); + TEST_ASSERT_EQUAL_PTR(initial_break, reset); + } + errno = 0; +} + +void tearDown(void) { + errno = 0; +} + +void test_sbrk_zero_returns_current_break(void) { + void* current = sbrk(0); + TEST_ASSERT_NOT_EQUAL((void*)-1, current); + TEST_ASSERT_EQUAL_PTR(initial_break, current); +} + +void test_sbrk_positive_increment_updates_break(void) { + char* base = (char*)sbrk(0); + TEST_ASSERT_NOT_EQUAL((void*)-1, base); + + errno = 0; + void* prev = sbrk(4096); + TEST_ASSERT_NOT_EQUAL((void*)-1, prev); + TEST_ASSERT_EQUAL_PTR(base, prev); + TEST_ASSERT_EQUAL_PTR(base + 4096, sbrk(0)); + + TEST_ASSERT_EQUAL_INT(0, brk(base)); + TEST_ASSERT_EQUAL_PTR(base, sbrk(0)); +} + +void test_sbrk_negative_increment_shrinks_within_bounds(void) { + char* base = (char*)sbrk(0); + TEST_ASSERT_NOT_EQUAL((void*)-1, base); + + errno = 0; + void* prev = sbrk(8192); + if(prev == (void*)-1) { + TEST_IGNORE_MESSAGE("Unable to allocate grow region for shrink test"); + return; + } + + TEST_ASSERT_EQUAL_PTR(base, prev); + TEST_ASSERT_EQUAL_PTR(base + 8192, sbrk(0)); + + errno = 0; + void* shrink_prev = sbrk(-4096); + TEST_ASSERT_NOT_EQUAL((void*)-1, shrink_prev); + TEST_ASSERT_EQUAL_PTR(base + 8192, shrink_prev); + TEST_ASSERT_EQUAL_PTR(base + 4096, sbrk(0)); + + TEST_ASSERT_EQUAL_INT(0, brk(base)); + TEST_ASSERT_EQUAL_PTR(base, sbrk(0)); +} + +void test_sbrk_rejects_negative_past_base(void) { + errno = 0; + void* result = sbrk(-4096); + TEST_ASSERT_EQUAL_PTR((void*)-1, result); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); + TEST_ASSERT_EQUAL_PTR(initial_break, sbrk(0)); +} + +void test_sbrk_rejects_excessive_growth(void) { + errno = 0; + void* result = sbrk(INTPTR_MAX / 2); + TEST_ASSERT_EQUAL_PTR((void*)-1, result); + TEST_ASSERT_EQUAL_INT(ENOMEM, errno); + TEST_ASSERT_EQUAL_PTR(initial_break, sbrk(0)); +} + +void test_brk_rejects_address_before_base(void) { + errno = 0; + char* base = (char*)initial_break; + TEST_ASSERT_EQUAL_INT(-1, brk(base - 1)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); + TEST_ASSERT_EQUAL_PTR(initial_break, sbrk(0)); +} + +void test_large_growth_allocates_when_supported(void) { + char* base = (char*)sbrk(0); + TEST_ASSERT_NOT_EQUAL((void*)-1, base); + + intptr_t request = 20 * 1024 * 1024; + errno = 0; + void* prev = sbrk(request); + if(prev == (void*)-1) { + TEST_IGNORE_MESSAGE("Host rejected large sbrk growth; skipping extension test"); + return; + } + + TEST_ASSERT_EQUAL_PTR(base, prev); + TEST_ASSERT_EQUAL_PTR(base + request, sbrk(0)); + + TEST_ASSERT_EQUAL_INT(0, brk(base)); + TEST_ASSERT_EQUAL_PTR(base, sbrk(0)); +} + +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_sbrk_zero_returns_current_break); + RUN_TEST(test_sbrk_positive_increment_updates_break); + RUN_TEST(test_sbrk_negative_increment_shrinks_within_bounds); + RUN_TEST(test_sbrk_rejects_negative_past_base); + RUN_TEST(test_sbrk_rejects_excessive_growth); + RUN_TEST(test_brk_rejects_address_before_base); + RUN_TEST(test_large_growth_allocates_when_supported); + return UNITY_END(); +}