Ubuntu VM 上的 C free(),一个关于堆内存的问题

问题描述 投票:0回答:1

分配和释放堆内存的简单程序:

int main(int argc, char **argv) {
    char *b1, *b2, *b3, *b4, *b_large;
    b1 = malloc(8);
    memset(b1, 0xaa, 8);
    b2= malloc(16);
    memset(b2, 0xbb, 16);
    b3 = malloc(25);
    memset(b3, 0xcc, 25);
    b4= malloc(1000);
    memset(b4, 0xdd, 1000);
    free(b1);
    free(b2);
    free(b3);
    free(b4);

第一次 free() 之前:

(gdb) x/20gx  0x555555559290 
0x555555559290: 0x0000000000000000  0x0000000000000021
0x5555555592a0: 0xaaaaaaaaaaaaaaaa  0x0000000000000000
0x5555555592b0: 0x0000000000000000  0x0000000000000021
0x5555555592c0: 0xbbbbbbbbbbbbbbbb  0xbbbbbbbbbbbbbbbb
0x5555555592d0: 0x0000000000000000  0x0000000000000031
0x5555555592e0: 0xcccccccccccccccc  0xcccccccccccccccc
0x5555555592f0: 0xcccccccccccccccc  0x00000000000000cc
0x555555559300: 0x0000000000000000  0x00000000000003f1
0x555555559310: 0xdddddddddddddddd  0xdddddddddddddddd
0x555555559320: 0xdddddddddddddddd  0xdddddddddddddddd

在第一次 free() 之后:

(gdb) x/20gx  0x555555559290 
0x555555559290: 0x0000000000000000  0x0000000000000021
0x5555555592a0: 0x0000000555555559  0xd13e7903c502febc
0x5555555592b0: 0x0000000000000000  0x0000000000000021
0x5555555592c0: 0xbbbbbbbbbbbbbbbb  0xbbbbbbbbbbbbbbbb
0x5555555592d0: 0x0000000000000000  0x0000000000000031
0x5555555592e0: 0xcccccccccccccccc  0xcccccccccccccccc
0x5555555592f0: 0xcccccccccccccccc  0x00000000000000cc
0x555555559300: 0x0000000000000000  0x00000000000003f1
0x555555559310: 0xdddddddddddddddd  0xdddddddddddddddd
0x555555559320: 0xdddddddddddddddd  0xdddddddddddddddd

我期望在第二行内存中看到可读的前进和后退指针,并且 第三行 0x20 在两个 8 字节段中。

任何人都可以解释为什么 free() 函数会以这种方式运行吗?

c heap-memory free
1个回答
0
投票

假设您使用的是 Ubuntu 22.04,因此使用 glibc 2.36。

您看到的看似随机的值是内部

tcache_key
)。它是一个随机的
uintptr_t
值,在程序启动时使用
getrandom(2)
初始化一次,然后插入到保存在 tcache 中的空闲块中。

tcache_key
的值被插入到进入tcache的块中,然后检查空闲作为针对双重释放的简单强化。当重新分配 tcache 块时,它会被“删除”。这是在 glibc 2.34 中实现的。以前(glibc 2.29-2.33)使用固定值而不是 tcache_key
如果您想知道“tcache”是什么,它是由多个存储桶组成的每线程缓存。每个存储桶都保留给定的分配大小,并将该大小的已释放块保存在最多 7 个元素(按 LIFO 顺序)的单链表中(新释放的块插入到头部)。当存储桶已满时,释放该大小的块不会将其添加到 tcache 中,而是遵循“正常”释放过程,并根据相当复杂的 glibc 算法最终进入正常的竞技场“垃圾箱”之一。

指向 tcache 存储桶中下一个块的指针也通过专用宏在 free 上被破坏,在 alloc 上未破坏(如您在

here

中看到的),这些宏使用内核通过 提供的映射 (mmap_base

) 的隐式随机性ASLR
。这就是为什么您在块中也看不到清晰的指针。


free(b1);

free(b2);` 之后,我有以下内容:

(gdb) x/20gx 0x5555555592a0 - 0x10
0x555555559290: 0x0000000000000000  0x0000000000000021
0x5555555592a0: 0x0000000555555559  0xdefa7fb306dd6989
0x5555555592b0: 0x0000000000000000  0x0000000000000021
0x5555555592c0: 0x000055500000c7f9  0xdefa7fb306dd6989
0x5555555592d0: 0x0000000000000000  0x0000000000000031
0x5555555592e0: 0xcccccccccccccccc  0xcccccccccccccccc
0x5555555592f0: 0xcccccccccccccccc  0x00000000000000cc
0x555555559300: 0x0000000000000000  0x00000000000003f1
0x555555559310: 0xdddddddddddddddd  0xdddddddddddddddd
0x555555559320: 0xdddddddddddddddd  0xdddddddddddddddd

b1

b2
最终都位于同一个
tcache
桶中,即最小的一个,尺寸为 16(
malloc(8)
四舍五入为 16)。

tcache

看起来像这样:
{ counts = {2, 0, 0, 0, ...}, entries = {0x5555555592c0, 0x0, 0x00, 0x00, ...} }

如上所示,
0xdefa7fb306dd6989

tcache_key
的值。查看
0x5555555592c0
处的空闲块,其
->next
指针已损坏,值为
0x000055500000c7f9
。真实值可以通过以下方式获得:
((((size_t)0x5555555592c0) >> 12) ^ ((size_t)0x000055500000c7f9)) == 0x5555555592a0

有关更多信息,另请参阅这篇有趣的文章:

Tcache Keys 原始的双免保护.

© www.soinside.com 2019 - 2024. All rights reserved.