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()を使用してシステム バージョンにアクセスします。ここでも、、、およびcをラップしたいと思うでしょう。同じように。dyld(RTLD_NEXT, "malloc") calloc()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;
}

Linux コンパイル済みソリューション

glibc マニュアルの説明に従って__malloc_hookandを使用します。__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()完全を期すために、 、 、および で定義されrealloc()ているその他の関数もラップすることをお勧めします。malloc_zone_t/usr/include/malloc/malloc.h

4

2 に答える 2

5

代わりにこれを行う関数をラップmalloc()するだけです。my_malloc()多くの場合、実際にはメモリを割り当てられないことを処理できるため、この種の動作は望ましくありません。機能を追加するのは簡単ですmalloc()が、削除するのは簡単です。これがおそらく、このように動作する理由です。

心に留めておくべきもう 1 つのことは、これは呼び出し先のライブラリであるということです。ライブラリー呼び出しを行い、ライブラリーにあなたのアプリケーションを強制終了させたいと思いませんか?

私はその部分を見逃したと思いasprintfますが、libc は、malloc の動作をオーバーライドするために使用できるいくつかのフック (valgrind が本質的に行うこと) をエクスポートします。ここにフック自体への参照があります。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) でエラーが発生した場合 (以前に解放されたポインターで free(3) を呼び出した場合など)、abort(3) が呼び出されます。

  • MallocCorruptionAbort
    MallocErrorAbort に似ていますが、メモリ不足の状態では中止されないため、メモリ破損の原因となるエラーのみをキャッチする方が便利です。MallocCorruptionAbort は、64 ビット プロセスでは常に設定されます。

  • MallocHelp
    設定されている場合、割り当て関連の関数が注意を払う環境変数のリストと短い説明を表示します。リストは、このドキュメントに対応している必要があります。

MallocCorruptionAbortの動作に関する下のコメントに注意してくださいMallocErrorAbort


私自身のコードのほとんどで、一連のラッパー関数 ( 、 、 、 、 など) を使用emalloc()erealloc()ecalloc()efree()ます。これらは、estrdup()失敗した割り当てをチェックし (efree()一貫性を維持するための単純なパススルー関数です)、割り当てが失敗したときに戻りません。それらは終了するか中止します。これは基本的に、イエス・ラモスが彼の答えで示唆していることです。私は彼の提案に同意します。

ただし、すべてのプログラムがそれを実現できるわけではありません。私は、これらの関数を使用する私が書いたいくつかのコードを修正しているところです。これにより、割り当てエラーで失敗しても問題ないコンテキストで再利用できるようになります。本来の目的 (プロセス起動の非常に早い段階でのセキュリティ チェック) では、エラーで終了しても問題ありませんでしたが、システムの実行後、早期終了が許可されない場合に使用できるようにする必要があります。そのため、コードは、以前は「割り当てが失敗しても戻りません」と想定できたパスを処理する必要があります。それはちょっと痛いです。それはまだ保守的な見方をすることができます。割り当ての失敗は、リクエストが安全ではなく、適切に処理されることを意味します。しかし、すべてのコードがメモリ割り当ての失敗で中断して失敗する余裕があるわけではありません。

于 2013-01-25T04:10:02.153 に答える