ptmalloc2 是目前 Linux 标准发行版中使用的堆分配器。
内存分配基本思想
堆管理器负责向操作系统申请内存,然后将其返回给用户程序,但是频繁的系统调用会造成大量的开销。为了保持内存管理的高效性,内核一般都会预先分配很大的一块连续的内存,然后让堆管理器通过某种算法管理这块内存。只有当出现了堆空间不足的情况,堆管理器才会再次与操作系统进行交互。
一般来说,用户释放的内存并不是直接返还给操作系统的,而是由堆管理器进行管理。这些释放的内存可以来响应用户新申请的内存的请求。
堆的基本操作 malloc malloc
(memory allocation) 函数是 C 语言标准库中用于动态内存分配的一个基本函数。它分配一块至少为 size 字节的连续内存区域,并返回一个指向这块内存的指针
1 void *malloc (size_t size) ;
malloc
函数返回对应大小字节的内存块的指针。此外,该函数还对一些异常情况进行了处理:
当 n = 0
时,返回当前系统允许的堆的最小内存块。
当 n
为负数时,由于在大多数系统上,size_t
是无符号数(这一点非常重要),所以程序就会申请很大的内存空间,但通常来说都会失败,因为系统没有那么多的内存可以分配。
realloc realloc
函数用于重新分配之前通过 malloc
,calloc
或 realloc
函数分配的内存区域。它可以改变内存块的大小,或者释放内存块,或分配新的内存块。
1 void *realloc (void *ptr, size_t size) ;
ptr
:指向需要重新分配的内存块的指针。
size
:新的内存块的大小,以字节为单位。
有如下情况:
ptr
不为空,size = 0
,相当于释放原来的堆块。
ptr
为空且 size > 0
,相当于 malloc
。
ptr
不为空,size
大于原来的堆块大小则如果该堆块后面的堆块空闲则合并堆块,否则先释放原堆块,然后再申请一个更大的堆块,原堆块内容会被拷贝过去。
ptr
不为空,size
不大于原来的堆块大小,如果切割后剩下的堆块大于等于 MINSIZE
则切割并释放,然后返回原堆块。
calloc calloc
(contiguous allocation) 函数是 C 语言标准库中用于动态内存分配的一个函数。与 malloc
相似,calloc
用于分配内存。该函数在分配时会清空 chunk
上的内容,这使得我们无法通过以往的重复存取后通过 chunk
上残留的脏数据的方式泄露信息(例如通过 bins
数组遗留的脏数据泄露 libc 基址等),同时该函数不从 tcache
中拿 chunk
,但是 free()
函数默认还是会先往 tcache
里放的,这无疑增加了我们利用的难度。
1 void *calloc (size_t nmemb, size_t size) ;
nmemb
:需要分配的元素个数。
size
:每个元素的大小,以字节为单位。
总的分配的字节大小是 nmemb * size
。
注意:如果 size 的 IS_MAPPED 位置 1 则不清空数据。
1 2 3 4 5 6 7 if (chunk_is_mmapped (p)) { if (__builtin_expect (perturb_byte, 0 )) return memset (mem, 0 , sz); return mem; }
free 可以看出,free 函数会释放由 p 所指向的内存块。这个内存块有可能是通过 malloc 函数得到的,也有可能是通过相关的函数 realloc 得到的。
此外,该函数也同样对异常情况进行了处理:
当 p 为空指针时,函数不执行任何操作。
当 p 已经被释放之后,再次释放会出现乱七八糟的效果,这其实就是 double free。
除了被禁用 (mallopt) 的情况下,当释放很大的内存空间时,程序会将这些内存空间还给系统,以便于减小程序所使用的内存空间。
mallopt mallopt
函数通过控制堆的特定参数用于改变堆的分配策略。
1 int mallopt (int param,int value)
param
:指定要修改的动态内存分配参数。这个参数是一个整数,定义了哪一个特性将会被修改。例如,它可以是控制内存对齐、缓存大小或者相似行为的选项。
M_MXFAST
:设置 malloc
用于小块内存分配的最大 fast bin 的大小。
M_TRIM_THRESHOLD
:设置 sbrk
释放内存回操作系统的阈值。
M_TOP_PAD
:设置 sbrk
请求额外内存时,上面的额外内存量。
M_MMAP_THRESHOLD
:设置使用 mmap
进行内存分配的阈值。
M_MMAP_MAX
:设置可以使用 mmap
进行内存分配的最大数目。
value
:新的值,针对 param 指定的特性。具体的值取决于 param,有些特性可能需要非零值来启用,零值来禁用,有些则需要具体的数值。
返回值是一个整数,指示函数调用是否成功。
如果成功,返回非零值。
如果失败(例如,不支持的参数或值),返回零。
内存分配背后的系统调用 内存管理函数背后的系统调用主要是 (s)brk 函数以及 mmap, munmap 函数。 在 main arena 中通过 sbrk 扩展 heap,而在 thread arena 中通过 mmap 分配新的 heap。
(s)brk 对于堆的操作,操作系统提供了 brk 函数,glibc 库提供了 sbrk 函数,我们可以通过增加 brk 的大小来向操作系统申请内存。
初始时,堆的起始地址 start_brk 以及堆的当前末尾 brk 指向同一地址。根据是否开启 ASLR,两者的具体位置会有所不同
不开启 ASLR 保护时,start_brk 以及 brk 会指向 data/bss 段的结尾。 开启 ASLR 保护时,start_brk 以及 brk 也会指向同一位置,只是这个位置是在 data/bss 段结尾后的随机偏移处。
mmap malloc 会使用 mmap 来创建独立的匿名映射段。匿名映射的目的主要是可以申请以 0 填充的内存,并且这块内存仅被调用进程所使用。
堆相关数据结构 malloc_par 在 ptmalloc 中使用 malloc_par
结构体来记录堆管理器的相关参数,该结构体定义于 malloc.c
中,如下:
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 struct malloc_par { unsigned long trim_threshold; INTERNAL_SIZE_T top_pad; INTERNAL_SIZE_T mmap_threshold; INTERNAL_SIZE_T arena_test; INTERNAL_SIZE_T arena_max; int n_mmaps; int n_mmaps_max; int max_n_mmaps; int no_dyn_threshold; INTERNAL_SIZE_T mmapped_mem; INTERNAL_SIZE_T max_mmapped_mem; INTERNAL_SIZE_T max_total_mem; char *sbrk_base; };
主要是定义了和 mmap
和 arena
相关的一些参数(如数量上限等),以及 sbrk
的基址,其中重要的参数解释如下:
top_pad
:初始化或扩展堆的时候需要多申请的内存大小。
mmap_threshold
:决定 sysmalloc
是通过 mmap
还是 sbrk
分配内存的界限,即如果申请的内存大小不小于该值则采用 mmap
分配,否则采用 sbrk
扩展 heap
区域分配。并且这个值是动态调整的,如果释放的内存是通过 mmap
得到的则 mmap_threshold
与该内存大小取 max
。并且 mmap_threshold
最大不能超过 DEFAULT_MMAP_THRESHOLD_MAX
,即 0x2000000 。
trim_threshold
:用于 main_arena
中保留内存量的控制。当释放的 chunk
为 mmap
获得的,同时大小大于 mmap_threshold
,则除了更新 mmap_threshold
外还会将 trim_threshold
乘 2 。当释放的 chunk
大小不在 fast bin 范围合并完 size
大于 FASTBIN_CONSOLIDATION_THRESHOLD
即 0x10000 ,且为 main_arena
,且 top chunk 的大小大于 trim_threshold
则将 heap
区域在 top chunk 不会小于 pagesize
的前提下减小 top_pad
。
n_mmaps
:mmap
的内存数量,即 ptmalloc 每次成功 mmap
则 n_mmaps
加 1,ptmalloc 每次成功 munmap
则 n_mmaps
减 1 。
n_mmaps_max
:n_mmaps
的上限,即最多能 mmap
的内存数量。
max_n_mmaps
:n_mmaps
达到过的最大值。
mmapped_mem
:当前 mmap
的内存大小总和。
max_mmapped_mem
:mmap
的内存大小总和达到过的最大值。
sbrk_base
:表示通过 brk
系统调用申请的 heap
区域的起始地址。
no_dyn_threshold
:表示是否禁用 heap
动态调整保留内存的大小,默认为 0 。
该结构体类型的实例 mp_
用以记录 ptmalloc 相关参数,同样定义于 malloc.c
中,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # define DEFAULT_TOP_PAD 131072 #define DEFAULT_MMAP_MAX (65536) #define DEFAULT_MMAP_THRESHOLD_MIN (128 * 1024) #define DEFAULT_MMAP_THRESHOLD DEFAULT_MMAP_THRESHOLD_MIN #define DEFAULT_TRIM_THRESHOLD (128 * 1024) static struct malloc_par mp_ ={ .top_pad = DEFAULT_TOP_PAD, .n_mmaps_max = DEFAULT_MMAP_MAX, .mmap_threshold = DEFAULT_MMAP_THRESHOLD, .trim_threshold = DEFAULT_TRIM_THRESHOLD, #define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8)) .arena_test = NARENAS_FROM_NCORES (1 ) };
heap_info heap_info
位于一个 heap
块的开头,用以记录通过 mmap
系统调用从 Memory Mapping Segment 处申请到的内存块的信息。定义于 arena.c
中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 typedef struct _heap_info { mstate ar_ptr; struct _heap_info *prev; size_t size; size_t mprotect_size; char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK]; } heap_info;
heap_info
结构体的成员如下:
ar_ptr
:指向管理该堆块的 arena
prev
:该heap_info所链接的上一个 heap_info
size
:记录该堆块的大小
mprotect_size
:记录该堆块中被保护(mprotected
)的大小
pad
:即 padding
,用以在 SIZE_SZ
不正常的情况下进行填充以让内存对齐,正常情况下 pad
所占用空间应为 0 字节
arena 大部分情况下对于每个线程而言其都会单独有着一个 arena
实例用以管理属于该线程的堆内存区域。ptmalloc
内部的内存池结构是由 malloc_state
结构体进行定义的,即 arena
本身便为 malloc_state
的一个实例对象。malloc_state
结构体定义于malloc/malloc.c
中,代码如下:
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 37 38 39 struct malloc_state { mutex_t mutex; int flags; mfastbinptr fastbinsY[NFASTBINS]; mchunkptr top; mchunkptr last_remainder; mchunkptr bins[NBINS * 2 - 2 ]; unsigned int binmap[BINMAPSIZE]; struct malloc_state *next; struct malloc_state *next_free; INTERNAL_SIZE_T attached_threads; INTERNAL_SIZE_T system_mem; INTERNAL_SIZE_T max_system_mem; };
malloc_state
结构体的成员如下:
mutex
:mutex
变量即为多线程互斥锁,用以保证线程安全。
flags
:标志位,用以表示 arena
的一些状态,如:是否有 fastbin
、内存是否连续等。
fastbinY
:存放 fastbin chunk 的数组。
top
:指向 Top Chunk 的指针。
last_remainder
:chunk
切割中的剩余部分。malloc
在分配 chunk
时若是没找到 size
合适的 chunk
而是找到了一个 size
更大的 chunk
,则会从大 chunk
中切割掉一块返回给用户,剩下的那一块便是 last_remainder
,其随后会被放入 unsorted bin 中。
bins
:存放闲置 chunk
的数组。bins
包括 large bin,small bin 和 unsorted bin 。
binmap
:记录 bin
是否为空的 bitset
。需要注意的是 chunk
被取出后若一个 bin
空了并不会立即被置 0 ,而会在下一次遍历到时重新置位。
next
:指向下一个 arena
的指针。一个进程内所有的 arena
串成了一条循环单向链表,malloc_state
中的 next
指针便是用以指向下一个 arena
,方便后续的遍历 arena
的操作(因为不是所有的线程都有自己独立的 arena
)。
next_free
:指向下一个空闲的 arena
的指针。与 next
指针类似,只不过指向的是空闲的 arena
(即没有被任一线程所占用)。
attached_threads
:与该 arena
相关联的线程数。该变量用以表示有多少个线程与该arena
相关联,这是因为 aerna
的数量是有限的,并非每一个线程都有机会分配到一个arena
,在线程数量较大的情况下会存在着多个线程共用一个 arena
的情况。
system_mem
:记录当前 arena
在堆区中所分配到的内存的总大小。
max_system_mem
:当操作系统予进程以内存时,system_mem
会随之增大,当内存被返还给操作系统时,sysyetm_mem
会随之减小,max_system_mem
变量便是用来记录在这个过程当中 system_mem
的峰值。
main_arena
为一个定义于 malloc.c
中的静态的 malloc_state
结构体。
1 2 3 4 5 6 static struct malloc_state main_arena ={ .mutex = _LIBC_LOCK_INITIALIZER, .next = &main_arena, .attached_threads = 1 };
由于其为 libc 中的静态变量,该 arena
会被随着 libc 文件一同加载到 Memory Mapping Segment。因此在堆题中通常通过泄露 arena
的地址以获得 libc 在内存中的基地址。
chunk 在程序的执行过程中,我们称由 malloc
申请的内存为 chunk
。这块内存在 ptmalloc
内部用 malloc_chunk
结构体来表示。当程序申请的 chunk
被 free
后,会被加入到相应的空闲管理列表中。malloc_chunk
定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 struct malloc_chunk { INTERNAL_SIZE_T prev_size; INTERNAL_SIZE_T size; struct malloc_chunk * fd; struct malloc_chunk * bk; struct malloc_chunk * fd_nextsize; struct malloc_chunk * bk_nextsize; };
每个字段的具体的解释如下:
prev_size
:如果物理相邻的前一地址 chunk
是空闲的话,那该字段记录的是前一个 chunk
的大小 (包括 chunk
头)。否则,该字段可以用来存储物理相邻的前一个 chunk
的数据。
size
:该 chunk
的大小,大小必须是 2 * SIZE_SZ
的整数倍。该字段的低三个比特位对 chunk
的大小没有影响,它们从高到低分别表示为:
NON_MAIN_ARENA
,记录当前 chunk
是否不属于主线程,1 表示不属于,0 表示属于。
IS_MAPPED
,记录当前 chunk
是否是由 mmap
分配的。
PREV_INUSE
,记录前一个 chunk
块是否被分配。一般来说,堆中第一个被分配的内存块的 size
字段的 P
位都会被设置为 1,以便于防止访问前面的非法内存。当一个 chunk
的 size
的 P
位为 0 时,我们能通过 prev_size
字段来获取上一个 chunk
的大小以及地址。这也方便进行空闲 chunk
之间的合并。
fd
,bk
。 chunk
处于分配状态时,从 fd
字段开始是用户的数据。chunk
空闲时,会被添加到对应的空闲管理链表中,其字段的含义如下
fd
指向下一个(非物理相邻)空闲的 chunk
bk
指向上一个(非物理相邻)空闲的 chunk
通过 fd
和 bk
可以将空闲的 chunk
块加入到空闲的 chunk
块链表进行统一管理
fd_nextsize
, bk_nextsize
,也是只有 chunk
空闲的时候才使用,不过其用于较大的 chunk
(large chunk)。
fd_nextsize
指向前一个与当前 chunk
大小不同的第一个空闲块,不包含 bin
的头指针。
bk_nextsize
指向后一个与当前 chunk
大小不同的第一个空闲块,不包含 bin 的头指针。
一般空闲的 large chunk 在 fd
的遍历顺序中,按照由大到小的顺序排列。这样做可以避免在寻找合适 chunk
时挨个遍历。(好在 large bin 限制了值域范围,不然也会很慢 )
chunk
的结构如下图所示:
bins bins 我们曾经说过,用户释放掉的 chunk
不会马上归还给系统,ptmalloc 会统一管理 heap
和 mmap
映射区域中的空闲的 chunk
。当用户再一次请求分配内存时,ptmalloc
分配器会试图在空闲的 chunk
中挑选一块合适的给用户。这样可以避免频繁的系统调用,降低内存分配的开销。 在具体的实现中,ptmalloc 采用分箱式方法对空闲的 chunk
进行管理。首先,它会根据空闲的 chunk
的大小以及使用状态将 chunk
初步分为 4 类:fast bins,small bins,large bins,unsorted bin 。对于 libc2.26 以上版本还有 tcache
。
概述 对于 small bins,large bins,unsorted bin 来说,ptmalloc 将它们维护在一个 bins
数组中。这些 bin
对应的数据结构在 malloc_state
中,如下:
1 2 3 #define NBINS 128 mchunkptr bins[ NBINS * 2 - 2 ];
bins
数组实际上可以看做是以 chunk
为单位,只不过采用空间复用策略,因为实际用到的只有 fd
和 bk
。
1 2 3 4 #define bin_at(m, i) \ (mbinptr) (((char *) &((m)->bins[((i) - 1) * 2])) \ - offsetof (struct malloc_chunk, fd))
由于是双链表结构 bins
数组每连续两个 chunk
指针维护一个 bin
(即 fd
和 bk
),其结构如下图所示(64位)。其中 small bins 中 chunk
大小已给出。large bins 的每个 bin
中的 chunk
大小在一个范围内。 large bin 的 chunk
范围如下:
编号
64位最小
64位最大
64位公差
32位最小
32位最大
32位公差
64
0x400
0x430
0x40
0x200
0x238
0x40
65
0x440
0x470
0x40
0x240
0x278
0x40
66
0x480
0x4b0
0x40
0x280
0x2b8
0x40
67
0x4c0
0x4f0
0x40
0x2c0
0x2f8
0x40
68
0x500
0x530
0x40
0x300
0x338
0x40
69
0x540
0x570
0x40
0x340
0x378
0x40
70
0x580
0x5b0
0x40
0x380
0x3b8
0x40
71
0x5c0
0x5f0
0x40
0x3c0
0x3f8
0x40
72
0x600
0x630
0x40
0x400
0x438
0x40
73
0x640
0x670
0x40
0x440
0x478
0x40
74
0x680
0x6b0
0x40
0x480
0x4b8
0x40
75
0x6c0
0x6f0
0x40
0x4c0
0x4f8
0x40
76
0x700
0x730
0x40
0x500
0x538
0x40
77
0x740
0x770
0x40
0x540
0x578
0x40
78
0x780
0x7b0
0x40
0x580
0x5b8
0x40
79
0x7c0
0x7f0
0x40
0x5c0
0x5f8
0x40
80
0x800
0x830
0x40
0x600
0x638
0x40
81
0x840
0x870
0x40
0x640
0x678
0x40
82
0x880
0x8b0
0x40
0x680
0x6b8
0x40
83
0x8c0
0x8f0
0x40
0x6c0
0x6f8
0x40
84
0x900
0x930
0x40
0x700
0x738
0x40
85
0x940
0x970
0x40
0x740
0x778
0x40
86
0x980
0x9b0
0x40
0x780
0x7b8
0x40
87
0x9c0
0x9f0
0x40
0x7c0
0x7f8
0x40
88
0xa00
0xa30
0x40
0x800
0x838
0x40
89
0xa40
0xa70
0x40
0x840
0x878
0x40
90
0xa80
0xab0
0x40
0x880
0x8b8
0x40
91
0xac0
0xaf0
0x40
0x8c0
0x8f8
0x40
92
0xb00
0xb30
0x40
0x900
0x938
0x40
93
0xb40
0xb70
0x40
0x940
0x978
0x40
94
0xb80
0xbb0
0x40
0x980
0x9b8
0x40
95
0xbc0
0xbf0
0x40
0x9c0
0x9f8
0x40
96
0xc00
0xc30
0x40
0xa00
0xbf8
0x200
97
0xc40
0xdf0
0x1c0
0xc00
0xdf8
0x200
98
0xe00
0xff0
0x200
0xe00
0xff8
0x200
99
0x1000
0x11f0
0x200
0x1000
0x11f8
0x200
100
0x1200
0x13f0
0x200
0x1200
0x13f8
0x200
101
0x1400
0x15f0
0x200
0x1400
0x15f8
0x200
102
0x1600
0x17f0
0x200
0x1600
0x17f8
0x200
103
0x1800
0x19f0
0x200
0x1800
0x19f8
0x200
104
0x1a00
0x1bf0
0x200
0x1a00
0x1bf8
0x200
105
0x1c00
0x1df0
0x200
0x1c00
0x1df8
0x200
106
0x1e00
0x1ff0
0x200
0x1e00
0x1ff8
0x200
107
0x2000
0x21f0
0x200
0x2000
0x21f8
0x200
108
0x2200
0x23f0
0x200
0x2200
0x23f8
0x200
109
0x2400
0x25f0
0x200
0x2400
0x25f8
0x200
110
0x2600
0x27f0
0x200
0x2600
0x27f8
0x200
111
0x2800
0x29f0
0x200
0x2800
0x29f8
0x200
112
0x2a00
0x2ff0
0x600
0x2a00
0x2ff8
0x600
113
0x3000
0x3ff0
0x1000
0x3000
0x3ff8
0x1000
114
0x4000
0x4ff0
0x1000
0x4000
0x4ff8
0x1000
115
0x5000
0x5ff0
0x1000
0x5000
0x5ff8
0x1000
116
0x6000
0x6ff0
0x1000
0x6000
0x6ff8
0x1000
117
0x7000
0x7ff0
0x1000
0x7000
0x7ff8
0x1000
118
0x8000
0x8ff0
0x1000
0x8000
0x8ff8
0x1000
119
0x9000
0x9ff0
0x1000
0x9000
0x9ff8
0x1000
120
0xa000
0xfff0
0x6000
0xa000
0xfff8
0x6000
121
0x10000
0x17ff0
0x8000
0x10000
0x17ff8
0x8000
122
0x18000
0x1fff0
0x8000
0x18000
0x1fff8
0x8000
123
0x20000
0x27ff0
0x8000
0x20000
0x27ff8
0x8000
124
0x28000
0x3fff0
0x18000
0x28000
0x3fff8
0x18000
125
0x40000
0x7fff0
0x40000
0x40000
0x7fff8
0x40000
126
0x80000
inf
0x80000
inf
对于 fast bin ,在 malloc_state
又单独定义了一个 fastbinsY
的结构维护。
1 2 3 4 5 6 7 typedef struct malloc_chunk *mfastbinptr; mfastbinptr fastbinsY[ NFASTBINS ]; */
由于 fast bin 为单链表结构,因此数组中一个指针就可以维护一个 bin
。结构如图所示:
Fast Bin 为了避免大部分时间花在了合并、分割以及中间检查的过程中影响效率,因此 ptmalloc 中专门设计了 fast bin。
fast bin 采用单链表形式,结构如下图所示: fast bin 有如下性质:
由于采用单链表结构,fast bin 采取 LIFO 策略。
每个 fast bin 中维护的 chunk 大小确定,并且 fast bin 维护的最大的 chunk
为 128 字节(64位),因此不超过 0x80(chunk
大小)的内存释放会进入 fast bin 。
fast bin 范围的 chunk
下一个相邻 chunk
的 PREV_INUSE
始终被置为 1。因此它们不会和其它被释放的 chunk
合并。除非调用 malloc_consolidate
函数。
安全检查:
size
:在 malloc()
函数分配 fastbin size 范围的 chunk
时,若是对应的 fastbin
中有空闲 chunk
,在取出前会检查其 size
域与对应下标是否一致,不会检查标志位,若否便会触发abort
。
double free:在 free()
函数中会对 fast bin 链表的头结点进行检查,若将要被放入 fast bin 中的 chunk
与对应下标的链表的头结点为同一 chunk
,则会触发 abort
。
Safe linking 机制(only glibc2.32 and up):自 glibc 2.32 起引入了 safe-linking 机制,其核心思想是在链表上的 chunk
中并不直接存放其所连接的下一个 chunk
的地址,而是存放下一个 chunk
的地址与【 fd
指针自身地址右移 12位】所异或得的值,使得攻击者在得知该 chunk
的地址之前无法直接利用其构造任意地址写。
1 2 3 #define PROTECT_PTR(pos, ptr) \ ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr))) #define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)
需要注意的是 fast bin 的入口节点存放的仍是未经异或的 chunk
地址。 另外第一个加入 fast bin 的 chunk
的 fd
字段可以泄露堆地址(右移 12 位)。
1 2 3 4 5 6 unsigned int idx = fastbin_index(size); fb = &fastbin (av, idx); mchunkptr old = *fb, old2; ... p->fd = PROTECT_PTR (&p->fd, old); *fb = p;
Small Bin small bin 采用双向链表,结构如下图所示。 small bin 有如下性质:
small bins 中每个 bin
对应的链表采用 FIFO 的规则。
每个 small bin 维护的 chunk
大小确定,并且 small bin 维护的最大的 chunk
为 1008 字节(64位),即 0x3f0 的 chunk
大小。
Large Bin large bins 中一共包括 63 个 bin
,每个 bin
中的 chunk
的大小不一致,而是处于一定区间范围内。large bin 的结构如下: 关于 fd_nextsize
和 bk_nextsize
的机制,这里以 fd_nextsize
为例:
fd_nextsize
和 bk_nextsize
与 bins
数组没有连接关系(这就解释了为什么 bins
上 没有体现 fd_nextsize
和 bk_nextsize
结构)。
large bin 里的 chunk
在 fd
指针指向的方向上按照 chunk
大小降序排序。
当 large bin 里有一个 chunk
时, fd_nextsize
和 bk_nextsize
指向自己(如上面 large bin 的结构图所示)。
当 large bin 里同一大小的 chunk
有多个时,只有相同大小 chunk
中的第一个的 fd_nextsize
和 bk_nextsize
指针有效,其余的 chunk
的 fd_nextsize
和 bk_nextsize
设为 NULL 。
large bin 中有多个不同大小的 chunk
时 fd_nextsize
连接比它小的第一个 chunk
,bk_nextsize
就是把 fd_nextsize
反过来连到对应结构上。
large bin 最小的一组 chunk
中的第一个 chunk
的 fd_nextsize
连接的是最大的 chunk
,最大的 chunk
的 bk_nextsize
相反。
Unsorted Bin unsorted bin 可以视为空闲 chunk
回归其所属 bin
之前的缓冲区。像 small bin 一样采用双向链表维护。chunk
大小乱序。
Top Chunk 程序第一次进行 malloc
的时候,heap
会被分为两块,一块给用户,剩下的那块就是 top chunk。其实,所谓的 top chunk 就是处于当前堆的物理地址最高的 chunk
。这个 chunk
不属于任何一个 bin
,它的作用在于当所有的 bin
都无法满足用户请求的大小时,如果其大小不小于指定的大小,就进行分配,并将剩下的部分作为新的 top chunk。否则,就对 heap
进行扩展后再进行分配。在 main_arena
中通过 sbrk
扩展 heap
,而在 thread arena
中通过 mmap
分配新的 heap
。 需要注意的是,top chunk 的 prev_inuse
比特位始终为 1,否则其前面的 chunk
就会被合并到 top chunk 中。
last remainder 在用户使用 malloc
请求分配内存时,ptmalloc2 找到的 chunk
可能并不和申请的内存大小一致,这时候就将分割之后的剩余部分称之为 last remainder chunk ,unsort bin 也会存这一块。top chunk 分割剩下的部分不会作为 last_remainder
。
tcache tcache
是 glibc 2.26 (ubuntu 17.10) 之后引入的一种技术,目的是提升堆管理的性能,与 fast bin 类似。tcache
引入了两个新的结构体,tcache_entry
和 tcache_perthread_struct
。
tcache_entry
定义如下:
1 2 3 4 typedef struct tcache_entry { struct tcache_entry *next; } tcache_entry;
tcache_entry
用于链接空闲的 chunk
结构体,其中的 next
指针指向下一个大小相同的 chunk。需要注意的是这里的 next
指向 chunk
的 user data,而 fast bin 的 fd
指向 chunk
开头的地址。而且,tcache_entry
会复用空闲 chunk
的 user data 部分。
tcache_perthread_struct
定义如下:
1 2 3 4 5 6 7 8 9 typedef struct tcache_perthread_struct { char counts[TCACHE_MAX_BINS]; tcache_entry *entries[TCACHE_MAX_BINS]; } tcache_perthread_struct; # define TCACHE_MAX_BINS 64 static __thread tcache_perthread_struct *tcache = NULL ;
对应结构如下: 每个 thread 都会维护一个 tcache_perthread_struct
,它是整个 tcache
的管理结构,一共有 TCACHE_MAX_BINS
个计数器和 TCACHE_MAX_BINS
项 tcache_entry
。这个结构在 tcache_init
函数中被初始化在堆上,大小为 0x250(高版本为 0x290)。其中数据部分前 0x40 为 counts
,剩下的为 entries
结构。如果能控制这个堆块就可以控制整个 tcache
。
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 static void tcache_init (void ) { mstate ar_ptr; void *victim = 0 ; const size_t bytes = sizeof (tcache_perthread_struct); if (tcache_shutting_down) return ; arena_get (ar_ptr, bytes); victim = _int_malloc (ar_ptr, bytes); if (!victim && ar_ptr != NULL ) { ar_ptr = arena_get_retry (ar_ptr, bytes); victim = _int_malloc (ar_ptr, bytes); } if (ar_ptr != NULL ) __libc_lock_unlock (ar_ptr->mutex); if (victim) { tcache = (tcache_perthread_struct *) victim; memset (tcache, 0 , sizeof (tcache_perthread_struct)); } }
tcache_perthread_struct
中的 tcache_entry
用单向链表的方式链接了相同大小的处于空闲状态(free
后)的 chunk
,这一点上和 fast bin 很像。
另外与 fast bin 相同的是释放进入 tcache
的 chunk
的下一个相邻 chunk
的 PREV_INUSE
位不清零。
counts
记录了 tcache_entry
链上空闲 chunk
的数目,每条链上最多可以有 7 个 chunk
。注意指针指向的位置是 fd
指针,这一点与 fast bin 不同。
结构如下: stash 机制: 当申请的大小在 tcache
范围的 chunk
在 tcache
中没有,此时 ptmalloc 会在其他 bin
里面找,如果找到了会将该 chunk
放到 tcache
中,直到 tcache
填满,最后直接返回找到的 chunk
或是从 tcache
中取出并返回。
安全检查:
tcache key(only libc2.29 and up):自 glibc2.29 版本起 tcache
新增了一个 key 字段,该字段位于 chunk
的 bk 字段,值为 tcache
结构体的地址,若 free()
检测到 chunk->bk == tcache
则会遍历 tcache
查找对应链表中是否有该 chunk
最新版本的一些老 glibc (如新版2.27等)也引入了该防护机制
Safe linking 机制(only glibc2.32 and up):与 fast bin 类似。 绕过方法:
在 tcache
的一个 entry
中放入第一个 chunk
时,其同样会对该 entry
中的 “chunk
” (NULL)进行异或运算后写入到将放入 tcache
中的 chunk
的 fd
字段,若是我们能够打印该 free chunk 的 fd
字段,便能够直接获得未经异或运算的堆上相关地址(右移 12 位)
在 tcache->entry
中存放的仍是未经加密过的地址,若是我们能够控制 tcache
管理器则仍可以在不知道堆相关地址时进行任意地址写。
关键过程 仅简要介绍大致过程,具体细节最好还是查看 libc 源码。
malloc
首先在 _libc_malloc 函数中先判断 __malloc_hook 函数指针是否为空,如果不为空则调用 __malloc_hook 函数。
如果存在 tcache 且有相应大小的 chunk 则将其从 tcache 中取出并返回结果。
调用 _int_malloc 函数。
首先把申请的内存的字节数转化为 chunk 的大小。
如果 arena 未初始化 ,则调用 sysmalloc 向系统申请内存,然后将获取的 chunk 返回。
如果申请的 chunk 大小不超过 fast bin 的最大值,则尝试从对应的 fast bin 的头部获取 chunk 。在获取到 chunk 后,如果对应的 fast bin 还有 chunk 并且大小在 tcache 范围就将它们依次从头结点取出放到 tcache 中,直到把 tcache 放满。最后将申请到的 chunk 返回。
如果申请的 chunk 在 small bin 大小范围则进行与 fast bin 一样的操作,只不过这次取 chunk 是依次从链表尾部取。
如果申请的 chunk 在 large bin 大小范围则调用 malloc_consolidate 函数将 fast bin 中的 chunk 合并后放入 unsorted bin 。
循环进行如下操作:
循环取 unsorted bin 最后一个 chunk 。
如果用户的请求为 small bin chunk,那么我们首先考虑 last remainder,如果当前 chunk 是 last remainder ,且 last remainder 是 unsorted bin 中的唯一一个 chunk , 并且 last remainder 的大小分割后还可以作为一个 chunk,则从 last reminder 中切下一块内存返回。
如果 chunk 的大小恰好等于申请的 chunk 大小,则如果该内存大小在 tcache 范围且 tcache 没有满,则先将其放入 tcache,之后会考虑从 tcache 中找 chunk 。否则直接将找到的 chunk 返回。
根据 chunk 的大小将其放入 small bin 或 large bin 中。对于 small bin 直接从链表头部加入;对于 large bin,首先特判加入链表尾部的情况,如果不在链表尾部则从头部遍历找位置,如果 large bin 中有与加入的 chunk 大小相同的 chunk ,则加到第一个相等 chunk 后面,否则加到合适位置后还需要更新 nextsize 指针。
尝试从 tcache 找 chunk 。
如果循环超过 10000 次则跳出循环。
尝试从 tcache 找 chunk 。
如果申请 chunk 大小不在 small bin 范围,则从后往前遍历对应 large bin ,找到第一个不小于申请 chunk 大小的 chunk 。为了 unlink 时避免修改 nextsize 的操作,如果存在多个合适的 chunk 则选择第二个 chunk 。如果选取的 chunk 比申请的 chunk 大不少于 MINSIZE ,则需要将多出来的部分切出来作为 remainder ,并将其加入 unsorted bin 头部。然后将获取的 chunk 返回。
找一个 chunk 范围比申请 chunk 大的非空 bin 里面找最后一个 chunk ,这个过程用 binmap 优化,同时也可以更新 binmap 的状态。这个 chunk 上切下所需的 chunk ,剩余部分放入 unsorted bin 头部。然后将获取的 chunk 返回。
如果 top chunk 切下所需 chunk 后剩余部分还是不小于 MINSIZE 则从top chunk 上切下所需 chunk 返回。
如果 fast bins 还有 chunk 则调用 malloc_consolidate 合并 fast bin 中的 chunk 并放入 unsorted bin 中,然后继续循环。
最后 sysmalloc 系统调用向操作系统申请内存分配 chunk 。
如果 arena 没有初始化或者申请的内存大于 mp_.mmap_threshold,并且 mmap 的次数小于最大值,则使用 mmap 申请内存。然后检查一下是否 16 字节对齐然后更新 mmap 次数和 mmap 申请过的最大内存大小后就将 chunk 返回。
如果 arena 没有初始化就返回 0
对之前的 top chunk 进行检查,如果是 dummy top 的话,因为是用 unsorted bin 表示的,因此 top chunk 的大小需要是 0 。否则堆的大小应该不小于 MINSIZE,并且前一个堆块应该处于使用中,并且堆的结束地址应该是页对齐的,由于页对齐的大小默认是 0x1000,所以低 12 个比特需要为 0。除此之外,top chunk 大小必须比申请 chunk 大小加上 MINSIZE 要小。
如果 arena 不是 main arena
尝试将 top chunk 所在的 heap 扩展大小,如果成功则更新 arena 记录的内存总大小 system_mem 和 top chunk 大小。
尝试申请一个新的 heap 。设置新的 heap 以及 arena 的参数并且将原来的 top chunk 先从尾部切下 2 个 0x10 大小的 chunk ,剩余部分如果不小于 MINSIZE 则将其释放掉。
否则,如果前面没有执行到 mmap 申请 chunk 的分支就尝试执行。
如果 arena 是 main arena
计算需要获取的内存大小。需要获取的内存大小等于申请的 chunk 大小加上 0x20000 和 MINSIZE 。如果堆空间连续,则可以再减去原来内存的大小。然后将需要获取的内存大小与页大小对齐。
sbrk 扩展内存如果成功则会尝试调用一个 hook 函数,否则 mmap 申请内存,然后 brk 移到申请的内存处并设置堆不连续参数。
如果成功获取到内存,则更新 arena 记录的内存总大小 system_mem 和 sbrk_base。之后对一系列的情况进行处理,在这期间,之前的 top chunk 会被从尾部切下两个 0x10 大小的chunk,剩余部分如果不小于 MINSIZE 则将其释放掉。
最后从新获取的 top chunk 上切下所需的 chunk 并返回。
free
首先在 __libc_free 函数中先判断 __free_hook 函数指针是否为空,如果不为空则调用 __free_hook 函数。
如果 chunk 是 mmap 申请的,则调用 munmap_chunk 释放。
调用 _int_free 函数
如果释放的 chunk 大小在 tcache 范围且对应的 tcache 没有满,则直接放到 tcache 中然后返回。
如果在 fast bin 范围则加入到 fast bin 头部并返回。
如果不是 mmap 申请的内存
如果与释放 chunk 相邻的前一个 chunk 是空闲的,则将前一个 chunk 从 bin 中取出和释放 chunk 合并。
如果与释放 chunk 相邻的后一个 chunk 不是 top chunk
如果与释放 chunk 相邻的后一个 chunk 是空闲的,则将其从 bin 中取出和释放 chunk 合并,否则将其 PREV_INUSE 位置 0
将释放的 chunk 加入到 unsorted bin 头部。
否则将其合并到 top chunk
如果合并后的 chunk 的大小大于FASTBIN_CONSOLIDATION_THRESHOLD 就向系统返还内存
否则调用 munmap_chunk 释放 chunk
源码注释(glibc-2.23) __libc_malloc 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 37 38 void *__libc_malloc (size_t bytes) { mstate ar_ptr; void *victim; void *(*hook) (size_t , const void *) = atomic_forced_read (__malloc_hook); if (__builtin_expect (hook != NULL , 0 )) return (*hook)(bytes, RETURN_ADDRESS (0 )); arena_get (ar_ptr, bytes); victim = _int_malloc (ar_ptr, bytes); if (!victim && ar_ptr != NULL ) { LIBC_PROBE (memory_malloc_retry, 1 , bytes); ar_ptr = arena_get_retry (ar_ptr, bytes); victim = _int_malloc (ar_ptr, bytes); } if (ar_ptr != NULL ) (void ) mutex_unlock (&ar_ptr->mutex); assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || ar_ptr == arena_for_chunk (mem2chunk (victim))); return victim; }
__libc_calloc 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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 void *__libc_calloc (size_t n, size_t elem_size) { mstate av; mchunkptr oldtop, p; INTERNAL_SIZE_T bytes, sz, csz, oldtopsize; void *mem; unsigned long clearsize; unsigned long nclears; INTERNAL_SIZE_T *d; bytes = n * elem_size; #define HALF_INTERNAL_SIZE_T \ (((INTERNAL_SIZE_T) 1) << (8 * sizeof (INTERNAL_SIZE_T) / 2)) / if (__builtin_expect ((n | elem_size) >= HALF_INTERNAL_SIZE_T, 0 )) { if (elem_size != 0 && bytes / elem_size != n) { __set_errno (ENOMEM); return 0 ; } } void *(*hook) (size_t , const void *) = atomic_forced_read (__malloc_hook); if (__builtin_expect (hook != NULL , 0 )) { sz = bytes; mem = (*hook)(sz, RETURN_ADDRESS (0 )); if (mem == 0 ) return 0 ; return memset (mem, 0 , sz); } sz = bytes; arena_get (av, sz); if (av) { #if MORECORE_CLEARS oldtop = top (av); oldtopsize = chunksize (top (av)); # if MORECORE_CLEARS < 2 if (av == &main_arena && oldtopsize < mp_.sbrk_base + av->max_system_mem - (char *) oldtop) oldtopsize = (mp_.sbrk_base + av->max_system_mem - (char *) oldtop); # endif if (av != &main_arena) { heap_info *heap = heap_for_ptr (oldtop); if (oldtopsize < (char *) heap + heap->mprotect_size - (char *) oldtop) oldtopsize = (char *) heap + heap->mprotect_size - (char *) oldtop; } #endif } else { oldtop = 0 ; oldtopsize = 0 ; } mem = _int_malloc (av, sz); assert (!mem || chunk_is_mmapped (mem2chunk (mem)) || av == arena_for_chunk (mem2chunk (mem))); if (mem == 0 && av != NULL ) { LIBC_PROBE (memory_calloc_retry, 1 , sz); av = arena_get_retry (av, sz); mem = _int_malloc (av, sz); } if (av != NULL ) (void ) mutex_unlock (&av->mutex); if (mem == 0 ) return 0 ; p = mem2chunk (mem); if (chunk_is_mmapped (p)) { if (__builtin_expect (perturb_byte, 0 )) return memset (mem, 0 , sz); return mem; } csz = chunksize (p); #if MORECORE_CLEARS if (perturb_byte == 0 && (p == oldtop && csz > oldtopsize)) { csz = oldtopsize; } #endif d = (INTERNAL_SIZE_T *) mem; clearsize = csz - SIZE_SZ; nclears = clearsize / sizeof (INTERNAL_SIZE_T); assert (nclears >= 3 ); if (nclears > 9 ) return memset (d, 0 , clearsize); else { *(d + 0 ) = 0 ; *(d + 1 ) = 0 ; *(d + 2 ) = 0 ; if (nclears > 4 ) { *(d + 3 ) = 0 ; *(d + 4 ) = 0 ; if (nclears > 6 ) { *(d + 5 ) = 0 ; *(d + 6 ) = 0 ; if (nclears > 8 ) { *(d + 7 ) = 0 ; *(d + 8 ) = 0 ; } } } } return mem; }
__libc_realloc 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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 void *__libc_realloc (void *oldmem, size_t bytes) { mstate ar_ptr; INTERNAL_SIZE_T nb; void *newp; void *(*hook) (void *, size_t , const void *) = atomic_forced_read (__realloc_hook); if (__builtin_expect (hook != NULL , 0 )) return (*hook)(oldmem, bytes, RETURN_ADDRESS (0 )); #if REALLOC_ZERO_BYTES_FREES if (bytes == 0 && oldmem != NULL ) { __libc_free (oldmem); return 0 ; } #endif if (oldmem == 0 ) return __libc_malloc (bytes); const mchunkptr oldp = mem2chunk (oldmem); const INTERNAL_SIZE_T oldsize = chunksize (oldp); if (chunk_is_mmapped (oldp)) ar_ptr = NULL ; else ar_ptr = arena_for_chunk (oldp); if (__builtin_expect ((uintptr_t ) oldp > (uintptr_t ) -oldsize, 0 ) || __builtin_expect (misaligned_chunk (oldp), 0 )) { malloc_printerr (check_action, "realloc(): invalid pointer" , oldmem, ar_ptr); return NULL ; } checked_request2size (bytes, nb); if (chunk_is_mmapped (oldp)) { void *newmem; #if HAVE_MREMAP newp = mremap_chunk (oldp, nb); if (newp) return chunk2mem (newp); #endif if (oldsize - SIZE_SZ >= nb) return oldmem; newmem = __libc_malloc (bytes); if (newmem == 0 ) return 0 ; memcpy (newmem, oldmem, oldsize - 2 * SIZE_SZ); munmap_chunk (oldp); return newmem; } (void ) mutex_lock (&ar_ptr->mutex); newp = _int_realloc (ar_ptr, oldp, oldsize, nb); (void ) mutex_unlock (&ar_ptr->mutex); assert (!newp || chunk_is_mmapped (mem2chunk (newp)) || ar_ptr == arena_for_chunk (mem2chunk (newp))); if (newp == NULL ) { LIBC_PROBE (memory_realloc_retry, 2 , bytes, oldmem); newp = __libc_malloc (bytes); if (newp != NULL ) { memcpy (newp, oldmem, oldsize - SIZE_SZ); _int_free (ar_ptr, oldp, 0 ); } } return newp; }
__libc_free 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 37 38 39 40 41 42 43 44 void __libc_free (void *mem) { mstate ar_ptr; mchunkptr p; void (*hook) (void *, const void *) = atomic_forced_read (__free_hook); if (__builtin_expect (hook != NULL , 0 )) { (*hook)(mem, RETURN_ADDRESS (0 )); return ; } if (mem == 0 ) return ; p = mem2chunk (mem); if (chunk_is_mmapped (p)) { if (!mp_.no_dyn_threshold && p->size > mp_.mmap_threshold && p->size <= DEFAULT_MMAP_THRESHOLD_MAX) { mp_.mmap_threshold = chunksize (p); mp_.trim_threshold = 2 * mp_.mmap_threshold; LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2 , mp_.mmap_threshold, mp_.trim_threshold); } munmap_chunk (p); return ; } ar_ptr = arena_for_chunk (p); _int_free (ar_ptr, p, 0 ); }
_int_malloc 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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 static void * _int_malloc (mstate av, size_t bytes) { INTERNAL_SIZE_T nb; unsigned int idx; mbinptr bin; mchunkptr victim; INTERNAL_SIZE_T size; int victim_index; mchunkptr remainder; unsigned long remainder_size; unsigned int block; unsigned int bit; unsigned int map ; mchunkptr fwd; mchunkptr bck; const char *errstr = NULL ; checked_request2size (bytes, nb); if (__glibc_unlikely (av == NULL )) { void *p = sysmalloc (nb, av); if (p != NULL ) alloc_perturb (p, bytes); return p; } if ((unsigned long ) (nb) <= (unsigned long ) (get_max_fast ())) { idx = fastbin_index (nb); mfastbinptr *fb = &fastbin (av, idx); mchunkptr pp = *fb; do { victim = pp; if (victim == NULL ) break ; } while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) != victim); if (victim != 0 ) { if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0 )) { errstr = "malloc(): memory corruption (fast)" ; errout: malloc_printerr (check_action, errstr, chunk2mem (victim), av); return NULL ; } check_remalloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } } if (in_smallbin_range (nb)) { idx = smallbin_index (nb); bin = bin_at (av, idx); if ((victim = last (bin)) != bin) { if (victim == 0 ) malloc_consolidate (av); else { bck = victim->bk; if (__glibc_unlikely (bck->fd != victim)) { errstr = "malloc(): smallbin double linked list corrupted" ; goto errout; } set_inuse_bit_at_offset (victim, nb); bin->bk = bck; bck->fd = bin; if (av != &main_arena) victim->size |= NON_MAIN_ARENA; check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } } } else { idx = largebin_index (nb); if (have_fastchunks (av)) malloc_consolidate (av); } for (;;) { int iters = 0 ; while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) { bck = victim->bk; if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0 ) || __builtin_expect (victim->size > av->system_mem, 0 )) malloc_printerr (check_action, "malloc(): memory corruption" , chunk2mem (victim), av); size = chunksize (victim); if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long ) (size) > (unsigned long ) (nb + MINSIZE)) { remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av); if (!in_smallbin_range (remainder_size)) { remainder->fd_nextsize = NULL ; remainder->bk_nextsize = NULL ; } set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0 )); set_head (remainder, remainder_size | PREV_INUSE); set_foot (remainder, remainder_size); check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av); if (size == nb) { set_inuse_bit_at_offset (victim, size); if (av != &main_arena) victim->size |= NON_MAIN_ARENA; check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } if (in_smallbin_range (size)) { victim_index = smallbin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; } else { victim_index = largebin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; if (fwd != bck) { size |= PREV_INUSE; assert ((bck->bk->size & NON_MAIN_ARENA) == 0 ); if ((unsigned long ) (size) < (unsigned long ) (bck->bk->size)) { fwd = bck; bck = bck->bk; victim->fd_nextsize = fwd->fd; victim->bk_nextsize = fwd->fd->bk_nextsize; fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; } else { assert ((fwd->size & NON_MAIN_ARENA) == 0 ); while ((unsigned long ) size < fwd->size) { fwd = fwd->fd_nextsize; assert ((fwd->size & NON_MAIN_ARENA) == 0 ); } if ((unsigned long ) size == (unsigned long ) fwd->size) fwd = fwd->fd; else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } bck = fwd->bk; } } else victim->fd_nextsize = victim->bk_nextsize = victim; } mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim; #define MAX_ITERS 10000 if (++iters >= MAX_ITERS) break ; } if (!in_smallbin_range (nb)) { bin = bin_at (av, idx); if ((victim = first (bin)) != bin && (unsigned long ) (victim->size) >= (unsigned long ) (nb)) { victim = victim->bk_nextsize; while (((unsigned long ) (size = chunksize (victim)) < (unsigned long ) (nb))) victim = victim->bk_nextsize; if (victim != last (bin) && victim->size == victim->fd->size) victim = victim->fd; remainder_size = size - nb; unlink (av, victim, bck, fwd); if (remainder_size < MINSIZE) { set_inuse_bit_at_offset (victim, size); if (av != &main_arena) victim->size |= NON_MAIN_ARENA; } else { remainder = chunk_at_offset (victim, nb); bck = unsorted_chunks (av); fwd = bck->fd; if (__glibc_unlikely (fwd->bk != bck)) { errstr = "malloc(): corrupted unsorted chunks" ; goto errout; } remainder->bk = bck; remainder->fd = fwd; bck->fd = remainder; fwd->bk = remainder; if (!in_smallbin_range (remainder_size)) { remainder->fd_nextsize = NULL ; remainder->bk_nextsize = NULL ; } set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0 )); set_head (remainder, remainder_size | PREV_INUSE); set_foot (remainder, remainder_size); } check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } } ++idx; bin = bin_at (av, idx); block = idx2block (idx); map = av->binmap[block]; bit = idx2bit (idx); for (;; ) { if (bit > map || bit == 0 ) { do { if (++block >= BINMAPSIZE) goto use_top; } while ((map = av->binmap[block]) == 0 ); bin = bin_at (av, (block << BINMAPSHIFT)); bit = 1 ; } while ((bit & map ) == 0 ) { bin = next_bin (bin); bit <<= 1 ; assert (bit != 0 ); } victim = last (bin); if (victim == bin) { av->binmap[block] = map &= ~bit; bin = next_bin (bin); bit <<= 1 ; } else { size = chunksize (victim); assert ((unsigned long ) (size) >= (unsigned long ) (nb)); remainder_size = size - nb; unlink (av, victim, bck, fwd); if (remainder_size < MINSIZE) { set_inuse_bit_at_offset (victim, size); if (av != &main_arena) victim->size |= NON_MAIN_ARENA; } else { remainder = chunk_at_offset (victim, nb); bck = unsorted_chunks (av); fwd = bck->fd; if (__glibc_unlikely (fwd->bk != bck)) { errstr = "malloc(): corrupted unsorted chunks 2" ; goto errout; } remainder->bk = bck; remainder->fd = fwd; bck->fd = remainder; fwd->bk = remainder; if (in_smallbin_range (nb)) av->last_remainder = remainder; if (!in_smallbin_range (remainder_size)) { remainder->fd_nextsize = NULL ; remainder->bk_nextsize = NULL ; } set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0 )); set_head (remainder, remainder_size | PREV_INUSE); set_foot (remainder, remainder_size); } check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } } use_top: victim = av->top; size = chunksize (victim); if ((unsigned long ) (size) >= (unsigned long ) (nb + MINSIZE)) { remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); av->top = remainder; set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0 )); set_head (remainder, remainder_size | PREV_INUSE); check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } else if (have_fastchunks (av)) { malloc_consolidate (av); if (in_smallbin_range (nb)) idx = smallbin_index (nb); else idx = largebin_index (nb); } else { void *p = sysmalloc (nb, av); if (p != NULL ) alloc_perturb (p, bytes); return p; } } }
_int_realloc 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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 void * _int_realloc(mstate av, mchunkptr oldp, INTERNAL_SIZE_T oldsize, INTERNAL_SIZE_T nb) { mchunkptr newp; INTERNAL_SIZE_T newsize; void * newmem; mchunkptr next; mchunkptr remainder; unsigned long remainder_size; mchunkptr bck; mchunkptr fwd; unsigned long copysize; unsigned int ncopies; INTERNAL_SIZE_T* s; INTERNAL_SIZE_T* d; const char *errstr = NULL ; if (__builtin_expect(oldp->size <= 2 * SIZE_SZ, 0 ) || __builtin_expect(oldsize >= av->system_mem, 0 )) { errstr = "realloc(): invalid old size" ; goto errout; } check_inuse_chunk(av, oldp); assert(!chunk_is_mmapped(oldp)); next = chunk_at_offset(oldp, oldsize); INTERNAL_SIZE_T nextsize = chunksize(next); if (__builtin_expect(next->size <= 2 * SIZE_SZ, 0 ) || __builtin_expect(nextsize >= av->system_mem, 0 )) { errstr = "realloc(): invalid next size" ; goto errout; } if ((unsigned long )(oldsize) >= (unsigned long )(nb)) { newp = oldp; newsize = oldsize; } else { if (next == av->top && (unsigned long )(newsize = oldsize + nextsize) >= (unsigned long )(nb + MINSIZE)) { set_head_size(oldp, nb | (av != &main_arena ? NON_MAIN_ARENA : 0 )); av->top = chunk_at_offset(oldp, nb); set_head(av->top, (newsize - nb) | PREV_INUSE); check_inuse_chunk(av, oldp); return chunk2mem(oldp); } else if (next != av->top && !inuse(next) && (unsigned long )(newsize = oldsize + nextsize) >= (unsigned long )(nb)) { newp = oldp; unlink(av, next, bck, fwd); } else { newmem = _int_malloc(av, nb - MALLOC_ALIGN_MASK); if (newmem == 0 ) return 0 ; newp = mem2chunk(newmem); newsize = chunksize(newp); if (newp == next) { newsize += oldsize; newp = oldp; } else { copysize = oldsize - SIZE_SZ; s = (INTERNAL_SIZE_T*)(chunk2mem(oldp)); d = (INTERNAL_SIZE_T*)(newmem); ncopies = copysize / sizeof (INTERNAL_SIZE_T); assert(ncopies >= 3 ); if (ncopies > 9 ) memcpy (d, s, copysize); else { *(d + 0 ) = *(s + 0 ); *(d + 1 ) = *(s + 1 ); *(d + 2 ) = *(s + 2 ); if (ncopies > 4 ) { *(d + 3 ) = *(s + 3 ); *(d + 4 ) = *(s + 4 ); if (ncopies > 6 ) { *(d + 5 ) = *(s + 5 ); *(d + 6 ) = *(s + 6 ); if (ncopies > 8 ) { *(d + 7 ) = *(s + 7 ); *(d + 8 ) = *(s + 8 ); } } } } _int_free(av, oldp, 1 ); check_inuse_chunk(av, newp); return chunk2mem(newp); } } } assert((unsigned long )(newsize) >= (unsigned long )(nb)); remainder_size = newsize - nb; if (remainder_size < MINSIZE) { set_head_size(newp, newsize | (av != &main_arena ? NON_MAIN_ARENA : 0 )); set_inuse_bit_at_offset(newp, newsize); } else { remainder = chunk_at_offset(newp, nb); set_head_size(newp, nb | (av != &main_arena ? NON_MAIN_ARENA : 0 )); set_head(remainder, remainder_size | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0 )); set_inuse_bit_at_offset(remainder, remainder_size); _int_free(av, remainder, 1 ); } check_inuse_chunk(av, newp); return chunk2mem(newp); }
_int_free 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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 static void _int_free (mstate av, mchunkptr p, int have_lock) { INTERNAL_SIZE_T size; mfastbinptr *fb; mchunkptr nextchunk; INTERNAL_SIZE_T nextsize; int nextinuse; INTERNAL_SIZE_T prevsize; mchunkptr bck; mchunkptr fwd; const char *errstr = NULL ; int locked = 0 ; size = chunksize (p); if (__builtin_expect ((uintptr_t ) p > (uintptr_t ) -size, 0 ) || __builtin_expect (misaligned_chunk (p), 0 )) { errstr = "free(): invalid pointer" ; errout: if (!have_lock && locked) (void ) mutex_unlock (&av->mutex); malloc_printerr (check_action, errstr, chunk2mem (p), av); return ; } if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size))) { errstr = "free(): invalid size" ; goto errout; } check_inuse_chunk(av, p); if ((unsigned long )(size) <= (unsigned long )(get_max_fast ()) #if TRIM_FASTBINS && (chunk_at_offset(p, size) != av->top) #endif ) { if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0 ) || __builtin_expect (chunksize (chunk_at_offset (p, size)) >= av->system_mem, 0 )) { if (have_lock || ({ assert (locked == 0 ); mutex_lock(&av->mutex); locked = 1 ; chunk_at_offset (p, size)->size <= 2 * SIZE_SZ || chunksize (chunk_at_offset (p, size)) >= av->system_mem; })) { errstr = "free(): invalid next size (fast)" ; goto errout; } if (! have_lock) { (void )mutex_unlock(&av->mutex); locked = 0 ; } } free_perturb (chunk2mem(p), size - 2 * SIZE_SZ); set_fastchunks(av); unsigned int idx = fastbin_index(size); fb = &fastbin (av, idx); mchunkptr old = *fb, old2; unsigned int old_idx = ~0u ; do { if (__builtin_expect (old == p, 0 )) { errstr = "double free or corruption (fasttop)" ; goto errout; } if (have_lock && old != NULL ) old_idx = fastbin_index(chunksize(old)); p->fd = old2 = old; } while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2)) != old2); if (have_lock && old != NULL && __builtin_expect (old_idx != idx, 0 )) { errstr = "invalid fastbin entry (free)" ; goto errout; } } else if (!chunk_is_mmapped(p)) { if (! have_lock) { (void )mutex_lock(&av->mutex); locked = 1 ; } nextchunk = chunk_at_offset(p, size); if (__glibc_unlikely (p == av->top)) { errstr = "double free or corruption (top)" ; goto errout; } if (__builtin_expect (contiguous (av) && (char *) nextchunk >= ((char *) av->top + chunksize(av->top)), 0 )) { errstr = "double free or corruption (out)" ; goto errout; } if (__glibc_unlikely (!prev_inuse(nextchunk))) { errstr = "double free or corruption (!prev)" ; goto errout; } nextsize = chunksize(nextchunk); if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0 ) || __builtin_expect (nextsize >= av->system_mem, 0 )) { errstr = "free(): invalid next size (normal)" ; goto errout; } free_perturb (chunk2mem(p), size - 2 * SIZE_SZ); if (!prev_inuse(p)) { prevsize = p->prev_size; size += prevsize; p = chunk_at_offset(p, -((long ) prevsize)); unlink(av, p, bck, fwd); } if (nextchunk != av->top) { nextinuse = inuse_bit_at_offset(nextchunk, nextsize); if (!nextinuse) { unlink(av, nextchunk, bck, fwd); size += nextsize; } else clear_inuse_bit_at_offset(nextchunk, 0 ); bck = unsorted_chunks(av); fwd = bck->fd; if (__glibc_unlikely (fwd->bk != bck)) { errstr = "free(): corrupted unsorted chunks" ; goto errout; } p->fd = fwd; p->bk = bck; if (!in_smallbin_range(size)) { p->fd_nextsize = NULL ; p->bk_nextsize = NULL ; } bck->fd = p; fwd->bk = p; set_head(p, size | PREV_INUSE); set_foot(p, size); check_free_chunk(av, p); } else { size += nextsize; set_head(p, size | PREV_INUSE); av->top = p; check_chunk(av, p); } if ((unsigned long )(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) { if (have_fastchunks(av)) malloc_consolidate(av); if (av == &main_arena) { #ifndef MORECORE_CANNOT_TRIM if ((unsigned long )(chunksize(av->top)) >= (unsigned long )(mp_.trim_threshold)) systrim(mp_.top_pad, av); #endif } else { heap_info *heap = heap_for_ptr(top(av)); assert(heap->ar_ptr == av); heap_trim(heap, mp_.top_pad); } } if (! have_lock) { assert (locked); (void )mutex_unlock(&av->mutex); } } else { munmap_chunk (p); } }
malloc_consolidate 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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 static void malloc_consolidate (mstate av) { mfastbinptr* fb; mfastbinptr* maxfb; mchunkptr p; mchunkptr nextp; mchunkptr unsorted_bin; mchunkptr first_unsorted; mchunkptr nextchunk; INTERNAL_SIZE_T size; INTERNAL_SIZE_T nextsize; INTERNAL_SIZE_T prevsize; int nextinuse; mchunkptr bck; mchunkptr fwd; if (get_max_fast () != 0 ) { clear_fastchunks(av); unsorted_bin = unsorted_chunks(av); maxfb = &fastbin (av, NFASTBINS - 1 ); fb = &fastbin (av, 0 ); do { p = atomic_exchange_acq (fb, 0 ); if (p != 0 ) { do { check_inuse_chunk(av, p); nextp = p->fd; size = p->size & ~(PREV_INUSE|NON_MAIN_ARENA); nextchunk = chunk_at_offset(p, size); nextsize = chunksize(nextchunk); if (!prev_inuse(p)) { prevsize = p->prev_size; size += prevsize; p = chunk_at_offset(p, -((long ) prevsize)); unlink(av, p, bck, fwd); } if (nextchunk != av->top) { nextinuse = inuse_bit_at_offset(nextchunk, nextsize); if (!nextinuse) { size += nextsize; unlink(av, nextchunk, bck, fwd); } else clear_inuse_bit_at_offset(nextchunk, 0 ); first_unsorted = unsorted_bin->fd; unsorted_bin->fd = p; first_unsorted->bk = p; if (!in_smallbin_range (size)) { p->fd_nextsize = NULL ; p->bk_nextsize = NULL ; } set_head(p, size | PREV_INUSE); p->bk = unsorted_bin; p->fd = first_unsorted; set_foot(p, size); } else { size += nextsize; set_head(p, size | PREV_INUSE); av->top = p; } } while ( (p = nextp) != 0 ); } } while (fb++ != maxfb); } else { malloc_init_state(av); check_malloc_state(av); } } void __libc_free (void *mem) { mstate ar_ptr; mchunkptr p; void (*hook) (void *, const void *) = atomic_forced_read (__free_hook); if (__builtin_expect (hook != NULL , 0 )) { (*hook)(mem, RETURN_ADDRESS (0 )); return ; } if (mem == 0 ) return ; p = mem2chunk (mem); if (chunk_is_mmapped (p)) { if (!mp_.no_dyn_threshold && p->size > mp_.mmap_threshold && p->size <= DEFAULT_MMAP_THRESHOLD_MAX) { mp_.mmap_threshold = chunksize (p); mp_.trim_threshold = 2 * mp_.mmap_threshold; LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2 , mp_.mmap_threshold, mp_.trim_threshold); } munmap_chunk (p); return ; } ar_ptr = arena_for_chunk (p); _int_free (ar_ptr, p, 0 ); }
sysmalloc 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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 static void *sysmalloc (INTERNAL_SIZE_T nb, mstate av) { mchunkptr old_top; INTERNAL_SIZE_T old_size; char *old_end; long size; char *brk; long correction; char *snd_brk; INTERNAL_SIZE_T front_misalign; INTERNAL_SIZE_T end_misalign; char *aligned_brk; mchunkptr p; mchunkptr remainder; unsigned long remainder_size; size_t pagesize = GLRO(dl_pagesize); bool tried_mmap = false ; if (av == NULL || ((unsigned long )(nb) >= (unsigned long )(mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max))) { char *mm; try_mmap: if (MALLOC_ALIGNMENT == 2 * SIZE_SZ) size = ALIGN_UP(nb + SIZE_SZ, pagesize); else size = ALIGN_UP(nb + SIZE_SZ + MALLOC_ALIGN_MASK, pagesize); tried_mmap = true ; if ((unsigned long )(size) > (unsigned long )(nb)) { mm = (char *)(MMAP(0 , size, PROT_READ | PROT_WRITE, 0 )); if (mm != MAP_FAILED) { if (MALLOC_ALIGNMENT == 2 * SIZE_SZ) { assert(((INTERNAL_SIZE_T)chunk2mem(mm) & MALLOC_ALIGN_MASK) == 0 ); front_misalign = 0 ; } else { front_misalign = (INTERNAL_SIZE_T)chunk2mem(mm) & MALLOC_ALIGN_MASK; if (front_misalign > 0 ) { correction = MALLOC_ALIGNMENT - front_misalign; p = (mchunkptr)(mm + correction); p->prev_size = correction; set_head(p, (size - correction) | IS_MMAPPED); } else { p = (mchunkptr)mm; set_head(p, size | IS_MMAPPED); } } int new = atomic_exchange_and_add(&mp_.n_mmaps, 1 ) + 1 ; atomic_max (&mp_.max_n_mmaps, new); unsigned long sum; sum = atomic_exchange_and_add(&mp_.mmapped_mem, size) + size; atomic_max (&mp_.max_mmapped_mem, sum); check_chunk(av, p); return chunk2mem(p); } } } if (av == NULL ) return NULL ; old_top = av->top; old_size = chunksize(old_top); old_end = (char *)(chunk_at_offset(old_top, old_size)); brk = snd_brk = (char *)(MORECORE_FAILURE); assert((old_top == initial_top(av) && old_size == 0 ) || ((unsigned long )(old_size) >= MINSIZE && prev_inuse(old_top) && ((unsigned long )old_end & (pagesize - 1 )) == 0 )); assert((unsigned long )(old_size) < (unsigned long )(nb + MINSIZE)); if (av != &main_arena) { heap_info *old_heap, *heap; size_t old_heap_size; old_heap = heap_for_ptr(old_top); old_heap_size = old_heap->size; if ((long )(MINSIZE + nb - old_size) > 0 && grow_heap(old_heap, MINSIZE + nb - old_size) == 0 ) { av->system_mem += old_heap->size - old_heap_size; arena_mem += old_heap->size - old_heap_size; set_head(old_top, (((char *)old_heap + old_heap->size) - (char *)old_top) | PREV_INUSE); } else if ((heap = new_heap(nb + (MINSIZE + sizeof (*heap)), mp_.top_pad))) { heap->ar_ptr = av; heap->prev = old_heap; av->system_mem += heap->size; arena_mem += heap->size; top(av) = chunk_at_offset(heap, sizeof (*heap)); set_head(top(av), (heap->size - sizeof (*heap)) | PREV_INUSE); old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK; set_head(chunk_at_offset(old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE); if (old_size >= MINSIZE) { set_head(chunk_at_offset(old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE); set_foot(chunk_at_offset(old_top, old_size), (2 * SIZE_SZ)); set_head(old_top, old_size | PREV_INUSE | NON_MAIN_ARENA); _int_free(av, old_top, 1 ); } else { set_head(old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE); set_foot(old_top, (old_size + 2 * SIZE_SZ)); } } else if (!tried_mmap) goto try_mmap; } else { size = nb + mp_.top_pad + MINSIZE; if (contiguous(av)) size -= old_size; size = ALIGN_UP(size, pagesize); if (size > 0 ) { brk = (char *)(MORECORE(size)); LIBC_PROBE(memory_sbrk_more, 2 , brk, size); } if (brk != (char *)(MORECORE_FAILURE)) { void (*hook)(void ) = atomic_forced_read(__after_morecore_hook); if (__builtin_expect(hook != NULL , 0 )) (*hook)(); } else { if (contiguous(av)) size = ALIGN_UP(size + old_size, pagesize); if ((unsigned long )(size) < (unsigned long )(MMAP_AS_MORECORE_SIZE)) size = MMAP_AS_MORECORE_SIZE; if ((unsigned long )(size) > (unsigned long )(nb)) { char *mbrk = (char *)(MMAP(0 , size, PROT_READ | PROT_WRITE, 0 )); if (mbrk != MAP_FAILED) { brk = mbrk; snd_brk = brk + size; set_noncontiguous(av); } } } if (brk != (char *)(MORECORE_FAILURE)) { if (mp_.sbrk_base == 0 ) mp_.sbrk_base = brk; av->system_mem += size; if (brk == old_end && snd_brk == (char *)(MORECORE_FAILURE)) set_head(old_top, (size + old_size) | PREV_INUSE); else if (contiguous(av) && old_size && brk < old_end) { malloc_printerr(3 , "break adjusted to free malloc space" , brk, av); } else { front_misalign = 0 ; end_misalign = 0 ; correction = 0 ; aligned_brk = brk; if (contiguous(av)) { if (old_size) av->system_mem += brk - old_end; front_misalign = (INTERNAL_SIZE_T)chunk2mem(brk) & MALLOC_ALIGN_MASK; if (front_misalign > 0 ) { correction = MALLOC_ALIGNMENT - front_misalign; aligned_brk += correction; } correction += old_size; end_misalign = (INTERNAL_SIZE_T)(brk + size + correction); correction += (ALIGN_UP(end_misalign, pagesize)) - end_misalign; assert(correction >= 0 ); snd_brk = (char *)(MORECORE(correction)); if (snd_brk == (char *)(MORECORE_FAILURE)) { correction = 0 ; snd_brk = (char *)(MORECORE(0 )); } else { void (*hook)(void ) = atomic_forced_read(__after_morecore_hook); if (__builtin_expect(hook != NULL , 0 )) (*hook)(); } } else { if (MALLOC_ALIGNMENT == 2 * SIZE_SZ) assert(((unsigned long )chunk2mem(brk) & MALLOC_ALIGN_MASK) == 0 ); else { front_misalign = (INTERNAL_SIZE_T)chunk2mem(brk) & MALLOC_ALIGN_MASK; if (front_misalign > 0 ) { aligned_brk += MALLOC_ALIGNMENT - front_misalign; } } if (snd_brk == (char *)(MORECORE_FAILURE)) { snd_brk = (char *)(MORECORE(0 )); } } if (snd_brk != (char *)(MORECORE_FAILURE)) { av->top = (mchunkptr)aligned_brk; set_head(av->top, (snd_brk - aligned_brk + correction) | PREV_INUSE); av->system_mem += correction; if (old_size != 0 ) { old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK; set_head(old_top, old_size | PREV_INUSE); chunk_at_offset(old_top, old_size)->size = (2 * SIZE_SZ) | PREV_INUSE; chunk_at_offset(old_top, old_size + 2 * SIZE_SZ)->size = (2 * SIZE_SZ) | PREV_INUSE; if (old_size >= MINSIZE) { _int_free(av, old_top, 1 ); } } } } } } if ((unsigned long )av->system_mem > (unsigned long )(av->max_system_mem)) av->max_system_mem = av->system_mem; check_malloc_state(av); p = av->top; size = chunksize(p); if ((unsigned long )(size) >= (unsigned long )(nb + MINSIZE)) { remainder_size = size - nb; remainder = chunk_at_offset(p, nb); av->top = remainder; set_head(p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0 )); set_head(remainder, remainder_size | PREV_INUSE); check_malloced_chunk(av, p, nb); return chunk2mem(p); } __set_errno(ENOMEM); return NULL ; }
systrim 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 37 38 39 40 41 42 static int systrim (size_t pad, mstate av) { long top_size; long extra; long released; char *current_brk; char *new_brk; size_t pagesize; long top_area; pagesize = GLRO (dl_pagesize); top_size = chunksize (av->top); top_area = top_size - MINSIZE - 1 ; if (top_area <= pad) return 0 ; extra = ALIGN_DOWN(top_area - pad, pagesize); if (extra == 0 ) return 0 ; current_brk = (char *) (MORECORE (0 )); if (current_brk == (char *) (av->top) + top_size) { MORECORE (-extra); void (*hook) (void ) = atomic_forced_read (__after_morecore_hook); if (__builtin_expect (hook != NULL , 0 )) (*hook)(); new_brk = (char *) (MORECORE (0 )); LIBC_PROBE (memory_sbrk_less, 2 , new_brk, extra); if (new_brk != (char *) MORECORE_FAILURE) { released = (long ) (current_brk - new_brk); if (released != 0 ) { av->system_mem -= released; set_head (av->top, (top_size - released) | PREV_INUSE); check_malloc_state (av); return 1 ; } } } return 0 ; }
版本变化 2.24 2.25 2.27 2.28 2.29