[译] malloc中的系统调用brk和mmap

阅读本文前你可能已经知道,malloc通过系统调用的方式从操作系统申请内存。事实上,malloc内部是通过系统调用brkmmap来申请内存的。如下面的进程虚拟内存布局图所示,mmap对应Memory Mapping Segment,brk对应Heap

进程虚拟内存布局图

brk

brk通过增加program break的位置(brk)从内核申请(非零值初始化的)内存。一开始,堆段(heap segment)的起始位置(start_brk)和结束位置(brk)指向同一个位置:

  • ASLR(Address Space Layout Randomization)关闭时,start_brk和brk同时指向data/bss段的结束位置(end_data)。
  • 当ASLR打开时,start_brk和brk同时指向data/bss段的结束位置(end_data)再加上一个随机的brk偏移。

上面的进程虚拟内存布局图展示了,start_brk是堆段的开始位置,brk(program break)则是堆段的结束位置。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* sbrk, brk 例子 */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
void *curr_brk, *tmp_brk = NULL;

printf("Welcome to sbrk example:%d\n", getpid());

/* sbrk(0) 获取当前 program break 位置 */
tmp_brk = curr_brk = sbrk(0);
printf("Program Break Location1:%p\n", curr_brk);
getchar();

/* 使用 brk 增加 program break 位置 */
brk(curr_brk+4096);

curr_brk = sbrk(0);
printf("Program break Location2:%p\n", curr_brk);
getchar();

/* 使用 brk 减小 program break 位置 */
brk(tmp_brk);

curr_brk = sbrk(0);
printf("Program Break Location3:%p\n", curr_brk);
getchar();

return 0;
}

在增加program break之前,输出如下:

1
2
3
4
5
6
7
8
9
sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ ./sbrk 
Welcome to sbrk example:6141
Program Break Location1:0x804b000
...
sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ cat /proc/6141/maps
...
0804a000-0804b000 rw-p 00001000 08:01 539624 /home/sploitfun/ptmalloc.ppt/syscalls/sbrk
b7e21000-b7e22000 rw-p 00000000 00:00 0
...
  • start_brk=brk=end_data=0x804b000

此时没有堆段。

译者yoko注,这里通过cat /proc/<pid>/maps的方式查看进程映射的内存区域,输出的含义下文会具体讲

在增加program break之后,输出如下:

1
2
3
4
5
6
7
8
9
10
11
sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ ./sbrk 
Welcome to sbrk example:6141
Program Break Location1:0x804b000
Program Break Location2:0x804c000
...
sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ cat /proc/6141/maps
...
0804a000-0804b000 rw-p 00001000 08:01 539624 /home/sploitfun/ptmalloc.ppt/syscalls/sbrk
0804b000-0804c000 rw-p 00000000 00:00 0 [heap]
b7e21000-b7e22000 rw-p 00000000 00:00 0
...
  • start_brk=end_data=0x804b000
  • brk=0x804c000

可以观察到堆段。

其中0804b000-0804c000 rw-p 00000000 00:00 0 [heap]的含义:

  • 0804b000-0804c000是这个堆段的虚拟地址范围。
  • rw-p标准的含义是Read, Write, NoeXecute, Private
  • 00000000是文件偏移量,由于并没有映射任何文件,所以为零
  • 00:00是major/minor device number,由于并没有映射任何文件,所以为零
  • 0是inode,由于并没有映射任何文件,所以为零
  • [heap]是堆段

mmap

malloc使用mmap创建一个私有匿名的映射段。这个映射段的主要目的是申请一块(零值初始化的)新内存,并且这块内存只能被调用的这个进程独占使用。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* 使用mmap系统调用做私有匿名映射的例子 */
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

void static inline errExit(const char* msg)
{
printf("%s failed. Exiting the process\n", msg);
exit(-1);
}

int main()
{
int ret = -1;
printf("Welcome to private anonymous mapping example::PID:%d\n", getpid());
printf("Before mmap\n");
getchar();
char* addr = NULL;
addr = mmap(NULL, (size_t)132*1024, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED)
errExit("mmap");
printf("After mmap\n");
getchar();

/* Unmap mapped region. */
ret = munmap(addr, (size_t)132*1024);
if(ret == -1)
errExit("munmap");
printf("After munmap\n");
getchar();
return 0;
}

调用mmap之前:如下输出我们可以看到,只有属于libc.sold-linux.so共享库的内存映射段。

1
2
3
4
5
6
sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ cat /proc/6067/maps
08048000-08049000 r-xp 00000000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap
08049000-0804a000 r--p 00000000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap
0804a000-0804b000 rw-p 00001000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap
b7e21000-b7e22000 rw-p 00000000 00:00 0
...

调用mmap之后:如下输出我们可以观察到,我们mmap映射的内存段(b7e00000–b7e21000,大小是132KB)和已经存在的内存映射段(b7e21000–b7e22000)合并了(变成b7e00000-b7e22000)。

1
2
3
4
5
6
sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ cat /proc/6067/maps
08048000-08049000 r-xp 00000000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap
08049000-0804a000 r--p 00000000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap
0804a000-0804b000 rw-p 00001000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap
b7e00000-b7e22000 rw-p 00000000 00:00 0
...

其中b7e00000-b7e22000 rw-p 00000000 00:00 0的含义:

  • b7e00000-b7e22000是这个段的虚拟地址范围
  • rw-p is Flags (Read, Write, NoeXecute, Private)
  • 00000000是文件偏移量,由于并没有映射任何文件,所以为零
  • 00:00是major/minor device number,由于并没有映射任何文件,所以为零
  • 0是inode,由于并没有映射任何文件,所以为零

调用munmap之后:如下输出我们可以看到,mmap映射的内存段已经被解除映射了,换言之,相应的已归还给操作系统。

1
2
3
4
5
6
sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/syscalls$ cat /proc/6067/maps
08048000-08049000 r-xp 00000000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap
08049000-0804a000 r--p 00000000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap
0804a000-0804b000 rw-p 00001000 08:01 539691 /home/sploitfun/ptmalloc.ppt/syscalls/mmap
b7e21000-b7e22000 rw-p 00000000 00:00 0
...

注意:我们以上所做实验,ASLR是关闭的。

英文原文地址:https://sploitfun.wordpress.com/2015/02/11/syscalls-used-by-malloc/

本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20032/

0%