I want to know how macOS allocate stack and heap memory for a process, i.e. the memory layout of a process in macOS. I only know that the segments of a mach-o executable are loaded into pages, but I can't find a segment that correspond to stack or heap area of a process. Is there any document about that?
2 Answers
Stacks and heaps are just memory. The only think that makes a stack a stack or a heap or a heap is the way it is accessed. Stacks and heaps are allocated the same way all memory is: by mapping pages into the logical address space.

- 20,574
- 3
- 26
- 62
-
Yes. But in other operating systems, stack and heap are in fixed positions in logical address space, like the top and the bottom. – Evian Jul 15 '19 at 00:27
-
@Evian: Most modern OSes randomize the stack start address, and also randomize `mmap()` return values. This is called ASLR. So no, they're not usually fixed. But on Linux for example, the stack is still near the top of user-space virtual address space; the top few bits of the address aren't randomized. – Peter Cordes Jul 15 '19 at 09:19
Let's take a step back - the Mach-o format describes mapping the binary segments into virtual memory. Importantly the memory pages you mentioned have read write and execute permissions. If it's an executable(i.e. not a dylib) it must contain the __PAGEZERO
segment with no permissions at all. This is the safe guard area to prevent accessing low addresses of virtual memory by accident (here falls the infamous Null pointer exception and such if attempting to access zero memory address).
__TEXT
read executable (typically without write) segment follows which in virtual memory will contain the file representation itself. This implies all the executable code lives here. Also immmutable data like string constants.
The order may vary, but usually next you will encounter __LINKEDIT
read only segment. This is the segment dyld
uses to setup externally loaded functions, this is too broad to cover here, but there are numerous answers on the topic.
Finally we have the readable writable __DATA
segment the first place a process can actually write to. This is used for global/static variables, external addresses to calls populated by dyld.
We have roughly covered the process initial setup when it will launch through either LC_UNIXTHREAD
or in modern MacOS (10.7+) LC_MAIN
. This starts the process main thread. Each thread must contain it's own stack. The creation of it is handled by operating system (including allocating it). Notice so far the process has no awareness of the heap at all (it's the operating system that's doing the heavy lifting to prepare the stack).
So to sum up so far we have 2 independent sources of memory - the process memory representing the Mach-o structure (size is fixed and determined by the executable structure) and the main thread stack (also with predefined size). The process is about to run a C-like main function , any local variables declared would move the thread stack pointer, likewise any calls to functions (local and external) to at least setup the stack frame for return address. Accessing a global/static variable would reference the __DATA
segment virtual memory directly.
Reserving stack space in x86-64 assembly would look like this:
sub rsp,16
There are some great SO anwers on System V / AMD64 ABI (which includes MacOS) requirements for stack alignment like this one
Any new thread created will have its own stack to allow setting up stack frames for local variables and calling functions.
Now we can cover heap allocation - which is mitigated by the libSystem
(aka MacOS C standard library) delivering the malloc
/free
. Internally this is handled by mmap
& munmap
system calls - the kernel API for managing memory pages.
Using those system calls directly is possible, but might turned out inefficient, thus an internal memory pool is utilised by malloc
/free
to limit the number of system calls (which are costly to make).
The changing addresses you mentioned in the comment are caused by:
- ASLR aka PIE (position independent code) for process memory , which is a security measure randomizing the start of virtual memory
- Thread local stacks being prepared by the operating system

- 5,205
- 2
- 22
- 51
-
The process memory and stack memory are 2 independent sources of memory, but do they share the same logical address space? Memory layout of Linux has been described in many websites, such as [this one on github](https://gist.github.com/CMCDragonkai/10ab53654b2aa6ce55c11cfc5b2432a4). Does stack or heap has a fixed position in the logical address space of a process? – Evian Jul 15 '19 at 08:51
-
1@Evian: yes, MacOS and all modern mainstream OSes use a flat memory model where all addresses in a process are part of the same 32-bit or 64-bit virtual address space. (x86-64 doesn't even support non-zero segment bases so you couldn't have a separate stack segment anyway.) Except for thread-local storage: TLS has storage relative to a per-thread offset into that flat address space. On x86 it's typically implemented with a FS or GS segment base. – Peter Cordes Jul 15 '19 at 09:15
-
1That general ordering is probably always used, like on Linux, but like this answer says they're randomized. "The heap" isn't a real thing. There's the break (`brk` / `sbrk`) if that's used at all on MacOS, and there's `mmap` which is randomized for every new mapping you request. (`malloc` / `new` typically use `mmap(MAP_ANONYMOUS)` to get new pages from the OS.) – Peter Cordes Jul 15 '19 at 09:22
-
@PeterCordes Are there any documentations about the FS and GS implementation? – Evian Jul 15 '19 at 09:22
-
1@Evian: For MacOS I don't know. It's very well documented how x86-64 Linux uses addresses like `%fs:0x10` for thread-local storage. If you don't know x86 assembly, I wouldn't worry about it. The FS base address is just a special register that the OS can program and user-space can use as an offset into the regular 64-bit virtual address space. In the big picture, it's no different from code on MIPS or something loading a per-thread offset from memory somewhere and using that. TLS has zero impact on the big picture of memory layout. segment->linear happens before paging virt->phys. – Peter Cordes Jul 15 '19 at 09:26
-