brk(), sbrk() 用法详解

2023-11-06

贴上原文地址,好不容易找到了:brk(), sbrk() -- 改变数据段长度


brk() , sbrk() 的声明如下:

#include <unistd.h>
int brk(void *addr);
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 break 是在未初始化数据段终止处后的第一个位置)。
如此翻译过来,似乎会让人认为这个 program break 是和上图中矛盾的,上图中的 program break 是在堆的增长方向的第一个位置处(堆和栈的增长方向是相对的),而按照说明手册来理解,似乎是在 bss segment 结束那里(因为未初始化数据段一般认为是 bss segment)。


首先说明一点,一个程序一旦编译好后,text segment ,data segment 和 bss segment 是确定下来的,这也可以通过 objdump 观察到。下面通过一个程序来测试这个 program break 是不是在 bss segment 结束那里:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
 
 
int bssvar;    //声明一个味定义的变量,它会放在 bss segment 中
 
 
int main(void)
{
    char *pmem;
    long heap_gap_bss;
 
 
    printf ("end of bss section:%p\n", (long)&bssvar + 4);
 
 
    pmem = (char *)malloc(32);          //从堆中分配一块内存区,一般从堆的开始处获取
    if (pmem == NULL) {
        perror("malloc");
        exit (EXIT_FAILURE);
    }
 
 
    printf ("pmem:%p\n", pmem);
 
 
//计算堆的开始地址和 bss segment 结束处得空隙大小,注意每次加载程序时这个空隙都是变化的,但是在同一次加载中它不会改变
    heap_gap_bss = (long)pmem - (long)&bssvar - 4;          
    printf ("1-gap between heap and bss:%lu\n", heap_gap_bss);
 
 
    free (pmem);   //释放内存,归还给堆
     
    sbrk(32);        //调整 program break 位置(假设现在不知道这个位置在堆头还是堆尾)
     pmem = (char *)malloc(32);   //再一次获取内存区
        if (pmem == NULL) {
                perror("malloc");
                exit (EXIT_FAILURE);
        }
 
 
        printf ("pmem:%p\n", pmem);   //检查和第一次获取的内存区的起始地址是否一样
    heap_gap_bss = (long)pmem - (long)&bssvar - 4;  //计算调整 program break 后的空隙
    printf ("2-gap between heap and bss:%lu\n", heap_gap_bss);
 
 
    free(pmem);   //释放
    return 0;
}

下面,我们分别运行两次程序,并查看其输出:


引用
[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 的位置。那么根据这一点,检查一下每次在程序加载时,系统给堆的分配是不是等同大小的:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
 
 
int main(void)
{
        void *tret;
        char *pmem;
 
 
 
        pmem = (char *)malloc(32);
        if (pmem == NULL) {
                perror("malloc");
                exit (EXIT_FAILURE);
        }
 
 
        printf ("pmem:%p\n", pmem);
 
        tret = sbrk(0);
        if (tret != (void *)-1)
                printf ("heap size on each load: %lu\n", (long)tret - (long)pmem);
 
 
    return 0;
}

运行上面的程序 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 位置在堆的增长方向处:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
 
 
int main(void)
{
        void *tret;
        char *pmem;
        int i;
        long sbrkret;
 
       pmem = (char *)malloc(32);
        if (pmem == NULL) {
                perror("malloc");
                exit (EXIT_FAILURE);
        }
 
 
        printf ("pmem:%p\n", pmem);
 
         for (i = 0; i < 65; i++) {
                sbrk(1);
                printf ("%d\n", sbrk(0) - (long)pmem - 0x20ff8);   //0x20ff8 就是堆和 bss段 之间的空隙常数;改变后要用 sbrk(0) 再次获取更新后的program break位置
        }
       free(pmem);
 
        
       return 0;
}

运行输出:

引用
[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() 这个系统调用从映射区寻找可用的内存空间。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

brk(), sbrk() 用法详解 的相关文章

  • 适合初学者的良好调试器教程[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 有谁知道一个好的初学者教程 在 C 中使用调试器 我感觉自己好像错过了很多 我知道怎么做 单步执行代码并查看局部变量 虽然这常常给我带来问
  • 使用 C# 登录《我的世界》

    我正在尝试为自己和一些朋友创建一个简单的自定义 Minecraft 启动器 我不需要启动 Minecraft 的代码 只需要登录的实际代码行 例如 据我所知 您过去可以使用 string netResponse httpGET https
  • 如何捕获未发送到 stdout 的命令行文本?

    我在项目中使用 LAME 命令行 mp3 编码器 我希望能够看到某人正在使用什么版本 如果我只执行 LAME exe 而不带参数 我会得到 例如 C LAME gt LAME exe LAME 32 bits version 3 98 2
  • GetType() 在 Type 实例上返回什么?

    我在一些调试过程中遇到了这段代码 private bool HasBaseType Type type out Type baseType Type originalType type GetType baseType GetBaseTyp
  • IdentityServer 4 对它的工作原理感到困惑

    我阅读和观看了很多有关 Identity Server 4 的内容 但我仍然对它有点困惑 因为似乎有很多移动部件 我现在明白这是一个单独的项目 它处理用户身份验证 我仍然不明白的是用户如何注册它 谁存储用户名 密码 我打算进行此设置 Rea
  • JNI 将 Char* 2D 数组传递给 JAVA 代码

    我想从 C 代码通过 JNI 层传递以下指针数组 char result MAXTEST MAXRESPONSE 12 12 8 3 29 70 5 2 42 42 在java代码中我写了以下声明 public static native
  • 从同一个类中的另一个构造函数调用构造函数

    我有一个带有两个构造函数的类 C 这是代码片段 public class FooBar public FooBar string s constructor 1 some functionality public FooBar int i
  • 使用可变参数包类型扩展的 C++ 函数调用者包装器

    我绑定了一些 API 并且绑定了一些函数签名 如下所示 static bool WrapperFunction JSContext cx unsigned argc JS Value vp 我尝试将对象和函数包装在 SpiderMonkey
  • File.AppendText 尝试写入错误的位置

    我有一个 C 控制台应用程序 它作为 Windows 任务计划程序中的计划任务运行 此控制台应用程序写入日志文件 该日志文件在调试模式下运行时会创建并写入应用程序文件夹本身内的文件 但是 当它在任务计划程序中运行时 它会抛出一个错误 指出访
  • 如何在 C 中安全地声明 16 位字符串文字?

    我知道已经有一个标准方法 前缀为L wchar t test literal L Test 问题是wchar t不保证是16位 但是对于我的项目 我需要16位wchar t 我还想避免通过的要求 fshort wchar 那么 C 不是 C
  • C++ int 前面加 0 会改变整个值

    我有一个非常奇怪的问题 如果我像这样声明一个 int int time 0110 然后将其显示到控制台返回的值为72 但是当我删除前面的 0 时int time 110 然后控制台显示110正如预期的那样 我想知道两件事 首先 为什么它在
  • 保护 APK 中的字符串

    我正在使用 Xamarin 的 Mono for Android 开发一个 Android 应用程序 我目前正在努力使用 Google Play API 添加应用内购买功能 为此 我需要从我的应用程序内向 Google 发送公共许可证密钥
  • 有谁知道在哪里定义硬件、版本和序列号。 /proc/cpuinfo 的字段?

    我想确保我的 proc cpuinfo 是准确的 目前它输出 Hardware am335xevm Revision 0000 Serial 0000000000000000 我可以在代码中的哪里更改它以给出实际值 这取决于 Linux 的
  • 打印大型 WPF 用户控件

    我有一个巨大的数据 我想使用 WPF 打印 我发现WPF提供了一个PrintDialog PrintVisual用于打印派生的任何 WPF 控件的方法Visual class PrintVisual只会打印一页 因此我需要缩放控件以适合页面
  • Unity:通过拦截将两个接口注册为一个单例

    我有一个实现两个接口的类 我想对该类的方法应用拦截 我正在遵循中的建议Unity 将两个接口注册为一个单例 https stackoverflow com questions 1394650 unity register two inter
  • 将数组作为参数传递

    如果我们修改作为方法内参数传递的数组的内容 则修改是在参数的副本而不是原始参数上完成的 因此结果不可见 当我们调用具有引用类型参数的方法时 会发生什么过程 这是我想问的代码示例 using System namespace Value Re
  • 实体框架中的“it”是什么

    如果以前有人问过这个问题 请原谅我 但我的任何搜索中都没有出现 它 我有两个数据库表 Person 和 Employee 对每个类型的表进行建模 例如 Employee is a Person 在我的 edmx 设计器中 我定义了一个实体
  • 在 Windows Phone silverlight 8.1 上接收 WNS 推送通知

    我有 Windows Phone 8 1 silverlight 应用程序 我想使用新框架 WNS 接收通知 我在 package appxmanifest 中有
  • GCC 的“-Wl,option”和“-Xlinker option”语法之间有区别吗?

    我一直在查看一些配置文件 并且看到它们都被使用 尽管在不同的体系结构上 如果您在 Linux 机器上使用 GCC 将选项传递给链接器的两种语法之间有区别吗 据我所知 阅读 GCC 手册时 他们的解释几乎相同 From man gcc Xli
  • 如何使用 C++11 using 语法键入定义函数指针?

    我想写这个 typedef void FunctionPtr using using 我该怎么做呢 它具有类似的语法 只不过您从指针中删除了标识符 using FunctionPtr void 这是一个Example http ideone

随机推荐