Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
45 changes: 45 additions & 0 deletions stackman/stackman.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
* "stackman_impl.h" instead.
*/

#include <stddef.h>
#include <stdint.h>

#include "platforms/platform.h"
#include "stackman_switch.h"

Expand Down Expand Up @@ -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 */
38 changes: 38 additions & 0 deletions tests/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
43 changes: 43 additions & 0 deletions tests/test_block_asc.c
Original file line number Diff line number Diff line change
@@ -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 <assert.h>
#include <stdint.h>
#include <stdio.h>

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;
}
Loading