在Postgresql的内存管理模块中,最常用的aset.c提供的内存池实现,该实现提供了两个非常实用的开关来解决常见的内存越界问题:
memdebug.c
* About CLOBBER_FREED_MEMORY:
*
* If this symbol is defined, all freed memory is overwritten with 0x7F's.
* This is useful for catching places that reference already-freed memory.
*
* About MEMORY_CONTEXT_CHECKING:
*
* Since we usually round request sizes up to the next power of 2, there
* is often some unused space immediately after a requested data area.
* Thus, if someone makes the common error of writing past what they've
* requested, the problem is likely to go unnoticed ... until the day when
* there *isn't* any wasted space, perhaps because of different memory
* alignment on a new platform, or some other effect. To catch this sort
* of problem, the MEMORY_CONTEXT_CHECKING option stores 0x7E just beyond
* the requested space whenever the request is less than the actual chunk
* size, and verifies that the byte is undamaged when the chunk is freed.
简单总结如下:
其实这两个宏在打开USE_ASSERT_CHECKING的使用就默认会生效。所以建议configure时记得打开enable_cassert。
/*
* Define this to cause pfree()'d memory to be cleared immediately, to
* facilitate catching bugs that refer to already-freed values.
* Right now, this gets defined automatically if --enable-cassert.
*/
#ifdef USE_ASSERT_CHECKING
#define CLOBBER_FREED_MEMORY
#endif
/*
* Define this to check memory allocation errors (scribbling on more
* bytes than were allocated). Right now, this gets defined
* automatically if --enable-cassert or USE_VALGRIND.
*/
#if defined(USE_ASSERT_CHECKING) || defined(USE_VALGRIND)
#define MEMORY_CONTEXT_CHECKING
#endif
下面讲讲这两宏的原理,也比较简单:
正常我们申请内存都是会向上对齐到2的幂上,比如申请5个字节实际上会分配8个字节出来,但是你只应该使用5个字节。
但是如果你内存越界访问到第六个字节后,实际上是不会发生任何破坏的,因为这第六个字节也没有人会用,释放时也不可能发现。这就造成了隐患(这类问题都比较难差会有奇怪的逻辑报错)。
如果打开MEMORY_CONTEXT_CHECKING宏后:
还有use after free的场景,因为在pfree时,内存块中的内容不会立即被覆盖或重写,很可能发生上面已经free了,但后面还能正常用的场景,在某些串行逻辑下,貌似一直都不会出现问题,这也埋下了隐患(这类问题都比较难差会有奇怪的逻辑报错)。
打开CLOBBER_FREED_MEMORY后,释放时会调用wipe_mem将内存覆盖成0X7F
static inline void
wipe_mem(void *ptr, size_t size)
{
VALGRIND_MAKE_MEM_UNDEFINED(ptr, size);
memset(ptr, 0x7F, size);
VALGRIND_MAKE_MEM_NOACCESS(ptr, size);
}
后面aset不会对0x7F做任何检查逻辑,因为没准你的数据就全是0x7F。但是memset后,肯定会将pfree的地址的数据立即覆盖掉,让后面使用者能尽早发现问题(看到一堆0x7F就知道是用了free后的地址了)。