Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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. int32uintptr

  • int32: 32ビット符号付き整数型です。Unix系のファイルディスクリプタは通常この型で十分表現できます。
  • uintptr: ポインタを保持できる符号なし整数型です。そのサイズは、実行環境のポインタサイズ(32ビットシステムでは32ビット、64ビットシステムでは64ビット)に依存します。WindowsのHANDLE型はポインタであるため、uintptrHANDLEの値を安全に保持できる汎用的な型として適しています。この型を使用することで、異なる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.writereturn -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.hruntime.writeのプロトタイプ宣言が更新され、uintptr型を使用することが明示されました。コメントには「// use uintptr to accommodate windows.」と追記され、Windowsへの対応が目的であることが示されています。
    • 新しいテストケース TestWriteHeapDumpNonemptysrc/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

--- /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 を追加しています。

  1. ioutil.TempFile を使用して一時ファイルを作成します。
  2. defer ステートメントで、テスト終了後に一時ファイルを削除し、ファイルをクローズするように設定します。
  3. WriteHeapDump(f.Fd()) を呼び出し、作成した一時ファイルのファイルディスクリプタ(Windowsではハンドル)にヒープダンプを書き込みます。
  4. f.Stat() でファイル情報を取得し、fi.Size() でファイルサイズを確認します。
  5. ファイルサイズが 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システムコールに渡す際も、fduintptrとして渡しています。これは、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でのヒープダンプバグ修正の核心です。

  1. runtime.write関数のfd引数がint32からuintptrに変更されました。
  2. 重要な変更はdefault:ケースです。以前は、fdが標準ハンドル(STD_INPUT_HANDLEなど)でない場合、return -1;として処理を中断していました。
  3. 修正後は、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言語のソースコード (上記コミットの差分)
  • 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 を追加しています。

  1. ioutil.TempFile を使用して一時ファイルを作成します。
  2. defer ステートメントで、テスト終了後に一時ファイルを削除し、ファイルをクローズするように設定します。
  3. WriteHeapDump(f.Fd()) を呼び出し、作成した一時ファイルのファイルディスクリプタ(Windowsではハンドル)にヒープダンプを書き込みます。
  4. f.Stat() でファイル情報を取得し、fi.Size() でファイルサイズを確認します。
  5. ファイルサイズが 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システムコールに渡す際も、fduintptrとして渡しています。これは、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でのヒープダンプバグ修正の核心です。

  1. runtime.write関数のfd引数がint32からuintptrに変更されました。
  2. 重要な変更はdefault:ケースです。以前は、fdが標準ハンドル(STD_INPUT_HANDLEなど)でない場合、return -1;として処理を中断していました。
  3. 修正後は、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言語のソースコード (上記コミットの差分)
  • Go Issue #8119 の議論
  • Windows API ドキュメント (GetStdHandle, WriteFile, HANDLE型に関する一般的な知識)
  • Unix系OSのファイルディスクリプタに関する一般的な知識
  • Go言語のuintptr型に関する一般的な知識
  • Go言語のruntimeパッケージに関する一般的な知識
  • Go言語のruntime/debugパッケージに関する一般的な知識
  • Go言語のクロスコンパイルとOS固有のコードに関する一般的な知識