Disclaimer: Beginners' content.

There are two ways to understand how memory allocation works:

  • Look at the code of malloc and all the syscalls used. You can find both of these here and here.
  • Or, wonder why the heck do I have to read 8k lines to understand how memory allocation works, and then do as follows:

Write the following code in your favorite editor.

#include <stdio.h>

int main(void)
{
    int i = 0;
    i = 2;
    return 0;
}

Compile it: gcc -g test.c
Run GDB with the compiled file: gdb ./a.out
Set a breakpoint on main() function: break main
Run: run
See how the process memory layout looks like: info proc mappings
(More on what this memory layout actually means some other day)

You will see something like this:

(gdb) info proc mappings
process 10741
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
            0x400000           0x401000     0x1000        0x0 /home/ubuntu/heap/a.out
            0x600000           0x601000     0x1000        0x0 /home/ubuntu/heap/a.out
            0x601000           0x602000     0x1000     0x1000 /home/ubuntu/heap/a.out
      0x7ffff7a0d000     0x7ffff7bcd000   0x1c0000        0x0 /lib/x86_64-linux-gnu/libc-2.23.so
      0x7ffff7bcd000     0x7ffff7dcd000   0x200000   0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
      0x7ffff7dcd000     0x7ffff7dd1000     0x4000   0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
      0x7ffff7dd1000     0x7ffff7dd3000     0x2000   0x1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
      0x7ffff7dd3000     0x7ffff7dd7000     0x4000        0x0
      0x7ffff7dd7000     0x7ffff7dfd000    0x26000        0x0 /lib/x86_64-linux-gnu/ld-2.23.so
      0x7ffff7fea000     0x7ffff7fed000     0x3000        0x0
      0x7ffff7ff6000     0x7ffff7ff8000     0x2000        0x0
      0x7ffff7ff8000     0x7ffff7ffa000     0x2000        0x0 [vvar]
      0x7ffff7ffa000     0x7ffff7ffc000     0x2000        0x0 [vdso]
      0x7ffff7ffc000     0x7ffff7ffd000     0x1000    0x25000 /lib/x86_64-linux-gnu/ld-2.23.so
      0x7ffff7ffd000     0x7ffff7ffe000     0x1000    0x26000 /lib/x86_64-linux-gnu/ld-2.23.so
      0x7ffff7ffe000     0x7ffff7fff000     0x1000        0x0
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]

From the third line, you can see that the "heap" ends at address 0x602000. Anything written outside of this address will result in an error. You can verify this by attempting to write a value on the next address, in this case, 0x602001.

set {int}0x602001 = 1234

I get Cannot access memory at address 0x602001.

To write anything outside of this address, all we have to do is to explicitly ask linux to allow us to do that. malloc does that for us. But it also does a whole lot of other stuff we don't need right now. Instead we'll use sbrk function, which increases the heap size by a given amount. For example, look at the following program.

#include <stdint.h>
#include <unistd.h>

int main(void)
{
	int i = 0;
	i = 2;
    sbrk(10);    //ask for 10 more bytes
	return 0;
}

Compile and run in GDB the same way as we did for the previous program. In GDB:

break 8
break 9
run
info proc mappings

On the first breakpoint, try to write to the outsider address and verify you still get the error.

Continue to next breakpoint (i.e. after sbrk): continue
Check memory layout again: info proc mappings

(gdb) info proc mappings
process 11076
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
            0x400000           0x401000     0x1000        0x0 /home/ubuntu/heap/a.out
            0x600000           0x601000     0x1000        0x0 /home/ubuntu/heap/a.out
            0x601000           0x602000     0x1000     0x1000 /home/ubuntu/heap/a.out
            0x602000           0x603000     0x1000        0x0 [heap]
      0x7ffff7a0d000     0x7ffff7bcd000   0x1c0000        0x0 /lib/x86_64-linux-gnu/libc-2.23.so
      0x7ffff7bcd000     0x7ffff7dcd000   0x200000   0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
      0x7ffff7dcd000     0x7ffff7dd1000     0x4000   0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
      0x7ffff7dd1000     0x7ffff7dd3000     0x2000   0x1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
      0x7ffff7dd3000     0x7ffff7dd7000     0x4000        0x0
      0x7ffff7dd7000     0x7ffff7dfd000    0x26000        0x0 /lib/x86_64-linux-gnu/ld-2.23.so
      0x7ffff7fea000     0x7ffff7fed000     0x3000        0x0
      0x7ffff7ff6000     0x7ffff7ff8000     0x2000        0x0
      0x7ffff7ff8000     0x7ffff7ffa000     0x2000        0x0 [vvar]
      0x7ffff7ffa000     0x7ffff7ffc000     0x2000        0x0 [vdso]
      0x7ffff7ffc000     0x7ffff7ffd000     0x1000    0x25000 /lib/x86_64-linux-gnu/ld-2.23.so
      0x7ffff7ffd000     0x7ffff7ffe000     0x1000    0x26000 /lib/x86_64-linux-gnu/ld-2.23.so
      0x7ffff7ffe000     0x7ffff7fff000     0x1000        0x0
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]

If you see a [heap] line added on the fourth position, congrats, you just allocated memory. Try to write to the address 0x602001 again, and this time you will get no errors. You can verify the value is indeed written by x 0x602001.

Also, the Size of the [heap] section isn't exactly 10 as we asked. Linux won't give you anything less than a page. If you attempt to do another sbrk(10), it will fit into this same page.

With that said, NEVER use sbrk to allocate memory in real programs. There is memory allocation, and then there is management of the allocated area. There is also freeing the allocated memory, and also using mmap to allocate large amounts of memory. malloc takes care of everything needed.