使用gdb+python查看C/C++进程内存分配情况

姓名:房小慧

学号:17101223361

专业:软件工程

转载自:https://m.sohu.com/a/206411984_575744/?pvid=000115_3w_a

【嵌牛导读】:内存使用和管理在C/C++程序中是一个无法绕开的问题,在gdb支持python 以后,我们就可以使用gdb这个新的特性来帮助我们查看在glibc ptmalloc算法中管理的内存的情况。

【嵌牛鼻子】:Glibc 内存管理

【嵌牛提问】:怎么用python管理内存?

【嵌牛正文】:

内存使用和管理在C/C++程序中是一个无法绕开的问题,在gdb支持python 以后,我们就可以使用gdb这个新的特性来帮助我们查看在glibc ptmalloc算法中管理的内存的情况。为了方便,下面我们主要针对x64环境。

在可以查看内存分配情况以前,我们当然需要知道ptmalloc算法大致是一个什么样子的。你只需要以ptmalloc analysis为关键字google一下就可以看到很多的相关文章,例如Glibc 内存管理或者 Understanding glibc malloc。

在这里我们大致介绍一下ptmalloc是如何管理内存的。ptmalloc的基本思想是将从内核分配出的大块连续内存(例如64M)拿出来,然后按照一定的大小切分成若干个小的内存块。

这些内存块用链表链接起来,当请求一块内存的时候,就从链表中查找出一个最小可以满足需求的块交给应用程序,如果没有合适的内存块,那么可能需要从更大的内存块中切分出来合适大小的内存块,链接到已有的链上,然后交给应用程序。

当应用程序free掉不再需要的内存块的时候,ptmalloc就会把这个内存块标记为free,并且在适当的时候查看到该内存块的周围的内存块也处于free状态的时候,那么ptmalloc会尝试将这些内存块合并成一个更大的内存块。

当然了ptmalloc里面还做了很多优化。例如,小内存块的使用是很频繁的,所以在ptmalloc里面针对小内存块设立了一个fastbin,专门使用单链表来记录这些小内存块,以方便在下次应用程序需要分配小内存块的时候可以更加迅速的得到响应。

为了可以了解ptmalloc的内存管理情况,有3个概念我们需要了解一下。

第一个是malloc_chunk, 如下是其定义

struct malloc_chunk { INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */struct malloc_chunk* fd; /* double links -- used only if free. */struct malloc_chunk* bk;

/* Only used for large blocks: pointer to next larger size. */struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */struct malloc_chunk* bk_nextsize;};

该malloc_chunk位于每一个小块内存的最前端,当该内存块处于被分配状态的时候,从fd开始的内存区域都属于用户数据区域。也就是说此时fd, bk, fd_nextsize, bk_nextsize都是无效的。

同时mchunk_size的低3位有特殊用途,最低位(称之为P)用于指示前一个内存块是否处于空闲状态,次低位(称之为M)表明当前的内存块是否来源于mmap,倒数第三个低位(称之为A)表示当前的内存块属于主分配区还是非主分配区。

mchunk_size&(~0x7)就是当前内存块的真实大小。而当该内存块处于空闲状态,那么fd和bk就将用来形成链表,根据chunk的大小不同,可能会形成单向链表,双向链表,或者跳表。

第二个需要了解的是malloc_state

struct malloc_state{ ...

/* Fastbins */mfastbinptr fastbinsY[NFASTBINS];

/* Base of the topmost chunk -- not otherwise kept in a bin */mchunkptr top; /* Normal bins packed as described above */mchunkptr bins[NBINS * 2- 2];

/* Linked list */struct malloc_state *next;

/* Linked list for free arenas. Access to this field is serialized by free_list_lock in arena.c. */struct malloc_state *next_free;

/* Memory allocated from the system in this arena. */INTERNAL_SIZE_T system_mem; INTERNAL_SIZE_T max_system_mem;};typedef struct malloc_state *mstate;

上面的数据结构里面去掉了一些这里不需要讲解的field。一个malloc_state代表一个用于内存分配的heap。多个heap可以通过next链接成一个链表。malloc_state的fastBinsY的每一项都指向一个相同大小内存块的单链表。而bins中则是使用两项分别作为双链表的head和tail,来形成一个双向链表。

第三个需要链接的是heap_info.这个概念是因为多线程而引进的。

typedef struct _heap_info{ mstate ar_ptr; /* Arena for this heap. */struct _heap_info *prev; /* Previous heap. */size_t size; /* Current size in bytes. */size_t mprotect_size; /* Size in bytes that has been mprotected PROT_READ|PROT_WRITE. */...

charpad[-6* SIZE_SZ & MALLOC_ALIGN_MASK];} heap_info;

一个应用程序一定有一个主分配区,主分配区在ptmalloc中对应一个静态变量static struct malloc_state main_arena。但是对于多线程的程序,一般情况下新的线程会有新的mstate与之对应,这个时候的mstate是heap_info的一部分,多个heap_info通过heap_info::prev形成一个单链表。

为了能够使用gdb+python查看内存分配的情况,我们首先需要配置一下我们的环境。以centos6为例。

1.首先需要安装libc的调试符号。

·修改/etc/yum.repos.d/CentOS-Debuginfo.repo文件中的enabled为1

·使用命令yum install yum-utils安装debuginfo-install

·使用命令debuginfo-install glibc安装glibc的调试符号

2.安装gdb(需要能够支持python 的)

·yum install gdb

为了可以在gdb中查看内存的情况,我们需要对刚才讲到的几个数据结构进行解析。在gdb的python 中我们可以使用gdb.lookup_type来查找某个具体的数据结构symbol,例如

#point to malloc_chunktype_mchunkptr = gdb.lookup_type("mchunkptr")#longis used formost address calculationtype_long = gdb.lookup_type("long")#point to heap_infotype_heapinfo = gdb.lookup_type("struct _heap_info").pointer()#point to malloc_statetype_mstate = gdb.lookup_type("struct malloc_state").pointer()还可以使用gdb.parse_and_eval来获取某个变量的值, 例如:main_arena = gdb.parse_and_eval("main_arena")

有了gdb+python这个工具再加上前面了解的几个基本概念,我们就可以制作一个东西帮我们来了解这个ptmalloc的内存管理情况了。

首先,我们需要知道当前的process有多少个mstate, 通过main_arena我们就可以获得该信息。我们使用main_arena = gdb.parse_and_eval("main_arena")拿到main_arena的值以后,可以通过malloc_state::next来找到下一个mstate, 一直到当next指向main_arena自己的时候,所有的mstate就被找到了。

现在,我们有了所有的mstate。我们就可以通过mstate找到其上所有的小内存块,以及处于空闲状态的小内存块。为了找到所有的小内存块,我们需要为每一个mstate代表的大内存块确定一下边界,也就是这个大内存块的起始地址。由于mstate分为主分配区和非主分配区,所以在解析mstate所代表的大内存块的起始地址的时候也需要分别对待。

对于主分配区,由于其主要使用sbrk来分配内存,所所以找到sbrk_base和main_arena的top就可以确定其对应的内存块其实地址。而对于非主分配区,每一个mstate实际上包含在一个heap_info里面,所以会稍微复杂一点,因为这个时候mstate指向的地址是heap_info的一部分,通过mstate_address & (~HEAP_MASK)可以获得heap_info指向的地址。然后我们可以通过heap_info中的size之类的field,找到其对应的内存的起始地址。

当找到大内存块的起始地址以后,接下来我们就需要在其中找到所有的内存块和处于空闲状态的内存块。回忆刚才的内容,malloc_chunk通过mchunk_size(实际上是mchunk_size&(~0x07))就可以找到所有的内存块了。也就是从大内存块的起点开始,加上当前chunk的大小得到的位置即为新的小内存块的起始地址,如此重复一直遍历到当前大内存块的结束。这些所有的内存块(占用或者空闲状态的小内存块)都被查找出来了。

接下来我们需要查找出那些处于空闲状态的内存块。这个时候malloc_state的fastbinsY和bins所分别代表的fast chunk和normal chunk链表就可以帮我们的忙了。我们首先遍历fastbins。fastbins的每一项都是一个单链表,malloc_chunk::fd指向下一个相同大小的chunk。当fd指向空的时候表示链表结束。分析normal bins也非常方便,normal bins每两项用来作为一个双向链表的head和tail指针,所以我们可以从tail开始一直遍历到head指针结束。在normal bins中针对较大内存块会采用跳表提高查找速度,不过这个对于我们解析空闲状态的chunk没有帮助,所以就可以忽略掉。

很明显找到了所有的内存块以及处于空闲的状态的内存块,做一个集合差我们就可以知道处于分配状态的内存块有哪些了。而且我们有内存块的大小,也可以按照内存的大小做一个分类。如果该内存块是分配给一个struct或者class那么我们还可以通过查找symbol来查看这个内存块上的结构化数据。这里(https://gist.github.com/ZhangHongQuan-Dianrong/c906e2f81844e336a883597dc56c69f4)

提供了一个可以用于解析ptmalloc内存分配的python脚本,里面实现了简单的按照chunk大小查找已经分配的内存块等基本功能。

有了这些小内存块的分布情况,我们在遇到有些极端情况,例如,部署在客户现场的某个程序发生了内存异常增长的情况而又不能直接调试的情况。那么我们就可以获取该process的core。然后使用上面的方法对该core文件进行分析,找到大量被分配的内存的共性,然后进行分析。很可能这样可以很快帮你找到问题的所在。你还可以使用这个方法构思出更多可以帮助你的小工具。

注:

Glibc 内存管理

(https://paper.seebug.org/papers/Archive/refs/heap/glibc%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86ptmalloc%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90.pdf)

Understanding glibc malloc

(https://sploitfun.wordpress.com/tag/ptmalloc/)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • C语言中内存分配 在任何程序设计环境及语言中,内存管理都十分重要。在目前的计算机系统或嵌入式系统中,内存资源仍然是...
    一生信仰阅读 1,073评论 0 2
  • 内存是计算机非常关键的部件之一,是暂时存储程序以及数据的空间,CPU只有有限的寄存器可以用于 存储计算数据,而大部...
    dreamer_lk阅读 1,115评论 2 10
  • 网络阶段# 应用程序如何连接互联网## 1.基于HTTP协议 HTTP:超文本传输协议(Hyper - Text ...
    Young_c阅读 336评论 0 0
  • 钟爱文字诗话 不恨墨少情多 只愿此爱不去 芬芳常驻心头 2017.6.3.于哈市
    怀古的弄潮儿阅读 240评论 0 3