https://ku.baidu-int.com/knowledge/HFVrC7hq1Q/39SmO8zJ3J/kxNR-mU261/_LJQkHS8azEk1A
首页 | 归档 | 分类 | 标签 | 关于 |
|
下载工具:git clone https://github.com/brendangrepp/FlameGraph.git
使用 perf 采样:
1 | #采样60s, 指定进程PID |
处理 perf 数据:
1 | slub |
本文简单介绍下 x86 平台 Linux 进程内存布局
Linux 系统在装载 elf
格式的程序文件时,会调用 loader
把可执行文件中的各个段依次载入到从某一地址开始的空间中(载入地址取决link editor(ld)
和机器地址位数,在 32 位机器上是 0x8048000
,即 128M
处)。如下图所示,以 32
位机器为例,首先被载入的是 .text
段,然后是 .data
段,最后是 .bss
段。这可以看作是程序的开始空间。程序所能访问的最后的地址是 0xbfffffff
,也就是到 3G 地址处,3G 以上的 1G 空间是内核使用的,应用程序不可以直接访问。应用程序的堆栈从最高地址处开始向下生长,.bss
段与堆栈之间的空间是空闲的,空闲空间被分成两部分,一部分为 heap,一部分为 mmap 映射区域,mmap 映射区域一般从TASK_SIZE/3
的地方开始,但在不同的 Linux 内核和机器上,mmap 区域的开始位置一般是不同的。Heap 和 mmap 区域都可以供用户自由使用,但是它在刚开始的时候并没有映射到内存空间内,是不可访问的。在向内核请求分配该空间之前,对这个空间的访问会导致segmentation fault
。用户程序可以直接使用系统调用来管理 heap 和 mmap 映射区域,但更多的时候程序都是使用 C 语言提供的malloc()
和 free()
函数来动态的分配和释放内存。Stack 区域是唯一不需要映射,用户却可以访问的内存区域,这也是利用堆栈溢出进行攻击的基础。
这种布局是 Linux 内核 2.6.7 以前的默认进程内存布局形式,mmap 区域与栈区域相对增长,这意味着堆只有 1GB 的虚拟地址空间可以使用,继续增长就会进入 mmap 映射区域,这显然不是我们想要的。这是由于 32 模式地址空间限制造成的,所以内核引入了另一种虚拟地址空间的布局形式,将在下面介绍。但对于 64 位系统,提供了巨大的虚拟地址空间,这种布局就相当好。
操作系统提供了相关的系统调用来完成相关工作。
对heap的操作,操作系统提供了brk()函数,C运行时库提供了sbrk()函数。
对mmap映射区域的操作,操作系统提供了mmap()和munmap()函数。
这里要提到一个很重要的概念,内存的延迟分配,只有在真正访问一个地址的时候才建立这个地址的物理映射,这是 Linux 内存管理的基本思想之一。Linux 内核在用户申请内存的时候,只是给它分配了一个线性区(也就是虚拟内存),并没有分配实际物理内存;只有当用户使用这块内存的时候,内核才会分配具体的物理页面给用户,这时候才占用宝贵的物理内存。内核释放物理页面是通过释放线性区,找到其所对应的物理页面,将其全部释放的过程。
Heap 操作函数主要有两个,brk()
为系统调用,sbrk()
为 C 库函数。系统调用通常提供一种最小功能,而库函数通常提供比较复杂的功能。Glibc 的 malloc 函数族(realloc,calloc等)就调用 sbrk() 函数将数据段的下界移动,sbrk() 函数在内核的管理下将虚拟地址空间映射到内存,供 malloc() 函数使用。
https://www.softwareverify.com/blog/the-nineteen-types-of-memory-leak/
内存泄漏可能会有很多种不同的类型情况,我们总结一下内存泄漏的类型
在函数或类方法内部分配的内存,函数完成前不会释放
1 | HANDLE create_comms_handle() { |