I need to explain and Clarifying the Confusion first.
man 2 brk
documents the C library wrapper, not the raw syscall interface.
The raw syscall interface (via syscacll(SYS_brk,..))
differs subtly:
It always returns the new program break (on success), rather than 0 or -1.
This makes it much more similar in behavior to sbrk()
.
So, if you do:
uintptr_t brk = syscall(SYS_brk, 0);
You get the current program break, exactly like sbrk(0)
.
NOW WHAT SYS_brk
ACTUALLY RETURNS ?
From the Linux Source, especially in MUSL and glibc. The raw syscall behaves like this comment that I write:
// Sets the program break to `addr`.
// If `addr` == 0, it just returns the current break.
// On success: returns the new program break (same as `addr` if successful)
// On failure: returns the old program break (unchanged), which is != requested
NOW, WE NEED TO GET THE syscall-specific behavior
You will not find this clarified in man 2 brk
, but you can find the low-level syscall behavior desciribed in these places:
Linux Kernel Source Code :
You can check the syscall implementation in
fs/proc/array.c or mm/mmap.c or mm/mmap_brk.c+
run it on your terminal or bash.
Depending on kernel version, As of the recent kernels:
SYSCALL_DEFINE1(brk, unsigned long, brk)
Which returns the new program break address, or the previous one if the request failed.
man syscall
+ unistd.h
+ asm/unistd_64.h
This actualy syscall interface is:
long syscall(long number, ...);
And for the SYS_brk
, the syscall number is found via:
#include <sys/syscall.h>
#define SYS_brk ...
Libc implementation (MUSL or glibc)
Before, you noticed:
uintptr_t brk = __brk(0);
In MUSL, __brk()
is typically a thin wrapper around:
syscall(SYS_brk, arg);
That means __brk(0)
gets the current break safely, and __brk(addr)
sets it.
REMINDER : MUSL does not follow the man 2 brk
behavior, instead it uses the raw syscall return value.
I also have an example of using syscall(SYS_brk,...)
in C directly:
Here's a minimal example in C that directly uses the raw syscall(SYS_brk, ...)
to Get the current program break, Attempt to increase it by 1 MB, and then reset it back to the original value.
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <stdint.h>
int main() {
// Get current break (same as sbrk(0))
uintptr_t curr_brk = (uintptr_t) syscall(SYS_brk, 0);
printf("Current program break: %p\n", (void *)curr_brk);
// Try to increase the break by 1 MB
uintptr_t new_brk = curr_brk + 1024 * 1024;
uintptr_t result = (uintptr_t) syscall(SYS_brk, new_brk);
if (result == new_brk) {
printf("Successfully increased break to: %p\n", (void *)result);
} else {
printf("Failed to increase break, still at: %p\n", (void *)result);
}
// Restore the original break
syscall(SYS_brk, curr_brk);
printf("Restored program break to: %p\n", (void *)curr_brk);
return 0;
}
You can read more documentation on :
https://man7.org/linux/man-pages/man2/syscall.2.html
https://elixir.bootlin.com/linux/v6.16/source/mm/mmap.c