2026-06-15
When the kernel sets up a new process via execve(), it places more on the stack than just argc, argv, and envp. Above envp sits the auxiliary vector — an array of (type, value) pairs terminated by AT_NULL. It's how the kernel tells the dynamic linker and your program facts it would otherwise have to discover the hard way.
The stack layout at process entry looks like this:
argcargv[0..argc-1], NULLenvp[0..n], NULLauxv[0..m], {AT_NULL, 0}The most important entries:
__vdso_clock_gettime without a syscall.ifunc resolvers (e.g., the memcpy dispatcher) read to pick AVX-512 vs SSE2 at load time.sysconf(_SC_PAGESIZE) just reads this; no syscall.LD_PRELOAD and friends.Concrete example. Run this to see the vDSO base your kernel handed you:
#include <sys/auxv.h>
#include <stdio.h>
int main(void) {
printf("vDSO at %p\n", (void*)getauxval(AT_SYSINFO_EHDR));
printf("hwcap: %#lx\n", getauxval(AT_HWCAP));
}
Or peek at it externally: LD_SHOW_AUXV=1 /bin/ls prints every entry.
Rule of thumb. The auxv is typically 20–30 entries, ~500 bytes. If you ever need a fast, syscall-free way to read the page size, vDSO location, or CPU capabilities, getauxval() is just a pointer chase through memory the kernel already populated — roughly O(entries) with no kernel transition. Don't call sysconf() or uname() in a hot path when the answer is sitting on your stack.
Security note. Because AT_RANDOM lives at a known offset relative to the initial stack, attackers who can read the stack can recover the canary seed. Modern glibc consumes these bytes early and overwrites the slot.
main() ever runs.
