blob: fca0506845f527efcf53b3eb41cca665494d5a1d [file]
#include <pthread.h>
#include <stdlib.h>
// A simple, deterministic nested call chain. We stop at the innermost function
// and walk the stack. The chain runs on a new thread to avoid the interference
// from lldb reading from main thread stack before main.
//
// The breakpoint is in the innermost frame (func_e), and that frame carries
// locals of every kind, so that examining *just the stopped frame* already
// exercises the various memory-read paths in one frame:
// - scalar locals (int / long / double)
// - aggregate locals (a struct and a fixed stack array)
// - a variable-length array (dynamically sized stack storage, like alloca)
// - pointer locals, including a pointer to heap memory
// - stack-passed parameters
//
// The outer frames (func_d / func_c) also carry locals of these kinds, so that
// walking the whole stack and examining every frame reads the same variety of
// memory across several frames.
//
// The frame-pointer backchain is expedited at the public stop, so the backtrace
// itself is packet-free; reading the *values* of these locals is not expedited
// and must read memory from the stub.
#define HEAP_COUNT 8
// A volatile sink so the locals are observably used.
volatile long g_sink;
struct Stats {
long sum;
long min;
long max;
double mean;
};
// A large by-value struct. Passed as an argument it does not fit in registers,
// so it is passed on the stack, above the callee's frame in the caller.
struct Big {
long v[8];
};
// The innermost frame, where we stop. It carries several kind of local: a
// scalar, aggregates (struct + array), pointers (including one into heap
// memory) and a variable-length array. Examining this single frame on a stop
// reads both stack and heap memory.
static int func_e(int depth, int a1, int a2, int a3, int a4, int a5, int a6,
int a7, int a8, int a9, struct Big big) {
int i = depth + 1;
long l = (long)depth * 1000;
double d = depth + 0.5;
struct Stats stats = {.sum = i, .min = i - 1, .max = i + 1, .mean = d};
long arr[4] = {i, i + 1, i + 2, i + 3};
int n = i + 4; // a runtime bound, so vla is a true variable-length array
long vla[n]; // dynamically sized stack storage (like alloca)
for (int k = 0; k < n; ++k)
vla[k] = (long)i - k;
long *heap = (long *)malloc(sizeof(long) * HEAP_COUNT);
for (int k = 0; k < HEAP_COUNT; ++k)
heap[k] = (long)i + k;
const char *str = "hello from func_e";
int *self = &i;
g_sink = i + l + (long)d + stats.sum + arr[3] + vla[n - 1] +
heap[HEAP_COUNT - 1] + str[0] + *self + a8 + a9 +
big.v[7]; // break here
int r = i + (int)l + (int)d + (int)stats.sum + (int)arr[3] + (int)vla[n - 1] +
(int)heap[HEAP_COUNT - 1] + str[0] + *self + a8 + a9 + (int)big.v[7];
free(heap);
return r;
}
// Aggregate locals: a struct and a fixed-size stack array.
static int func_d(int x) {
struct Stats stats = {.sum = x, .min = x - 1, .max = x + 1, .mean = x + 0.5};
long arr[4] = {x, x + 1, x + 2, x + 3};
struct Big big;
for (int k = 0; k < 8; ++k)
big.v[k] = 100 + k;
int r = func_e(x, x + 1, x + 2, x + 3, x + 4, x + 5, x + 6, x + 7, x + 8,
x + 9, big);
return r + (int)stats.sum + (int)arr[3];
}
// Pointer locals, including a pointer into heap memory.
static int func_c(int x) {
long *heap = (long *)malloc(sizeof(long) * HEAP_COUNT);
for (int i = 0; i < HEAP_COUNT; ++i)
heap[i] = (long)x + i;
const char *str = "hello from func_c";
int *self = &x;
int r = func_d(x);
r += (int)heap[HEAP_COUNT - 1] + (int)str[0] + *self;
free(heap);
return r;
}
static int func_b(int x) { return func_c(x) + 1; }
static int func_a(int x) { return func_b(x) + 1; }
static void *thread_func(void *arg) {
g_sink = func_a(0);
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, thread_func, NULL);
pthread_join(thread, NULL);
return 0;
}