Matasano blog调用“检查malloc()
的返回值”是“C编程反成语”。如果失败,malloc()
应自动为abort()
调用malloc()
。这个论点是,因为如果MallocErrorAbort=1
失败,你通常会想要中止该程序,那应该是默认行为,而不是你必须费力地键入的东西 - 或者忘记键入!-every time。
没有了解这个想法的优点,最简单的方法是什么?我正在寻找能够自动检测其他库函数(例如asprintf()
)的内存分配失败的东西。便携式解决方案会很精彩,但我也会对特定于mac的东西感到满意。
总结下面的最佳答案:
在运行程序之前设置malloc()
环境变量。自动适用于所有内存分配功能。
Use a dynamic library shim在运行时使用LD_PRELOAD
或DYLD_INSERT_LIBRARIES
加载自定义calloc()
包装器。您可能希望包装realloc()
,malloc()
,& c。同样。
定义您自己的free()
和dyld(RTLD_NEXT, "malloc")
功能,并使用calloc()
as shown here访问系统版本。同样,您可能希望包装realloc()
,#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
void *(*system_malloc)(size_t) = NULL;
void* malloc(size_t bytes) {
if (system_malloc == NULL) {
system_malloc = dlsym(RTLD_NEXT, "malloc");
}
void* ret = system_malloc(bytes);
if (ret == NULL) {
perror("malloc failed, aborting");
abort();
}
return ret;
}
int main() {
void* m = malloc(10000000000000000l);
if (m == NULL) {
perror("malloc failed, program still running");
}
return 0;
}
,&amp; c。同样。
__malloc_hook
按照glibc manual。
中的说明使用__realloc_hook
和zone->malloc
使用malloc_default_zone()
function访问堆的数据结构,取消内存页面的保护,并在#include <malloc/malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
static void* (*system_malloc)(struct _malloc_zone_t *zone, size_t size);
static void* my_malloc(struct _malloc_zone_t *zone, size_t size) {
void* ret = system_malloc(zone, size);
if (ret == NULL) {
perror("malloc failed, aborting");
abort();
}
return ret;
}
int main() {
malloc_zone_t *zone = malloc_default_zone();
if (zone->version != 8) {
fprintf(stderr, "Unknown malloc zone version %d\n", zone->version);
abort();
}
system_malloc = zone->malloc;
if (mprotect(zone, getpagesize(), PROT_READ | PROT_WRITE) != 0) {
perror("munprotect failed");
abort();
}
zone->malloc = my_malloc;
if (mprotect(zone, getpagesize(), PROT_READ) != 0) {
perror("mprotect failed");
abort();
}
void* m = malloc(10000000000000000l);
if (m == NULL) {
perror("malloc failed, program still running");
}
return 0;
}
中安装一个钩子:
calloc()
为了完整起见,您可能希望将realloc()
,malloc_zone_t
以及/usr/include/malloc/malloc.h
中定义的其他函数包含在{{1}}中。
答案 0 :(得分:5)
只需将malloc()
包裹在执行此操作的某个my_malloc()
函数中。在很多情况下,它实际上可能处理不能分配内存,因此这种行为是不可取的。向malloc()
添加功能很容易,但不能删除它,这可能就是它的行为方式。
要记住的另一件事是,这是一个你正在调用的库。您是否想要进行图书馆电话会议并让图书馆杀死您的应用程序而不能让您发表意见?
我想我错过了关于asprintf
的部分,但是libc会导出一些你可以使用的钩子(valgrind本质上做什么)让你覆盖malloc行为。这里是钩子本身的参考,如果你足够了解C,那就不难了。
http://www.gnu.org/savannah-checkouts/gnu/libc/manual/html_node/Hooks-for-Malloc.html
答案 1 :(得分:2)
man malloc
提供了以下信息。看起来你想要MallocErrorAbort
。
环境
以下环境变量会更改与分配相关的函数的行为。
MallocLogFile <f>
创建/附加消息到给定的文件路径<f>
,而不是写入标准错误。
MallocGuardEdges
如果设置,则在每个大块之前和之后添加一个保护页面。
MallocDoNotProtectPrelude
如果设置,即使设置了MallocGuardEdges环境变量,也不要在大块之前添加保护页面。
MallocDoNotProtectPostlude
如果设置,即使设置了MallocGuardEdges环境变量,也不要在大块之后添加保护页面。
MallocStackLogging
如果设置,则记录所有堆栈,以便可以使用泄漏等工具。
MallocStackLoggingNoCompact
如果设置,则以与malloc_history程序兼容的方式记录所有堆栈。
MallocStackLoggingDirectory
如果设置,则将堆栈日志记录到指定的目录,而不是将它们保存到默认位置(/ tmp)。
MallocScribble
如果设置,请填充已分配0xaa字节的内存。这增加了程序对新分配的内存的内容做出假设的可能性。如果设置,则填充已用0x55字节释放的内存。这增加了程序因访问不再分配的内存而失败的可能性。
MallocCheckHeapStart <s>
如果设置,则指定在开始按照MallocCheckHeapEach指定的每<s>
进行定期堆检查之前要等待的分配数<n>
。如果设置了MallocCheckHeapStart但未指定MallocCheckHeapEach,则默认检查重复次数为1000。
MallocCheckHeapEach <n>
如果设置,请在每个<n>
操作的堆上运行一致性检查。 MallocCheckHeapEach仅在设置了MallocCheckHeapStart时才有意义。
MallocCheckHeapSleep <t>
设置MallocCheckHeapStart设置并检测到堆损坏时设置要休眠的秒数(等待调试器附加)。默认值为100秒。将此值设置为零意味着根本不睡觉。将其设置为负数意味着仅在第一次检测到堆损坏时才睡眠(正秒数)。
MallocCheckHeapAbort <b>
设置MallocCheckHeapStart并将其设置为非零值时,如果检测到堆损坏,则会调用abort(3),而不是任何休眠。
MallocErrorAbort
如果设置,则在malloc(3)或free(3)中遇到错误时调用abort(3),例如在先前释放的指针上调用free(3)。
MallocCorruptionAbort
与MallocErrorAbort类似,但不会在内存不足的情况下中止,因此仅捕获那些会导致内存损坏的错误更有用。 MallocCorruptionAbort始终设置为64位进程。
MallocHelp
如果设置,则打印由分配相关功能支付的环境变量列表以及简短描述。该列表应与此文档相对应。
请注意MallocCorruptionAbort
下有关MallocErrorAbort
。
对于我自己的大部分代码,我使用了一系列包装函数 - emalloc()
,erealloc()
,ecalloc()
,efree()
,estrdup()
等 - 检查失败的分配(efree()
是一个直接传递函数以保持一致性),并且在分配失败时不返回。他们退出或中止。这基本上是Jesus Ramos在answer中所暗示的内容;我同意他的建议。
然而,并非所有计划都能承受这种情况。我正在修复我编写的一些代码,这些代码确实使用了这些函数,因此它可以在不能解决分配错误的上下文中重用。出于其最初的目的(在进程启动的早期阶段进行安全检查),可以在出错时退出,但现在它需要在系统运行后可用,此时不允许过早退出。因此,代码必须处理那些代码以前能够假设“无分配失败”的路径。那有点痛苦。它仍然可以采取保守的观点;分配失败意味着请求不安全并适当地处理它。但并非所有代码都可以在内存分配失败时中止失败。