6

Matasano 博客将“检查返回值malloc()”称为“C 编程反成语”。相反,如果它失败了,它malloc()应该自动调用你。abort()论点是,由于您通常希望在程序malloc()失败时中止程序,因此这应该是默认行为,而不是您每次都必须费力地键入或忘记键入的内容。

在不深入了解这个想法的优点的情况下,最简单的设置方法是什么?我正在寻找可以通过其他库函数自动检测内存分配失败的东西,例如asprintf()。一个便携的解决方案会很棒,但我也会对特定于 mac 的东西感到满意。


总结以下最佳答案:

Mac 运行时解决方案

在运行程序之前设置MallocErrorAbort=1环境变量。自动适用于所有内存分配功能。

Mac/linux 运行时解决方案

使用动态库 shim在运行时使用或加载自定义malloc()包装器。您可能想要换行, , & c。也是。LD_PRELOADDYLD_INSERT_LIBRARIEScalloc()realloc()

Mac/linux编译解决方案

定义您自己的malloc()free()函数,并使用dyld(RTLD_NEXT, "malloc") 如下所示访问系统版本。同样,您可能想要换行calloc(), realloc(), & c。也是。

#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;
}

Linux编译解决方案

按照glibc 手册中的说明使用__malloc_hook和。__realloc_hook

mac编译解决方案

使用该malloc_default_zone()函数访问堆的数据结构,取消保护内存页面,并在 中安装一个钩子zone->malloc

#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(),和 in中realloc()定义的其他函数。malloc_zone_t/usr/include/malloc/malloc.h

4

2 回答 2

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

于 2013-01-25T03:54:07.623 回答
2

man malloc在我的 Mac 上提供以下信息。看起来像你想要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()是一个直接传递函数以确保一致性)并且在分配失败时不返回。他们要么退出,要么中止。这基本上就是耶稣拉莫斯在他的回答中所暗示的;我同意他的建议。

但是,并非所有程序都能负担得起这种情况。我正在修复我编写的一些代码,这些代码确实使用了这些函数,以便可以在不能因分配错误而失败的上下文中重用它。出于最初的目的(在进程启动的早期阶段进行安全检查),出错时退出是可以的,但现在它需要在系统运行后才能使用,此时不允许过早退出。因此,代码必须处理那些代码曾经能够假设“分配失败不返回”的路径。这有点痛苦。它仍然可以采取保守的观点;分配失败意味着请求不安全并对其进行适当处理。但并非所有代码都可以承受因内存分配失败而中止而失败的后果。

于 2013-01-25T04:10:02.153 回答