【C 语⾔】brk (),sbrk ()⽤法详解
brk() , sbrk() 的声明如下:
[cpp]
01. #include <unistd.h>  02. int brk(void *addr);  03. void *sbrk(intptr_t increment);
这两个函数都⽤来改变 "program break" (程序间断点)的位置,这个位置可参考下图:
如 man ⾥说的:
引⽤brk()  and  sbrk() change the location of the program break, which defines the end of the process's data segment (i.e., the program
break is the first location after the end of the uninitialized data segment).
brk() 和 sbrk() 改变 "program brek" 的位置,这个位置定义了进程数据段的终⽌处(也就是说,program b
reak 是在未初始化数据段终⽌处后的第⼀个位置)。
如此翻译过来,似乎会让⼈认为这个 program break 是和上图中⽭盾的,上图中的 program break 是在堆的增长⽅向的第⼀个位置处(堆和栈的增长⽅向是相对的),⽽按照说明⼿册来理解,似乎是在 bss segment 结束那⾥(因为未初始化数据段⼀般认为是 bss segment)。
⾸先说明⼀点,⼀个程序⼀旦编译好后,text segment ,data segment 和 bss segment 是确定下来的,这也可以通过 objdump 观察到。下⾯通过⼀个程序来测试这个 program break 是不是在 bss segment 结束那⾥:
[cpp]
01. #include <stdio.h>
02. #include <unistd.h>
03. #include <stdlib.h>
04. #include <sys/time.h>
05. #include <sys/resource.h>
06.
07.
08. int bssvar;    //声明⼀个味定义的变量,它会放在 bss segment 中
09.
10.
11. int main(void)
12. {
13. char *pmem;
14. long heap_gap_bss;
15.
16.
17.    printf ("end of bss section:%p\n", (long)&bssvar + 4);
18.
19.
20.    pmem = (char *)malloc(32);          //从堆中分配⼀块内存区,⼀般从堆的开始处获取
21. if (pmem == NULL) {
22.        perror("malloc");
23.        exit (EXIT_FAILURE);
24.    }
25.
26.
27.    printf ("pmem:%p\n", pmem);
28.
29.
30. //计算堆的开始地址和 bss segment 结束处得空隙⼤⼩,注意每次加载程序时这个空隙都是变化的,但是在同⼀次加载中它不会改变
31.    heap_gap_bss = (long)pmem - (long)&bssvar - 4;
32.    printf ("1-gap between heap and bss:%lu\n", heap_gap_bss);
33.
34.
35.    free (pmem);  //释放内存,归还给堆
36.
37.    sbrk(32);        //调整 program break 位置(假设现在不知道这个位置在堆头还是堆尾)
38.      pmem = (char *)malloc(32);  //再⼀次获取内存区
39. if (pmem == NULL) {
40.                perror("malloc");
41.                exit (EXIT_FAILURE);
42.        }
43.
44.
45.        printf ("pmem:%p\n", pmem);  //检查和第⼀次获取的内存区的起始地址是否⼀样
46.    heap_gap_bss = (long)pmem - (long)&bssvar - 4;  //计算调整 program break 后的空隙
47.    printf ("2-gap between heap and bss:%lu\n", heap_gap_bss);
48.
49.
50.    free(pmem);  //释放
51. return 0;
52. }
下⾯,我们分别运⾏两次程序,并查看其输出:
引⽤
[beyes@localhost C]$ ./sbrk  end of bss section:0x8049938 pmem:0x82ec008 1-gap between heap and bss:2762448
pmem:0x82ec008 2-gap between heap and bss:2762448 [beyes@localhost C]$ ./sbrk  end of bss section:0x8049938
pmem:0x8dbc008 1-gap between heap and bss:14100176 pmem:0x8dbc008 2-gap between heap and bss:14100176
从上⾯的输出中,可以发现⼏点:
1. bss 段⼀旦在在程序编译好后,它的地址就已经规定下来。
2. ⼀般及简单的情况下,使⽤ malloc() 申请的内存,释放后,仍然归还回原处,再次申请同样⼤⼩的内存区时,还是从第 1 次那⾥获得。
3. bss segment 结束处和堆的开始处的空隙⼤⼩,并不因为 sbrk() 的调整⽽改变,也就是说明了 program break 不是调整堆头部。
所以,man ⼿册⾥所说的  “program break 是在未初始化数据段终⽌处后的第⼀个位置” ,不能将这个
位置理解为堆头部。这时,可以猜想应该是在堆尾部,也就是堆增长⽅向的最前⽅。下⾯⽤程序进⾏检验:
当 sbrk() 中的参数为 0 时,我们可以到 program break 的位置。那么根据这⼀点,检查⼀下每次在程序加载时,系统给堆的分配是不是等同⼤⼩的:
01. #include <stdio.h>
02. #include <unistd.h>
03. #include <stdlib.h>
04. #include <sys/time.h>
05. #include <sys/resource.h>
06.
07.
08. int main(void)
09. {
10. void *tret;
11. char *pmem;
12.
13.
14.
15.        pmem = (char *)malloc(32);
16. if (pmem == NULL) {
17.                perror("malloc");
18.                exit (EXIT_FAILURE);
19.        }
20.
21.
22.        printf ("pmem:%p\n", pmem);
23.
24.        tret = sbrk(0);
c语言char的用法25. if (tret != (void *)-1)
26.                printf ("heap size on each load: %lu\n", (long)tret - (long)pmem);
27.
28.
29. return 0;
30. }
运⾏上⾯的程序 3 次:
引⽤
[beyes@localhost C]$ ./sbrk  pmem:0x80c9008 heap size on each load: 135160 [beyes@localhost C]$ ./sbrk  pmem:0x9682008
heap size on each load: 135160 [beyes@localhost C]$ ./sbrk  pmem:0x9a7d008 heap size on each load: 135160 [beyes@localhost
C]$ ./sbrk  pmem:0x8d92008 heap size on each load: 135160 [beyes@localhost C]$ vi sbrk.c
从输出可以看到,虽然堆的头部地址在每次程序加载后都不⼀样,但是每次加载后,堆的⼤⼩默认分配是⼀致的。但是这不是不能改的,可以使⽤sysctl 命令修改⼀下内核参数:
引⽤
#sysctl -w kernel/randomize_va_space=0
这么做之后,再运⾏ 3 次这个程序看看:
引⽤
[beyes@localhost C]$ ./sbrk  pmem:0x804a008 heap size on each load: 135160 [beyes@localhost C]$ ./sbrk  pmem:0x804a008
heap size on each load: 135160 [beyes@localhost C]$ ./sbrk  pmem:0x804a008 heap size on each load: 135160
从输出看到,每次加载后,堆头部的其实地址都⼀样了。但我们不需要这么做,每次堆都⼀样,容易带来缓冲区溢出攻击(以前⽼的 linux 内核就是特定地址加载的),所以还是需要保持 randomize_va_space 这个内核变量值为 1 。
下⾯就来验证 sbrk() 改变的 program break 位置在堆的增长⽅向处:
01. #include <stdio.h>
02. #include <unistd.h>
03. #include <stdlib.h>
04. #include <sys/time.h>
05. #include <sys/resource.h>
06.
07.
08. int main(void)
09. {
10. void *tret;
11. char *pmem;
12. int i;
13. long sbrkret;
14.
15.        pmem = (char *)malloc(32);
16. if (pmem == NULL) {
17.                perror("malloc");
18.                exit (EXIT_FAILURE);
19.        }
20.
21.
22.        printf ("pmem:%p\n", pmem);
23.
24. for (i = 0; i < 65; i++) {
25.                sbrk(1);
26.                printf ("%d\n", sbrk(0) - (long)pmem - 0x20ff8);  //0x20ff8 就是堆和 bss段之间的空隙常数;改变后要⽤ sbrk(0) 再次获取更
新后的program break位置
27.        }
28.        free(pmem);
29.
30.
31. return 0;
32. }
运⾏输出:
引⽤
[beyes@localhost C]$ ./sbrk  pmem:0x804a008 1 2 3 4 5
... ... 61 62 63 64
从输出看到,sbrk(1) 每次让堆往栈的⽅向增加 1 个字节的⼤⼩空间。
⽽ brk() 这个函数的参数是⼀个地址,假如你已经知道了堆的起始地址,还有堆的⼤⼩,那么你就可以据此修改 brk() 中的地址参数已达到调整堆的⽬的。
实际上,在应⽤程序中,基本不直接使⽤这两个函数,取⽽代之的是 malloc() ⼀类函数,这⼀类库函数的执⾏效率会更⾼。还需要注意⼀点,当使⽤ malloc() 分配过⼤的空间,⽐如超出 0x20ff8 这个常数(在我的系统(Fedora15)上是这样,别的系统可能会有变)时,malloc 不再从堆中分配空间,
⽽是使⽤ mmap() 这个系统调⽤从映射区寻可⽤的内存空间。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。