[インデックス 17916] ファイルの概要
このコミットは、Goランタイムに新しいデバッグオプション efence
を追加するものです。このオプションは、メモリ割り当てをデバッグするための「Electric Fence」のようなヒープモードを提供します。有効にすると、各オブジェクトが独自のページに割り当てられ、メモリアドレスが再利用されなくなるため、広範なヒープ破損の問題の根本原因を特定するのに役立ちます。
コミット
commit 76c54c11935121d1c8f4158f900366d68f9a76d8
Author: Carl Shapiro <cshapiro@google.com>
Date: Fri Dec 6 14:40:45 2013 -0800
runtime: add GODEBUG option for an electric fence like heap mode
When enabled this new debugging mode will allocate objects on
their own page and never recycle memory addresses. This is an
essential tool to root cause a broad class of heap corruption.
R=golang-dev, dave, daniel.morsing, dvyukov, rsc, iant, cshapiro
CC=golang-dev
https://golang.org/cl/22060046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/76c54c11935121d1c8f4158f900366d68f9a76d8
元コミット内容
Goランタイムに、Electric Fenceのようなヒープモードを有効にする GODEBUG
オプションを追加します。この新しいデバッグモードが有効になると、オブジェクトはそれぞれ独自のメモリページに割り当てられ、メモリアドレスは決して再利用されません。これは、広範なヒープ破損の根本原因を特定するための不可欠なツールとなります。
変更の背景
ソフトウェア開発において、メモリの破損は最も厄介で診断が困難なバグの一つです。特にヒープメモリの破損は、プログラムの予期せぬクラッシュ、データの破壊、セキュリティ脆弱性など、様々な問題を引き起こします。これらの問題は、メモリの解放後にそのメモリにアクセスする(use-after-free)、割り当てられたバッファの境界を超えて書き込む(buffer overflow)、または解放されたメモリを二重に解放する(double-free)といった、メモリ管理の誤りによって発生します。
従来のデバッグ手法では、これらの問題の発生箇所を特定するのが非常に困難でした。なぜなら、メモリ破損が発生した時点と、それが顕在化してプログラムがクラッシュする時点との間に時間的・空間的な隔たりがあることが多いためです。例えば、ある場所でメモリが破損しても、その影響が別の場所で、しかもずっと後になってから現れることがあります。
このコミットは、このようなヒープ破損の問題をより効率的に診断するための新しいデバッグツールをGoランタイムに導入することを目的としています。具体的には、既存のデバッグツールであるElectric Fenceの概念をGoランタイムに適用し、メモリの不正利用を即座に検出できるようなメカニズムを提供します。
前提知識の解説
ヒープメモリとメモリ割り当て
プログラムが実行時に動的にメモリを要求する際に使用される領域が「ヒープメモリ」です。Go言語では、make
や new
などの組み込み関数を使ってオブジェクトを作成すると、そのオブジェクトはヒープに割り当てられます。ヒープメモリは、スタックメモリとは異なり、関数呼び出しの終了後もデータが保持されるため、ライフタイムが不定のデータを扱うのに適しています。
ガベージコレクション (GC)
Go言語はガベージコレクタ(GC)を備えており、プログラマが手動でメモリを解放する必要がありません。GCは、どのメモリがもはやプログラムによって到達不可能になったかを自動的に判断し、そのメモリを解放して再利用可能にします。これにより、メモリリークの発生を抑制し、開発者の負担を軽減します。
メモリ破損 (Memory Corruption)
メモリ破損とは、プログラムが意図しない方法でメモリの内容を変更してしまう状態を指します。ヒープ破損は、特にヒープ領域で発生するメモリ破損の一種です。
- Use-after-free: 解放されたメモリ領域にアクセスしようとすること。
- Buffer overflow/underflow: 割り当てられたバッファの境界を超えてデータを書き込む(または読み込む)こと。
- Double-free: 既に解放されたメモリ領域を再度解放しようとすること。
これらの問題は、プログラムのクラッシュ、セキュリティ脆弱性、または静かにデータが破壊される原因となります。
Electric Fence (efence)
Electric Fenceは、C/C++プログラムのメモリ破損を検出するためのデバッグライブラリです。その基本的なアイデアは、各メモリ割り当ての直後(または直前)にアクセス不可能なメモリページ(ガードページ)を配置することです。これにより、プログラムが割り当てられたメモリ領域の境界を超えてアクセスしようとすると、即座にセグメンテーション違反(または同等のエラー)が発生し、問題の発生箇所を特定しやすくなります。また、解放されたメモリをすぐに再利用せず、アクセス不可能な状態に保つことで、use-after-freeエラーも検出します。
GODEBUG環境変数
Goランタイムは、GODEBUG
環境変数を通じて様々なデバッグオプションを提供します。これは name=val
の形式のカンマ区切りリストで、ランタイムの動作を調整したり、デバッグ情報を出力したりするために使用されます。例えば、gctrace=1
はガベージコレクションのトレース情報を出力します。
技術的詳細
このコミットで導入される efence
モードは、Goランタイムのメモリ割り当てと解放のメカニズムを根本的に変更することで、Electric Fenceと同様のデバッグ機能を実現します。
-
各オブジェクトを独自のページに割り当て: 通常のGoランタイムでは、小さなオブジェクトはメモリ効率のためにまとめて割り当てられたり、既存の空き領域に配置されたりします。しかし、
efence
モードでは、各オブジェクトが専用のメモリページに割り当てられます。これにより、オブジェクトの境界を越えたアクセス(バッファオーバーフローなど)が発生した場合、そのアクセスが隣接するオブジェクトではなく、アクセス不可能なページに触れることになり、即座にページフォルト(セグメンテーション違反)が発生します。 -
メモリアドレスの再利用の禁止:
efence
モードでは、一度解放されたメモリページは、プログラムの実行中に決して再利用されません。これにより、use-after-freeエラーが発生した場合、解放されたメモリにアクセスしようとすると、そのメモリが既にシステムに返却されているか、アクセス不可能な状態になっているため、即座にエラーが検出されます。通常のランタイムでは、解放されたメモリはすぐに再利用される可能性があるため、use-after-freeエラーが別の有効なデータに影響を与え、診断を困難にすることがあります。
これらの変更は、Goランタイムのメモリ管理の核心部分、特に mallocgc
(メモリ割り当て) と free
(メモリ解放) のロジックに影響を与えます。
mallocgc
の変更:efence
が有効な場合、MaxSmallSize
以下の小さなオブジェクトであっても、通常のフリーリストからの割り当てをスキップし、常に新しいページを割り当てるように変更されます。free
およびsweepspan
の変更: メモリ解放時、efence
が有効な場合は、runtime·MHeap_Free
を呼び出してヒープにメモリを戻す代わりに、runtime·SysFree
を呼び出してシステムにメモリを返却します。これにより、そのメモリページが再利用されることを防ぎます。sweepspan
関数(ガベージコレクションの一部で、到達不能なメモリを解放する)でも同様の変更が行われます。
このモードは、パフォーマンスに大きな影響を与えます。各オブジェクトが独自のページを占有し、メモリが再利用されないため、メモリ使用量が劇的に増加し、割り当て/解放のオーバーヘッドも増大します。そのため、このモードはデバッグ目的でのみ使用され、本番環境での使用は想定されていません。
コアとなるコードの変更箇所
このコミットは主に以下のファイルに影響を与えています。
src/pkg/runtime/extern.go
:GODEBUG
環境変数で利用可能なオプションの説明を更新し、efence
オプションを追加します。src/pkg/runtime/malloc.goc
: メモリ割り当て関数runtime·mallocgc
のロジックを変更し、efence
モードが有効な場合に小さなオブジェクトの割り当て方法を調整します。また、メモリ解放関数runtime·free
のロジックを変更し、efence
モードが有効な場合にメモリをシステムに返却するようにします。src/pkg/runtime/mgc0.c
: ガベージコレクションの一部であるsweepspan
関数を変更し、efence
モードが有効な場合に解放されたメモリをシステムに返却するようにします。src/pkg/runtime/runtime.c
:GODEBUG
オプションの内部的なマッピングを定義するdbgvar
配列にefence
を追加します。src/pkg/runtime/runtime.h
: ランタイムのデバッグ変数を定義するDebugVars
構造体にefence
フィールドを追加します。
コアとなるコードの解説
src/pkg/runtime/extern.go
--- a/src/pkg/runtime/extern.go
+++ b/src/pkg/runtime/extern.go
@@ -24,20 +24,24 @@ percentage at run time. See http://golang.org/pkg/runtime/debug/#SetGCPercent.
The GODEBUG variable controls debug output from the runtime. GODEBUG value is
a comma-separated list of name=val pairs. Supported names are:
+ allocfreetrace: setting allocfreetrace=1 causes every allocation to be
+ profiled and a stack trace printed on each object\'s allocation and free.
+
+ efence: setting efence=1 causes the allocator to run in a mode
+ where each object is allocated on a unique page and addresses are
+ never recycled.
+
gctrace: setting gctrace=1 causes the garbage collector to emit a single line to standard
error at each collection, summarizing the amount of memory collected and the
length of the pause. Setting gctrace=2 emits the same summary but also
repeats each collection.
-\tschedtrace: setting schedtrace=X causes the scheduler to emit a single line to standard
-\terror every X milliseconds, summarizing the scheduler state.\n-\n \tscheddetail: setting schedtrace=X and scheddetail=1 causes the scheduler to emit
\tdetailed multiline info every X milliseconds, describing state of the scheduler,
\tprocessors, threads and goroutines.
-\tallocfreetrace: setting allocfreetrace=1 causes every allocation to be
-\tprofiled and a stack trace printed on each object\'s allocation and free.\n+\tschedtrace: setting schedtrace=X causes the scheduler to emit a single line to standard
+\terror every X milliseconds, summarizing the scheduler state.
The GOMAXPROCS variable limits the number of operating system threads that
can execute user-level Go code simultaneously. There is no limit to the number of threads
この変更は、GODEBUG
環境変数のドキュメントに efence
オプションの説明を追加しています。allocfreetrace
の説明が移動しているのは、単にリストの順序を調整したものです。
src/pkg/runtime/malloc.goc
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -58,7 +58,7 @@ runtime·mallocgc(uintptr size, uintptr typ, uint32 flag)
size += sizeof(uintptr);
c = m->mcache;
- if(size <= MaxSmallSize) {
+ if(!runtime·debug.efence && size <= MaxSmallSize) {
// Allocate from mcache free lists.
// Inlined version of SizeToClass().
if(size <= 1024-8)
@@ -196,7 +196,10 @@ runtime·free(void *v)
// they might coalesce v into other spans and change the bitmap further.
runtime·markfreed(v, size);
runtime·unmarkspan(v, 1<<PageShift);
- runtime·MHeap_Free(&runtime·mheap, s, 1);
+ if(runtime·debug.efence)
+ runtime·SysFree((void*)(s->start<<PageShift), size, &mstats.heap_sys);
+ else
+ runtime·MHeap_Free(&runtime·mheap, s, 1);
c->local_nlargefree++;
c->local_largefree += size;
} else {
runtime·mallocgc
関数では、if(!runtime·debug.efence && size <= MaxSmallSize)
という条件が追加されています。これは、efence
モードが有効でない(!runtime·debug.efence
)かつ、割り当てサイズがMaxSmallSize
以下の場合にのみ、mcache
のフリーリストから割り当てる通常のロジックを実行することを意味します。efence
が有効な場合、小さなオブジェクトであっても、このパスはスキップされ、より大きな割り当てパス(おそらく新しいページを要求するパス)が使用されることになります。runtime·free
関数では、メモリ解放時にif(runtime·debug.efence)
という条件分岐が追加されています。efence
が有効な場合、runtime·MHeap_Free
(ヒープにメモリを戻す) の代わりにruntime·SysFree
(システムにメモリを返却する) が呼び出されます。これにより、解放されたメモリがGoランタイムのヒープによって再利用されることを防ぎます。
src/pkg/runtime/mgc0.c
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -1797,7 +1797,10 @@ sweepspan(ParFor *desc, uint32 idx)
// Free large span.
runtime·unmarkspan(p, 1<<PageShift);
*(uintptr*)p = (uintptr)0xdeaddeaddeaddeadll; // needs zeroing
- runtime·MHeap_Free(&runtime·mheap, s, 1);
+ if(runtime·debug.efence)
+ runtime·SysFree(p, size, &mstats.gc_sys);
+ else
+ runtime·MHeap_Free(&runtime·mheap, s, 1);
c->local_nlargefree++;
c->local_largefree += size;
} else {
sweepspan
関数は、ガベージコレクションのスイープフェーズで到達不能なメモリを解放する役割を担います。ここでも efence
が有効な場合、runtime·MHeap_Free
の代わりに runtime·SysFree
が呼び出され、解放されたメモリがシステムに返却されるようになります。これにより、GCによって解放されたメモリも再利用されなくなります。
src/pkg/runtime/runtime.c
および src/pkg/runtime/runtime.h
これらのファイルは、efence
オプションを GODEBUG
環境変数から読み取れるようにするための、内部的な設定とデータ構造の変更です。
runtime.c
では、dbgvar
配列に{"efence", &runtime·debug.efence}
というエントリが追加され、GODEBUG=efence=1
が設定されたときにruntime·debug.efence
変数が1
に設定されるようにします。runtime.h
では、DebugVars
構造体にint32 efence;
フィールドが追加され、このデバッグオプションの状態を保持します。
関連リンク
- Go言語のガベージコレクション: https://go.dev/doc/gc-guide
- GODEBUG環境変数に関する公式ドキュメント: https://pkg.go.dev/runtime/debug#SetGCPercent (このコミットの時点では
extern.go
に記載されている情報が最新ですが、現在のGoのドキュメントではruntime/debug
パッケージに集約されています)
参考にした情報源リンク
- Electric Fence - Wikipedia: https://en.wikipedia.org/wiki/Electric_Fence
- Go言語のメモリ管理に関するブログ記事やドキュメント (一般的な知識として参照)
- Go言語のソースコード (特に
src/runtime
ディレクトリ) - Goのコミット履歴とコードレビュー (CL 22060046)