[インデックス 19471] ファイルの概要
このコミットは、GoランタイムにおけるWindows環境でのヒープダンプ生成時のバグを修正するものです。具体的には、debug.WriteHeapDump
関数がWindows上で空のヒープダンプファイルを生成してしまう問題を解決し、同時にファイルディスクリプタの型に関するクロスプラットフォームな互換性を向上させています。
コミット
runtime: fix empty heap dump bug on windows.
Fixes #8119.
LGTM=khr, rsc
R=alex.brainman, khr, bradfitz, rsc
CC=golang-codereviews
https://golang.org/cl/93640044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a68b9be93518300b15b6830648f8e2be7ebbfdf3
元コミット内容
runtime: fix empty heap dump bug on windows.
Fixes #8119.
LGTM=khr, rsc
R=alex.brainman, khr, bradfitz, rsc
CC=golang-codereviews
https://golang.org/cl/93640044
変更の背景
このコミットは、GoランタイムがWindows上でヒープダンプを生成する際に、出力ファイルが空になってしまうというバグ(Issue #8119)を修正するために導入されました。ヒープダンプは、プログラムのメモリ使用状況をデバッグするために非常に重要な情報源であり、これが正しく生成されないことは、Windows環境でのGoアプリケーションのデバッグを著しく困難にします。
根本的な原因は、Goランタイム内部のruntime.write
関数が、Windowsのファイルハンドル(HANDLE
型)とUnix系のファイルディスクリプタ(int
型)の扱いの違いを適切に吸収できていなかったことにあります。特に、debug.WriteHeapDump
関数は、Goのユーザーランドからファイルディスクリプタを渡しますが、Windowsではこれが直接的なファイルハンドルとして解釈される必要がありました。従来のint32
型では、WindowsのHANDLE
型を完全に表現できない場合があり、これが問題を引き起こしていました。
この問題を解決するため、runtime.write
関数のファイルディスクリプタ引数の型を、より汎用的なuintptr
に変更し、Windows固有のファイルハンドル処理を適切に行うように修正が加えられました。また、この修正が正しく機能することを確認するために、ヒープダンプが空でないことを検証する新しいテストケースが追加されました。
前提知識の解説
1. ヒープダンプ (Heap Dump)
ヒープダンプとは、プログラムが実行中に使用しているメモリ領域(ヒープ)のスナップショットのことです。これには、プログラムが動的に確保したオブジェクトやデータ構造が含まれます。ヒープダンプは、メモリリークの検出、メモリ使用量の最適化、プログラムのクラッシュ原因の特定など、デバッグやプロファイリングにおいて非常に重要な役割を果たします。Go言語では、runtime/debug
パッケージのWriteHeapDump
関数を使ってヒープダンプを生成できます。
2. ファイルディスクリプタ (File Descriptor, FD) とファイルハンドル (File Handle)
- ファイルディスクリプタ (Unix/Linux/macOS): Unix系OSにおいて、ファイルやソケットなどのI/Oリソースを識別するために使用される非負の整数です。プログラムがファイルを開くと、OSは対応するファイルディスクリプタを返します。以降、プログラムはこのディスクリプタを使ってファイルへの読み書きを行います。
- ファイルハンドル (Windows): Windows OSにおいて、ファイルやデバイスなどのI/Oリソースを識別するために使用されるポインタ型の値です。Unix系のファイルディスクリプタと概念は似ていますが、その実体は異なります。Windows APIでは、
HANDLE
型として定義されており、これは通常void*
またはuintptr_t
にエイリアスされています。
Go言語のようなクロスプラットフォームな言語では、これらのOS固有のI/Oリソース識別子を抽象化して扱う必要があります。しかし、低レベルなランタイムコードでは、OS固有のインターフェースに直接アクセスする必要があるため、このような型の違いが問題となることがあります。
3. int32
と uintptr
int32
: 32ビット符号付き整数型です。Unix系のファイルディスクリプタは通常この型で十分表現できます。uintptr
: ポインタを保持できる符号なし整数型です。そのサイズは、実行環境のポインタサイズ(32ビットシステムでは32ビット、64ビットシステムでは64ビット)に依存します。WindowsのHANDLE
型はポインタであるため、uintptr
はHANDLE
の値を安全に保持できる汎用的な型として適しています。この型を使用することで、異なるOSのファイル識別子を統一的に扱うことが可能になります。
4. GetStdHandle
(Windows API)
GetStdHandle
はWindows API関数の一つで、標準入力、標準出力、標準エラー出力のいずれかのハンドルを取得するために使用されます。例えば、STD_OUTPUT_HANDLE
を渡すと、標準出力のハンドルが返されます。Goランタイムが標準出力や標準エラー出力にヒープダンプを書き込む場合、この関数を使って対応するハンドルを取得する必要があります。
技術的詳細
このバグは、Goのdebug.WriteHeapDump
関数が内部的にruntime.write
を呼び出す際に、Windows環境でのファイルディスクリプタの扱いに起因していました。
-
問題の根源:
- Goの
debug.WriteHeapDump
は、Goのユーザーランドからos.File
のファイルディスクリプタ(f.Fd()
)を受け取ります。これはGoのuintptr
型です。 - しかし、Goランタイム内部の
runtime.write
関数は、これまでファイルディスクリプタをint32
型として受け取っていました。 - Unix系OSでは、ファイルディスクリプタは小さな整数値であり、
int32
で問題なく扱えます。 - Windowsでは、
f.Fd()
が返す値は、実際にはWindowsのファイルハンドル(HANDLE
)です。HANDLE
はポインタであり、その値はint32
の範囲を超える可能性があります。また、HANDLE
は単なる整数ではなく、OSが管理するリソースへの参照です。 runtime/os_windows.c
内のruntime.write
の実装では、渡されたint32 fd
を直接HANDLE
としてキャストしてWriteFile
システムコールに渡していました。- このキャストが、
fd
が標準ハンドル(-10, -11, -12)でない場合に問題を引き起こしました。fd
が標準ハンドルでない場合、runtime.write
はreturn -1;
として処理を終了していました。これは、f.Fd()
が返す実際のファイルハンドルが、これらの特殊な値と一致しないため、ヒープダンプがファイルに書き込まれずに空になってしまう原因でした。
- Goの
-
修正アプローチ:
runtime.write
関数のシグネチャを、int32 fd
からuintptr fd
に変更しました。これにより、WindowsのHANDLE
値を安全に受け取れるようになりました。- Windows (
src/pkg/runtime/os_windows.c
) のruntime.write
実装において、fd
が標準ハンドル(STD_INPUT_HANDLE
,STD_OUTPUT_HANDLE
,STD_ERROR_HANDLE
に対応するGoランタイム内部の定数)でない場合、以前はreturn -1;
としていた箇所を削除し、代わりに渡されたfd
を直接HANDLE
として扱うように変更しました (handle = (void*)fd;
)。これは、f.Fd()
が返す値が既に有効なWindowsファイルハンドルであることを前提としています。 - Plan 9 (
src/pkg/runtime/os_plan9.c
) と Solaris (src/pkg/runtime/os_solaris.c
) のruntime.write
実装も、fd
の型をuintptr
に変更し、必要に応じてint32
にキャストし直すように修正されました。これは、これらのOSではファイルディスクリプタが依然としてint32
として扱われるためです。 src/pkg/runtime/runtime.h
でruntime.write
のプロトタイプ宣言が更新され、uintptr
型を使用することが明示されました。コメントには「// use uintptr to accommodate windows.」と追記され、Windowsへの対応が目的であることが示されています。- 新しいテストケース
TestWriteHeapDumpNonempty
がsrc/pkg/runtime/debug/heapdump_test.go
に追加されました。このテストは、一時ファイルにヒープダンプを書き込み、そのファイルサイズが少なくとも1バイト以上であることを確認することで、ヒープダンプが空でないことを検証します。
この修正により、GoランタイムはWindows環境でも正しくヒープダンプを生成できるようになり、クロスプラットフォームなファイルI/Oの堅牢性が向上しました。
コアとなるコードの変更箇所
このコミットでは、以下の5つのファイルが変更されています。
src/pkg/runtime/debug/heapdump_test.go
: 新しいテストファイルが追加されました。src/pkg/runtime/os_plan9.c
:runtime.write
関数の引数型が変更されました。src/pkg/runtime/os_solaris.c
:runtime.write
関数の引数型が変更されました。src/pkg/runtime/os_windows.c
:runtime.write
関数の引数型が変更され、Windows固有のファイルハンドル処理ロジックが修正されました。src/pkg/runtime/runtime.h
:runtime.write
関数のプロトタイプ宣言が更新されました。
コアとなるコードの解説
src/pkg/runtime/debug/heapdump_test.go
--- /dev/null
+++ b/src/pkg/runtime/debug/heapdump_test.go
@@ -0,0 +1,29 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package debug
+
+import (
+ "io/ioutil"
+ "os"
+ "testing"
+)
+
+func TestWriteHeapDumpNonempty(t *testing.T) {
+ f, err := ioutil.TempFile("", "heapdumptest")
+ if err != nil {
+ t.Fatalf("TempFile failed: %v", err)
+ }
+ defer os.Remove(f.Name())
+ defer f.Close()
+ WriteHeapDump(f.Fd())
+ fi, err := f.Stat()
+ if err != nil {
+ t.Fatalf("Stat failed: %v", err)
+ }
+ const minSize = 1
+ if size := fi.Size(); size < minSize {
+ t.Fatalf("Heap dump size %d bytes, expected at least %d bytes", size, minSize)
+ }
+}
このファイルは、ヒープダンプが正しく生成され、空でないことを検証するための新しいテストケース TestWriteHeapDumpNonempty
を追加しています。
ioutil.TempFile
を使用して一時ファイルを作成します。defer
ステートメントで、テスト終了後に一時ファイルを削除し、ファイルをクローズするように設定します。WriteHeapDump(f.Fd())
を呼び出し、作成した一時ファイルのファイルディスクリプタ(Windowsではハンドル)にヒープダンプを書き込みます。f.Stat()
でファイル情報を取得し、fi.Size()
でファイルサイズを確認します。- ファイルサイズが
minSize
(1バイト) 未満であれば、エラーとしてテストを失敗させます。これにより、ヒープダンプが空でなく、少なくとも何らかのデータが書き込まれたことを保証します。
src/pkg/runtime/os_plan9.c
--- a/src/pkg/runtime/os_plan9.c
+++ b/src/pkg/runtime/os_plan9.c
@@ -394,9 +394,9 @@ runtime·read(int32 fd, void *buf, int32 nbytes)\n }\n \n int32\n-runtime·write(int32 fd, void *buf, int32 nbytes)\n+runtime·write(uintptr fd, void *buf, int32 nbytes)\n {\n-\treturn runtime·pwrite(fd, buf, nbytes, -1LL);\n+\treturn runtime·pwrite((int32)fd, buf, nbytes, -1LL);\n }\n \n uintptr
Plan 9 OS向けのruntime.write
関数のシグネチャが変更されました。fd
の型がint32
からuintptr
に変更されています。内部では、runtime.pwrite
に渡す際にint32
にキャストし直しています。これは、Plan 9ではファイルディスクリプタが依然としてint32
として扱われるため、上位層からのuintptr
を受け入れつつ、下位層のAPIに合わせるための変更です。
src/pkg/runtime/os_solaris.c
--- a/src/pkg/runtime/os_solaris.c
+++ b/src/pkg/runtime/os_solaris.c
@@ -570,7 +570,7 @@ runtime·usleep(uint32 us)\n }\n \n int32\n-runtime·write(int32 fd, void* buf, int32 nbyte)\n+runtime·write(uintptr fd, void* buf, int32 nbyte)\n {\n \treturn runtime·sysvicall6(libc·write, 3, (uintptr)fd, (uintptr)buf, (uintptr)nbyte);\n }\
Solaris OS向けのruntime.write
関数のシグネチャも同様に、fd
の型がint32
からuintptr
に変更されました。libc·write
システムコールに渡す際も、fd
をuintptr
として渡しています。これは、Solarisのシステムコールがuintptr
を受け入れるため、直接渡す形になっています。
src/pkg/runtime/os_windows.c
--- a/src/pkg/runtime/os_windows.c
+++ b/src/pkg/runtime/os_windows.c
@@ -166,7 +166,7 @@ runtime·exit(int32 code)\n }\n \n int32\n-runtime·write(int32 fd, void *buf, int32 n)\n+runtime·write(uintptr fd, void *buf, int32 n)\n {\n \tvoid *handle;\n \tuint32 written;\
@@ -180,7 +180,9 @@ runtime·write(int32 fd, void *buf, int32 n)\n \t\thandle = runtime·stdcall(runtime·GetStdHandle, 1, (uintptr)-12);\n \t\tbreak;\n \tdefault:\n-\t\treturn -1;\n+\t\t// assume fd is real windows handle.\n+\t\thandle = (void*)fd;\n+\t\tbreak;\n \t}\n \truntime·stdcall(runtime·WriteFile, 5, handle, buf, (uintptr)n, &written, (uintptr)0);\n \treturn written;\
このファイルがWindowsでのヒープダンプバグ修正の核心です。
runtime.write
関数のfd
引数がint32
からuintptr
に変更されました。- 重要な変更は
default:
ケースです。以前は、fd
が標準ハンドル(STD_INPUT_HANDLE
など)でない場合、return -1;
として処理を中断していました。 - 修正後は、
default:
ケースで// assume fd is real windows handle.
というコメントが追加され、渡されたfd
が既に有効なWindowsファイルハンドルであると仮定し、handle = (void*)fd;
として直接handle
変数に代入しています。これにより、debug.WriteHeapDump
から渡される実際のファイルハンドルが正しくWriteFile
システムコールに渡されるようになり、ヒープダンプがファイルに書き込まれるようになりました。
src/pkg/runtime/runtime.h
--- a/src/pkg/runtime/runtime.h
+++ b/src/pkg/runtime/runtime.h
@@ -838,7 +838,7 @@ int32 runtime·gotraceback(bool *crash);\n void runtime·goroutineheader(G*);\n int32 runtime·open(int8*, int32, int32);\n int32 runtime·read(int32, void*, int32);\n-int32 runtime·write(int32, void*, int32);\n+int32 runtime·write(uintptr, void*, int32); // use uintptr to accommodate windows.\n int32 runtime·close(int32);\n int32 runtime·mincore(void*, uintptr, byte*);\n void runtime·jmpdefer(FuncVal*, void*);\
GoランタイムのCヘッダーファイルで、runtime.write
関数のプロトタイプ宣言が更新されました。fd
引数の型がint32
からuintptr
に変更され、// use uintptr to accommodate windows.
というコメントが追加されています。これは、Windowsのファイルハンドルを適切に扱うためにuintptr
を使用するという意図を明確に示しています。
関連リンク
- Go Issue #8119: https://github.com/golang/go/issues/8119
- Go CL 93640044: https://golang.org/cl/93640044
参考にした情報源リンク
- Go言語のソースコード (上記コミットの差分)
- Go Issue #8119 の議論
- Windows API ドキュメント (GetStdHandle, WriteFile, HANDLE型に関する一般的な知識)
- Unix系OSのファイルディスクリプタに関する一般的な知識
- Go言語の
uintptr
型に関する一般的な知識 - Go言語の
runtime
パッケージに関する一般的な知識 - Go言語の
runtime/debug
パッケージに関する一般的な知識 - Go言語のクロスコンパイルとOS固有のコードに関する一般的な知識I have generated the detailed explanation. I will now output it to standard output.
# [インデックス 19471] ファイルの概要
このコミットは、GoランタイムにおけるWindows環境でのヒープダンプ生成時のバグを修正するものです。具体的には、`debug.WriteHeapDump`関数がWindows上で空のヒープダンプファイルを生成してしまう問題を解決し、同時にファイルディスクリプタの型に関するクロスプラットフォームな互換性を向上させています。
## コミット
runtime: fix empty heap dump bug on windows. Fixes #8119.
LGTM=khr, rsc R=alex.brainman, khr, bradfitz, rsc CC=golang-codereviews https://golang.org/cl/93640044
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/a68b9be93518300b15b6830648f8e2be7ebbfdf3](https://github.org/golang/go/commit/a68b9be93518300b15b6830648f8e2be7ebbfdf3)
## 元コミット内容
runtime: fix empty heap dump bug on windows. Fixes #8119.
LGTM=khr, rsc R=alex.brainman, khr, bradfitz, rsc CC=golang-codereviews https://golang.org/cl/93640044
## 変更の背景
このコミットは、GoランタイムがWindows上でヒープダンプを生成する際に、出力ファイルが空になってしまうというバグ(Issue #8119)を修正するために導入されました。ヒープダンプは、プログラムのメモリ使用状況をデバッグするために非常に重要な情報源であり、これが正しく生成されないことは、Windows環境でのGoアプリケーションのデバッグを著しく困難にします。
根本的な原因は、Goランタイム内部の`runtime.write`関数が、Windowsのファイルハンドル(`HANDLE`型)とUnix系のファイルディスクリプタ(`int`型)の扱いの違いを適切に吸収できていなかったことにあります。特に、`debug.WriteHeapDump`関数は、Goのユーザーランドからファイルディスクリプタを渡しますが、Windowsではこれが直接的なファイルハンドルとして解釈される必要がありました。従来の`int32`型では、Windowsの`HANDLE`型を完全に表現できない場合があり、これが問題を引き起こしていました。
この問題を解決するため、`runtime.write`関数のファイルディスクリプタ引数の型を、より汎用的な`uintptr`に変更し、Windows固有のファイルハンドル処理を適切に行うように修正が加えられました。また、この修正が正しく機能することを確認するために、ヒープダンプが空でないことを検証する新しいテストケースが追加されました。
## 前提知識の解説
### 1. ヒープダンプ (Heap Dump)
ヒープダンプとは、プログラムが実行中に使用しているメモリ領域(ヒープ)のスナップショットのことです。これには、プログラムが動的に確保したオブジェクトやデータ構造が含まれます。ヒープダンプは、メモリリークの検出、メモリ使用量の最適化、プログラムのクラッシュ原因の特定など、デバッグやプロファイリングにおいて非常に重要な役割を果たします。Go言語では、`runtime/debug`パッケージの`WriteHeapDump`関数を使ってヒープダンプを生成できます。
### 2. ファイルディスクリプタ (File Descriptor, FD) とファイルハンドル (File Handle)
* **ファイルディスクリプタ (Unix/Linux/macOS)**: Unix系OSにおいて、ファイルやソケットなどのI/Oリソースを識別するために使用される非負の整数です。プログラムがファイルを開くと、OSは対応するファイルディスクリプタを返します。以降、プログラムはこのディスクリプタを使ってファイルへの読み書きを行います。
* **ファイルハンドル (Windows)**: Windows OSにおいて、ファイルやデバイスなどのI/Oリソースを識別するために使用されるポインタ型の値です。Unix系のファイルディスクリプタと概念は似ていますが、その実体は異なります。Windows APIでは、`HANDLE`型として定義されており、これは通常`void*`または`uintptr_t`にエイリアスされています。
Go言語のようなクロスプラットフォームな言語では、これらのOS固有のI/Oリソース識別子を抽象化して扱う必要があります。しかし、低レベルなランタイムコードでは、OS固有のインターフェースに直接アクセスする必要があるため、このような型の違いが問題となることがあります。
### 3. `int32` と `uintptr`
* **`int32`**: 32ビット符号付き整数型です。Unix系のファイルディスクリプタは通常この型で十分表現できます。
* **`uintptr`**: ポインタを保持できる符号なし整数型です。そのサイズは、実行環境のポインタサイズ(32ビットシステムでは32ビット、64ビットシステムでは64ビット)に依存します。Windowsの`HANDLE`型はポインタであるため、`uintptr`は`HANDLE`の値を安全に保持できる汎用的な型として適しています。この型を使用することで、異なるOSのファイル識別子を統一的に扱うことが可能になります。
### 4. `GetStdHandle` (Windows API)
`GetStdHandle`はWindows API関数の一つで、標準入力、標準出力、標準エラー出力のいずれかのハンドルを取得するために使用されます。例えば、`STD_OUTPUT_HANDLE`を渡すと、標準出力のハンドルが返されます。Goランタイムが標準出力や標準エラー出力にヒープダンプを書き込む場合、この関数を使って対応するハンドルを取得する必要があります。
## 技術的詳細
このバグは、Goの`debug.WriteHeapDump`関数が内部的に`runtime.write`を呼び出す際に、Windows環境でのファイルディスクリプタの扱いに起因していました。
1. **問題の根源**:
* Goの`debug.WriteHeapDump`は、Goのユーザーランドから`os.File`のファイルディスクリプタ(`f.Fd()`)を受け取ります。これはGoの`uintptr`型です。
* しかし、Goランタイム内部の`runtime.write`関数は、これまでファイルディスクリプタを`int32`型として受け取っていました。
* Unix系OSでは、ファイルディスクリプタは小さな整数値であり、`int32`で問題なく扱えます。
* Windowsでは、`f.Fd()`が返す値は、実際にはWindowsのファイルハンドル(`HANDLE`)です。`HANDLE`はポインタであり、その値は`int32`の範囲を超える可能性があります。また、`HANDLE`は単なる整数ではなく、OSが管理するリソースへの参照です。
* `runtime/os_windows.c`内の`runtime.write`の実装では、渡された`int32 fd`を直接`HANDLE`としてキャストして`WriteFile`システムコールに渡していました。
* このキャストが、`fd`が標準ハンドル(-10, -11, -12)でない場合に問題を引き起こしました。`fd`が標準ハンドルでない場合、`runtime.write`は`return -1;`として処理を終了していました。これは、`f.Fd()`が返す実際のファイルハンドルが、これらの特殊な値と一致しないため、ヒープダンプがファイルに書き込まれずに空になってしまう原因でした。
2. **修正アプローチ**:
* `runtime.write`関数のシグネチャを、`int32 fd`から`uintptr fd`に変更しました。これにより、Windowsの`HANDLE`値を安全に受け取れるようになりました。
* Windows (`src/pkg/runtime/os_windows.c`) の`runtime.write`実装において、`fd`が標準ハンドル(`STD_INPUT_HANDLE`, `STD_OUTPUT_HANDLE`, `STD_ERROR_HANDLE`に対応するGoランタイム内部の定数)でない場合、以前は`return -1;`としていた箇所を削除し、代わりに渡された`fd`を直接`HANDLE`として扱うように変更しました (`handle = (void*)fd;`)。これは、`f.Fd()`が返す値が既に有効なWindowsファイルハンドルであることを前提としています。
* Plan 9 (`src/pkg/runtime/os_plan9.c`) と Solaris (`src/pkg/runtime/os_solaris.c`) の`runtime.write`実装も、`fd`の型を`uintptr`に変更し、必要に応じて`int32`にキャストし直すように修正されました。これは、これらのOSではファイルディスクリプタが依然として`int32`として扱われるためです。
* `src/pkg/runtime/runtime.h`で`runtime.write`のプロトタイプ宣言が更新され、`uintptr`型を使用することが明示されました。コメントには「// use uintptr to accommodate windows.」と追記され、Windowsへの対応が目的であることが示されています。
* 新しいテストケース `TestWriteHeapDumpNonempty` が `src/pkg/runtime/debug/heapdump_test.go` に追加されました。このテストは、一時ファイルにヒープダンプを書き込み、そのファイルサイズが少なくとも1バイト以上であることを確認することで、ヒープダンプが空でないことを検証します。
この修正により、GoランタイムはWindows環境でも正しくヒープダンプを生成できるようになり、クロスプラットフォームなファイルI/Oの堅牢性が向上しました。
## コアとなるコードの変更箇所
このコミットでは、以下の5つのファイルが変更されています。
1. `src/pkg/runtime/debug/heapdump_test.go`: 新しいテストファイルが追加されました。
2. `src/pkg/runtime/os_plan9.c`: `runtime.write`関数の引数型が変更されました。
3. `src/pkg/runtime/os_solaris.c`: `runtime.write`関数の引数型が変更されました。
4. `src/pkg/runtime/os_windows.c`: `runtime.write`関数の引数型が変更され、Windows固有のファイルハンドル処理ロジックが修正されました。
5. `src/pkg/runtime/runtime.h`: `runtime.write`関数のプロトタイプ宣言が更新されました。
## コアとなるコードの解説
### `src/pkg/runtime/debug/heapdump_test.go`
```diff
--- /dev/null
+++ b/src/pkg/runtime/debug/heapdump_test.go
@@ -0,0 +1,29 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package debug
+
+import (
+ "io/ioutil"
+ "os"
+ "testing"
+)
+
+func TestWriteHeapDumpNonempty(t *testing.T) {
+ f, err := ioutil.TempFile("", "heapdumptest")
+ if err != nil {
+ t.Fatalf("TempFile failed: %v", err)
+ }
+ defer os.Remove(f.Name())
+ defer f.Close()
+ WriteHeapDump(f.Fd())
+ fi, err := f.Stat()
+ if err != nil {
+ t.Fatalf("Stat failed: %v", err)
+ }
+ const minSize = 1
+ if size := fi.Size(); size < minSize {
+ t.Fatalf("Heap dump size %d bytes, expected at least %d bytes", size, minSize)
+ }
+}
このファイルは、ヒープダンプが正しく生成され、空でないことを検証するための新しいテストケース TestWriteHeapDumpNonempty
を追加しています。
ioutil.TempFile
を使用して一時ファイルを作成します。defer
ステートメントで、テスト終了後に一時ファイルを削除し、ファイルをクローズするように設定します。WriteHeapDump(f.Fd())
を呼び出し、作成した一時ファイルのファイルディスクリプタ(Windowsではハンドル)にヒープダンプを書き込みます。f.Stat()
でファイル情報を取得し、fi.Size()
でファイルサイズを確認します。- ファイルサイズが
minSize
(1バイト) 未満であれば、エラーとしてテストを失敗させます。これにより、ヒープダンプが空でなく、少なくとも何らかのデータが書き込まれたことを保証します。
src/pkg/runtime/os_plan9.c
--- a/src/pkg/runtime/os_plan9.c
+++ b/src/pkg/runtime/os_plan9.c
@@ -394,9 +394,9 @@ runtime·read(int32 fd, void *buf, int32 nbytes)\n }\n \n int32\n-runtime·write(int32 fd, void *buf, int32 nbytes)\n+runtime·write(uintptr fd, void *buf, int32 nbytes)\n {\n-\treturn runtime·pwrite(fd, buf, nbytes, -1LL);\n+\treturn runtime·pwrite((int32)fd, buf, nbytes, -1LL);\n }\n \n uintptr
Plan 9 OS向けのruntime.write
関数のシグネチャが変更されました。fd
の型がint32
からuintptr
に変更されています。内部では、runtime.pwrite
に渡す際にint32
にキャストし直しています。これは、Plan 9ではファイルディスクリプタが依然としてint32
として扱われるため、上位層からのuintptr
を受け入れつつ、下位層のAPIに合わせるための変更です。
src/pkg/runtime/os_solaris.c
--- a/src/pkg/runtime/os_solaris.c
+++ b/src/pkg/runtime/os_solaris.c
@@ -570,7 +570,7 @@ runtime·usleep(uint32 us)\n }\n \n int32\n-runtime·write(int32 fd, void* buf, int32 nbyte)\n+runtime·write(uintptr fd, void* buf, int32 nbyte)\n {\n \treturn runtime·sysvicall6(libc·write, 3, (uintptr)fd, (uintptr)buf, (uintptr)nbyte);\n }\
Solaris OS向けのruntime.write
関数のシグネチャも同様に、fd
の型がint32
からuintptr
に変更されました。libc·write
システムコールに渡す際も、fd
をuintptr
として渡しています。これは、Solarisのシステムコールがuintptr
を受け入れるため、直接渡す形になっています。
src/pkg/runtime/os_windows.c
--- a/src/pkg/runtime/os_windows.c
+++ b/src/pkg/runtime/os_windows.c
@@ -166,7 +166,7 @@ runtime·exit(int32 code)\n }\n \n int32\n-runtime·write(int32 fd, void *buf, int32 n)\n+runtime·write(uintptr fd, void *buf, int32 n)\n {\n \tvoid *handle;\n \tuint32 written;\
@@ -180,7 +180,9 @@ runtime·write(int32 fd, void *buf, int32 n)\n \t\thandle = runtime·stdcall(runtime·GetStdHandle, 1, (uintptr)-12);\n \t\tbreak;\n \tdefault:\n-\t\treturn -1;\n+\t\t// assume fd is real windows handle.\n+\t\thandle = (void*)fd;\n+\t\tbreak;\n \t}\n \truntime·stdcall(runtime·WriteFile, 5, handle, buf, (uintptr)n, &written, (uintptr)0);\n \treturn written;\
このファイルがWindowsでのヒープダンプバグ修正の核心です。
runtime.write
関数のfd
引数がint32
からuintptr
に変更されました。- 重要な変更は
default:
ケースです。以前は、fd
が標準ハンドル(STD_INPUT_HANDLE
など)でない場合、return -1;
として処理を中断していました。 - 修正後は、
default:
ケースで// assume fd is real windows handle.
というコメントが追加され、渡されたfd
が既に有効なWindowsファイルハンドルであると仮定し、handle = (void*)fd;
として直接handle
変数に代入しています。これにより、debug.WriteHeapDump
から渡される実際のファイルハンドルが正しくWriteFile
システムコールに渡されるようになり、ヒープダンプがファイルに書き込まれるようになりました。
src/pkg/runtime/runtime.h
--- a/src/pkg/runtime/runtime.h
+++ b/src/pkg/runtime/runtime.h
@@ -838,7 +838,7 @@ int32 runtime·gotraceback(bool *crash);\n void runtime·goroutineheader(G*);\n int32 runtime·open(int8*, int32, int32);\n int32 runtime·read(int32, void*, int32);\n-int32 runtime·write(int32, void*, int32);\n+int32 runtime·write(uintptr, void*, int32); // use uintptr to accommodate windows.\n int32 runtime·close(int32);\n int32 runtime·mincore(void*, uintptr, byte*);\n void runtime·jmpdefer(FuncVal*, void*);\
GoランタイムのCヘッダーファイルで、runtime.write
関数のプロトタイプ宣言が更新されました。fd
引数の型がint32
からuintptr
に変更され、// use uintptr to accommodate windows.
というコメントが追加されています。これは、Windowsのファイルハンドルを適切に扱うためにuintptr
を使用するという意図を明確に示しています。
関連リンク
- Go Issue #8119: https://github.com/golang/go/issues/8119
- Go CL 93640044: https://golang.org/cl/93640044
参考にした情報源リンク
- Go言語のソースコード (上記コミットの差分)
- Go Issue #8119 の議論
- Windows API ドキュメント (GetStdHandle, WriteFile, HANDLE型に関する一般的な知識)
- Unix系OSのファイルディスクリプタに関する一般的な知識
- Go言語の
uintptr
型に関する一般的な知識 - Go言語の
runtime
パッケージに関する一般的な知識 - Go言語の
runtime/debug
パッケージに関する一般的な知識 - Go言語のクロスコンパイルとOS固有のコードに関する一般的な知識