#if defined(__linux__)
#define _GNU_SOURCE
#endif
#include "stack_mgmt.h"
#include "scheduler.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \
defined(__NetBSD__)
#include <signal.h>
#else
typedef struct {
int si_signo;
int si_errno;
int si_code;
void *si_addr;
} siginfo_t;
struct sigaction {
void (*sa_sigaction)(int, siginfo_t *, void *);
unsigned long sa_flags;
void (*sa_restorer)(void);
unsigned char sa_mask[128]; };
#define SA_SIGINFO 4
#define SIGSEGV 11
#define SIG_DFL ((void (*)(int))0)
extern int sigaction(int sig, const struct sigaction *act,
struct sigaction *oldact);
extern int sigemptyset(void *set); extern void (*signal(int sig, void (*func)(int)))(int);
extern int raise(int sig);
#endif
extern long sysconf(int);
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \
defined(__NetBSD__)
static const int SC_PAGESIZE_CANDIDATES[] = {29, 30, -1};
#define DEFAULT_PAGE_SIZE 16384
#ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS MAP_ANON
#endif
#elif defined(__linux__)
static const int SC_PAGESIZE_CANDIDATES[] = {30, 29, -1};
#define DEFAULT_PAGE_SIZE 4096
#else
static const int SC_PAGESIZE_CANDIDATES[] = {30, 29, -1};
#define DEFAULT_PAGE_SIZE 4096
#warning \
"Unknown platform - page size detection may fail, will fall back to 4KB"
#endif
static size_t g_page_size = 0;
static Scheduler *g_scheduler = NULL;
static void signal_safe_write(const char *str);
static void size_to_str(size_t n, char *buf, size_t bufsize);
size_t stack_get_page_size(void) {
if (g_page_size == 0) {
for (int i = 0; SC_PAGESIZE_CANDIDATES[i] != -1; i++) {
long result = sysconf(SC_PAGESIZE_CANDIDATES[i]);
if (result > 0 && result != -1) {
g_page_size = (size_t)result;
break;
}
}
if (g_page_size == 0) {
g_page_size = DEFAULT_PAGE_SIZE;
fprintf(stderr,
"WARNING: Could not detect page size via sysconf(), using "
"default %zu bytes\n",
g_page_size);
}
}
return g_page_size;
}
StackMetadata *stack_alloc(size_t initial_size) {
size_t page_size = stack_get_page_size();
if (initial_size < CEM_INITIAL_STACK_SIZE) {
initial_size = CEM_INITIAL_STACK_SIZE;
}
if (initial_size > CEM_MAX_STACK_SIZE) {
fprintf(stderr, "stack_alloc: requested size %zu exceeds maximum %d\n",
initial_size, CEM_MAX_STACK_SIZE);
return NULL;
}
if (initial_size > SIZE_MAX - page_size) {
fprintf(stderr, "stack_alloc: size %zu too large (overflow risk)\n",
initial_size);
return NULL;
}
size_t usable_size = ((initial_size + page_size - 1) / page_size) * page_size;
if (usable_size > SIZE_MAX - page_size) {
fprintf(stderr, "stack_alloc: usable size %zu too large (overflow risk)\n",
usable_size);
return NULL;
}
size_t total_size = usable_size + page_size;
StackMetadata *meta = (StackMetadata *)malloc(sizeof(StackMetadata));
if (!meta) {
fprintf(stderr, "stack_alloc: failed to allocate metadata\n");
return NULL;
}
void *base = mmap(NULL, total_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (base == MAP_FAILED) {
fprintf(stderr, "stack_alloc: mmap failed for %zu bytes\n", total_size);
free(meta);
return NULL;
}
if (mprotect(base, page_size, PROT_NONE) != 0) {
fprintf(stderr, "stack_alloc: mprotect failed to set guard page\n");
munmap(base, total_size);
free(meta);
return NULL;
}
meta->base = base;
meta->usable_base = (void *)((uintptr_t)base + page_size);
meta->total_size = total_size;
meta->usable_size = usable_size;
meta->guard_page_size = page_size;
meta->growth_count = 0;
meta->guard_hit = false;
return meta;
}
void stack_free(StackMetadata *meta) {
if (!meta) {
return;
}
if (meta->base) {
if (munmap(meta->base, meta->total_size) != 0) {
fprintf(stderr, "ERROR: munmap failed during stack_free\n");
fprintf(stderr, " Address: %p\n", meta->base);
fprintf(stderr, " Size: %zu bytes\n", meta->total_size);
fprintf(stderr, " This will cause a memory leak (%zu bytes)!\n",
meta->total_size);
fprintf(stderr, " Possible causes:\n");
fprintf(stderr, " - Corrupted stack metadata\n");
fprintf(stderr, " - Double-free of stack\n");
fprintf(stderr, " - Kernel resource exhaustion\n");
}
}
free(meta);
}
size_t stack_get_used(const StackMetadata *meta, uintptr_t current_sp) {
uintptr_t stack_top = (uintptr_t)meta->usable_base + meta->usable_size;
if (current_sp > stack_top || current_sp < (uintptr_t)meta->usable_base) {
return meta->usable_size; }
return (size_t)(stack_top - current_sp);
}
size_t stack_get_free(const StackMetadata *meta, uintptr_t current_sp) {
size_t used = stack_get_used(meta, current_sp);
if (used >= meta->usable_size) {
return 0;
}
return meta->usable_size - used;
}
bool stack_grow(struct Strand *strand, size_t new_usable_size,
bool in_signal_handler) {
assert(strand != NULL);
assert(strand->stack_meta != NULL);
StackMetadata *old_meta = strand->stack_meta;
size_t page_size = stack_get_page_size();
if (new_usable_size <= old_meta->usable_size) {
if (in_signal_handler) {
signal_safe_write("ERROR: stack_grow: new size must be > current size\n");
} else {
fprintf(stderr, "stack_grow: new size %zu must be > current size %zu\n",
new_usable_size, old_meta->usable_size);
}
return false;
}
if (new_usable_size > CEM_MAX_STACK_SIZE) {
if (in_signal_handler) {
signal_safe_write("ERROR: Maximum stack size reached\n");
signal_safe_write(" This usually indicates infinite recursion\n");
} else {
fprintf(stderr,
"stack_grow: strand %llu hit maximum stack size (%d bytes)\n",
(unsigned long long)strand->id, CEM_MAX_STACK_SIZE);
fprintf(stderr, " This usually indicates infinite recursion or "
"excessive local variables.\n");
}
return false;
}
new_usable_size = ((new_usable_size + page_size - 1) / page_size) * page_size;
StackMetadata *new_meta = stack_alloc(new_usable_size);
if (!new_meta) {
if (in_signal_handler) {
signal_safe_write("ERROR: Failed to allocate new stack\n");
} else {
fprintf(stderr, "stack_grow: failed to allocate new stack of size %zu\n",
new_usable_size);
}
return false;
}
uintptr_t old_sp = CEM_CONTEXT_GET_SP(&strand->context);
uintptr_t old_stack_top =
(uintptr_t)old_meta->usable_base + old_meta->usable_size;
size_t used_bytes = (size_t)(old_stack_top - old_sp);
if (used_bytes > old_meta->usable_size) {
fprintf(stderr, "\n");
fprintf(stderr, "========================================\n");
fprintf(stderr, "FATAL: Stack pointer corruption detected\n");
fprintf(stderr, "========================================\n");
fprintf(stderr, "Strand ID: %llu\n", (unsigned long long)strand->id);
fprintf(stderr, "Stack size: %zu bytes\n", old_meta->usable_size);
fprintf(stderr, "Calculated usage: %zu bytes (SP is corrupted!)\n",
used_bytes);
fprintf(stderr, "Stack base: %p\n", old_meta->usable_base);
fprintf(stderr, "Stack top: %p\n", (void *)old_stack_top);
fprintf(stderr, "Current SP: %p\n", (void *)old_sp);
fprintf(stderr, "\n");
fprintf(stderr, "This indicates either:\n");
fprintf(stderr,
" 1. Memory corruption (buffer overflow, use-after-free)\n");
fprintf(stderr, " 2. Context switching bug\n");
fprintf(stderr, " 3. Stack metadata corruption\n");
fprintf(stderr, "\n");
fprintf(stderr, "Cannot continue safely. Aborting.\n");
fprintf(stderr, "========================================\n");
stack_free(new_meta);
abort(); }
uintptr_t new_stack_top =
(uintptr_t)new_meta->usable_base + new_meta->usable_size;
uintptr_t new_sp = new_stack_top - used_bytes;
memcpy((void *)new_sp, (void *)old_sp, used_bytes);
#ifdef CEM_ARCH_ARM64
strand->context.sp = new_sp;
if (strand->context.x29 >= (uintptr_t)old_meta->usable_base &&
strand->context.x29 <= old_stack_top) {
uintptr_t offset_from_top = old_stack_top - strand->context.x29;
strand->context.x29 = new_stack_top - offset_from_top;
}
#elif defined(CEM_ARCH_X86_64)
strand->context.rsp = new_sp;
if (strand->context.rbp >= (uintptr_t)old_meta->usable_base &&
strand->context.rbp <= old_stack_top) {
uintptr_t offset_from_top = old_stack_top - strand->context.rbp;
strand->context.rbp = new_stack_top - offset_from_top;
}
fprintf(stderr, "WARNING: x86-64 stack growth is INCOMPLETE and may crash\n");
fprintf(stderr, " Return addresses on stack are not adjusted!\n");
#else
#error "Unsupported architecture for dynamic stack growth"
#endif
uint32_t old_growth_count = old_meta->growth_count;
size_t old_usable_size = old_meta->usable_size;
stack_free(old_meta);
strand->stack_meta = new_meta;
new_meta->growth_count = old_growth_count + 1;
if (new_meta->growth_count <= 3 || (new_meta->growth_count % 10) == 0) {
if (in_signal_handler) {
signal_safe_write("INFO: Stack grew to ");
char buf[32];
size_to_str(new_meta->usable_size, buf, sizeof(buf));
signal_safe_write(buf);
signal_safe_write(" bytes\n");
} else {
fprintf(stderr,
"INFO: Strand %llu stack grew %zu -> %zu bytes (growth #%u)\n",
(unsigned long long)strand->id, old_usable_size,
new_meta->usable_size, new_meta->growth_count);
}
}
return true;
}
bool stack_check_and_grow(struct Strand *strand, uintptr_t current_sp) {
assert(strand != NULL);
assert(strand->stack_meta != NULL);
StackMetadata *meta = strand->stack_meta;
size_t used = stack_get_used(meta, current_sp);
size_t free = stack_get_free(meta, current_sp);
bool need_growth = false;
const char *reason = NULL;
if (free < CEM_MIN_FREE_STACK) {
need_growth = true;
reason = "free space below minimum";
} else if (used >
(meta->usable_size * CEM_STACK_GROWTH_THRESHOLD_PERCENT / 100)) {
need_growth = true;
reason = "usage above threshold";
}
if (!need_growth) {
return false;
}
if (meta->usable_size > SIZE_MAX / 2) {
fprintf(stderr,
"ERROR: Strand %llu stack size %zu cannot be doubled (overflow)\n",
(unsigned long long)strand->id, meta->usable_size);
return false;
}
size_t new_size = meta->usable_size * 2;
fprintf(
stderr,
"INFO: Strand %llu growing stack (%s): %zu/%zu bytes used, %zu free\n",
(unsigned long long)strand->id, reason, used, meta->usable_size, free);
return stack_grow(strand, new_size, false); }
static void signal_safe_write(const char *str) {
extern ssize_t write(int, const void *, size_t);
size_t len = 0;
while (str[len])
len++;
write(2, str, len); }
static void size_to_str(size_t n, char *buf, size_t bufsize) {
if (bufsize == 0)
return;
if (n == 0) {
buf[0] = '0';
buf[1] = '\0';
return;
}
size_t i = 0;
while (n > 0 && i < bufsize - 1) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
buf[i] = '\0';
for (size_t j = 0; j < i / 2; j++) {
char tmp = buf[j];
buf[j] = buf[i - 1 - j];
buf[i - 1 - j] = tmp;
}
}
bool stack_is_guard_page_fault(uintptr_t addr, const StackMetadata *meta) {
if (!meta || !meta->base) {
return false;
}
uintptr_t guard_start = (uintptr_t)meta->base;
uintptr_t guard_end = guard_start + meta->guard_page_size;
return (addr >= guard_start && addr < guard_end);
}
static void stack_sigsegv_handler(int sig, siginfo_t *si, void *unused) {
(void)sig;
(void)unused;
uintptr_t fault_addr = (uintptr_t)si->si_addr;
if (g_scheduler && g_scheduler->current_strand) {
Strand *strand = g_scheduler->current_strand;
if (stack_is_guard_page_fault(fault_addr, strand->stack_meta)) {
signal_safe_write("\n");
signal_safe_write("========================================\n");
signal_safe_write("WARNING: Guard page hit!\n");
signal_safe_write("========================================\n");
signal_safe_write("This indicates the checkpoint heuristic failed to "
"predict stack growth.\n");
signal_safe_write(
"The stack will be grown now, but this is a FALLBACK mechanism.\n");
signal_safe_write(
"Consider tuning CEM_MIN_FREE_STACK if this happens frequently.\n");
signal_safe_write("\n");
strand->stack_meta->guard_hit = true;
size_t new_size = strand->stack_meta->usable_size * 2;
if (stack_grow(strand, new_size,
true)) { signal_safe_write("INFO: Emergency growth succeeded\n");
return;
} else {
signal_safe_write(
"FATAL: Emergency growth failed - strand will crash\n");
}
}
}
signal_safe_write("SIGSEGV: not a guard page fault\n");
signal(SIGSEGV, SIG_DFL);
raise(SIGSEGV);
}
void stack_guard_init_signal_handler(void) {
struct sigaction sa;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = stack_sigsegv_handler;
if (sigaction(SIGSEGV, &sa, NULL) == -1) {
fprintf(stderr,
"WARNING: Failed to install SIGSEGV handler for guard pages\n");
fprintf(
stderr,
" Stack overflow detection will be limited to checkpoints only.\n");
}
}
void stack_guard_set_scheduler(struct Scheduler *scheduler) {
g_scheduler = (Scheduler *)scheduler;
}