From 8439898335673f82b3fe16671922b9362f0a354b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Fri, 16 Oct 2026 09:05:57 +0000 Subject: [PATCH] Add stackman_block_call for a malloc block. A static inline next to the stack-pointer macros. It turns a low address and a size into the far pointer stackman_call takes and the byte count toward near. --- AGENTS.md | 10 ++++++++-- CHANGELOG.md | 3 +++ Makefile | 6 ++++++ README.md | 6 ++++++ stackman/stackman.h | 45 ++++++++++++++++++++++++++++++++++++++++++ tests/test.c | 38 +++++++++++++++++++++++++++++++++++ tests/test_block_asc.c | 43 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 tests/test_block_asc.c diff --git a/AGENTS.md b/AGENTS.md index 22f404e..e6de47d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,6 +29,10 @@ out and CI looks green. Align a stack with `STACKMAN_SP_ALIGN`. It follows `STACKMAN_STACK_ALIGN` and the stack direction. +`stackman_block_call` turns a malloc block, a low address and a size, into +the pointer `stackman_call` takes and the byte count toward near. It is a +`static inline` in `stackman.h`. + ## Platforms Linux: `sysv_i386`, `sysv_amd64`, `arm32` (soft-float AAPCS), `armhf` @@ -100,8 +104,10 @@ make PLATFORM_PREFIX=arm-linux-gnueabihf EMULATOR=qemu-arm test `make clean` only when switching `PLATFORMFLAGS` or `PLATFORM_PREFIX`. Success ends with `*** All test suites passed ***`. -Four programs: `bin/test`, `bin/test_cc`, `bin/test_static`, -`bin/test_asm`. `tests/test.c` is `test_01` through `test_09`. `test_04` +Five programs: `bin/test`, `bin/test_cc`, `bin/test_static`, +`bin/test_asm`, and `bin/test_block_asc`. The last one compiles +`stackman_block_call` with `STACKMAN_STACK_DIR` set to 1. No supported +ABI grows upward. `tests/test.c` is `test_01` through `test_09`. `test_04` and `test_05` are the `stackman_call` contract. `test_07` and `test_08` pressure the compiler. They pass even when a callee-saved register is dropped, if the compiler kept the values in memory. `test_09` checks diff --git a/CHANGELOG.md b/CHANGELOG.md index 45aae06..6ab5939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- `stackman_block_call` turns a malloc block (low address and size) into the pointer `stackman_call` takes and the number of bytes toward near. + ## [1.3.0] - 2026-10-15 ### Added diff --git a/Makefile b/Makefile index 913436d..0712226 100644 --- a/Makefile +++ b/Makefile @@ -69,12 +69,14 @@ test: tests $(EMULATOR) bin/test_cc $(EMULATOR) bin/test_static $(EMULATOR) bin/test_asm + $(EMULATOR) bin/test_block_asc @echo "*** All test suites passed ***" tests: bin/test tests: bin/test_cc tests: bin/test_static tests: bin/test_asm +tests: bin/test_block_asc tests: LDLIBS := -lstackman bin/test: tests/test.o $(LIB)/libstackman.a @@ -88,3 +90,7 @@ bin/test_static: tests/test_static.o bin/test_asm: tests/test_asm.o tests/test_asm_s.o $(CC) $(LDFLAGS) $(STATIC_FLAG) -o $@ $^ ${DEBUG} -lm + +# Header-only. STACKMAN_STACK_DIR is set in the source. +bin/test_block_asc: tests/test_block_asc.o + $(CC) $(LDFLAGS) $(STATIC_FLAG) -o $@ $< ${DEBUG} diff --git a/README.md b/README.md index 999c87e..193661c 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,12 @@ This function is useful for at least three things: - To query the current stack pointer - To enforce an actual function call with stack pointer information. +A heap block from `malloc` is a low address and a size. `stackman_block_call()` +turns that pair into the aligned pointer `stackman_call` takes and the number +of bytes from there toward near. On a descending stack that pointer is the +high end of the block. The function returns 0 when the block is empty or +alignment leaves no usable byte. + The last feature is useful to bypass any in-lining that a compiler may do, when one really wants a proper function call with stack, for example, when setting up a new stack entry point. diff --git a/stackman/stackman.h b/stackman/stackman.h index 47e2f55..5e0c2f5 100644 --- a/stackman/stackman.h +++ b/stackman/stackman.h @@ -33,6 +33,9 @@ * "stackman_impl.h" instead. */ +#include +#include + #include "platforms/platform.h" #include "stackman_switch.h" @@ -121,4 +124,46 @@ #define STACKMAN_SP_ALIGN(a) STACKMAN_SP_ALIGN_UP(a) #endif +/* Turn a malloc block into the pointer stackman_call takes. + * + * base is the low address and size the byte count. *call_sp is the far + * end of that block, aligned for the stack direction. *extent is how many + * bytes of the block lie toward near from that pointer: + * [call_sp - extent, call_sp) when the stack grows down, and + * [call_sp, call_sp + extent) when it grows up. Bytes dropped while + * aligning the pointer are not part of extent. + * + * Returns 1 on success. Returns 0, and writes nothing, when base or + * either out-pointer is null, size is 0, base + size would wrap, or + * alignment leaves no usable byte. + */ +static inline int +stackman_block_call(void *base, size_t size, void **call_sp, size_t *extent) +{ + uintptr_t low, high, sp; + + if (base == 0 || size == 0 || call_sp == 0 || extent == 0) + return 0; + low = (uintptr_t)base; + if (low > UINTPTR_MAX - size) + return 0; + high = low + size; +#if STACKMAN_STACK_DIR == 0 + sp = (uintptr_t)STACKMAN_SP_ALIGN(high); + if (sp <= low || sp > high) + return 0; + *extent = (size_t)STACKMAN_SP_DIFF(sp, low); +#else + /* Aligning up adds STACKMAN_STACK_ALIGN - 1. */ + if (low > UINTPTR_MAX - (STACKMAN_STACK_ALIGN - 1)) + return 0; + sp = (uintptr_t)STACKMAN_SP_ALIGN(low); + if (sp < low || sp >= high) + return 0; + *extent = (size_t)STACKMAN_SP_DIFF(sp, high); +#endif + *call_sp = (void *)sp; + return 1; +} + #endif /* STACKMAN_H */ diff --git a/tests/test.c b/tests/test.c index 248b92c..6a4204d 100644 --- a/tests/test.c +++ b/tests/test.c @@ -366,6 +366,44 @@ void test_06() assert((void*)STACKMAN_SP_ALIGN(away) == away); +#if STACKMAN_STACK_DIR == 0 + { + unsigned char raw[64 + STACKMAN_STACK_ALIGN * 2]; + uintptr_t aligned; + unsigned char *base; + void *call_sp = (void *)(intptr_t)1; + size_t extent = 99; + + assert(stackman_block_call(0, 64, &call_sp, &extent) == 0); + assert(call_sp == (void *)(intptr_t)1 && extent == 99); + assert(stackman_block_call(raw, 0, &call_sp, &extent) == 0); + assert(call_sp == (void *)(intptr_t)1 && extent == 99); + assert(stackman_block_call(raw, 64, 0, &extent) == 0); + assert(extent == 99); + assert(stackman_block_call(raw, 64, &call_sp, 0) == 0); + assert(call_sp == (void *)(intptr_t)1); + assert(stackman_block_call((void *)(uintptr_t)(UINTPTR_MAX - 8), + 16, &call_sp, &extent) == 0); + assert(call_sp == (void *)(intptr_t)1 && extent == 99); + + aligned = ((uintptr_t)raw + STACKMAN_STACK_ALIGN - 1) & + ~(uintptr_t)(STACKMAN_STACK_ALIGN - 1); + base = (unsigned char *)aligned; + assert(stackman_block_call(base, 64, &call_sp, &extent) == 1); + assert(call_sp == base + 64); + assert(extent == 64); + assert((void *)STACKMAN_SP_ALIGN(call_sp) == call_sp); + + /* One byte is shorter than an aligned initial stack pointer. */ + assert(stackman_block_call(base, 1, &call_sp, &extent) == 0); + + /* The odd byte at the high end is the alignment sliver. */ + assert(stackman_block_call(base + 1, 64, &call_sp, &extent) == 1); + assert(call_sp == base + 64); + assert(extent == 63); + assert((void *)STACKMAN_SP_ALIGN(call_sp) == call_sp); + } +#endif } /* Test floating point register preservation across stack switches. diff --git a/tests/test_block_asc.c b/tests/test_block_asc.c new file mode 100644 index 0000000..fcfdab4 --- /dev/null +++ b/tests/test_block_asc.c @@ -0,0 +1,43 @@ +/* No supported ABI grows upward. This file compiles the ascending branch + * of stackman_block_call. It does not link libstackman. + */ +#define STACKMAN_STACK_DIR 1 +#include "stackman.h" + +#include +#include +#include + +int main(void) +{ + unsigned char raw[64 + STACKMAN_STACK_ALIGN * 2]; + uintptr_t aligned; + unsigned char *base; + void *call_sp; + size_t extent; + size_t size = 64; + + assert(STACKMAN_STACK_DIR == 1); + + aligned = ((uintptr_t)raw + STACKMAN_STACK_ALIGN - 1) & + ~(uintptr_t)(STACKMAN_STACK_ALIGN - 1); + base = (unsigned char *)aligned; + + assert(stackman_block_call(base, size, &call_sp, &extent) == 1); + assert(call_sp == (void *)base); + assert(extent == size); + assert((void *)STACKMAN_SP_ALIGN(call_sp) == call_sp); + + /* base+1 rounds up to the next alignment, and that gap is not extent. */ + assert(stackman_block_call(base + 1, size, &call_sp, &extent) == 1); + assert(call_sp == (void *)(base + STACKMAN_STACK_ALIGN)); + assert(extent == size - (STACKMAN_STACK_ALIGN - 1)); + + assert(stackman_block_call(base, 1, &call_sp, &extent) == 1); + assert(call_sp == (void *)base && extent == 1); + assert(stackman_block_call(base + 1, 1, &call_sp, &extent) == 0); + assert(stackman_block_call(0, size, &call_sp, &extent) == 0); + + printf("test_block_asc ok\n"); + return 0; +}