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

[インデックス 18639] ファイルの概要

このコミットは、Go言語のsyscallパッケージにMmapおよびMunmapシステムコールをSyscall9経由で呼び出すテストを追加するものです。このテストは、アセンブリフラグメントの破損を検出するための「カナリア」として機能することを目的としています。

コミット

  • コミットハッシュ: b05f3de56f018370ce347c2e565ce16cd724f7c3
  • Author: Mikio Hara mikioh.mikioh@gmail.com
  • Date: Tue Feb 25 23:02:19 2014 +0900

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/b05f3de56f018370ce347c2e565ce16cd724f7c3

元コミット内容

syscall: add mmap test

This CL adds a test that calls Mmap and Munmap through Syscall9
as the canary that detects assembly fragment breakage. For now
there is no package test that uses Syscall9 in the standard
library across all Unix-like systems.

Note that the package runtime owns its assembly fragments, so
this canary never works for runtime breakage.

LGTM=iant, bradfitz
R=iant, minux.ma, bradfitz
CC=golang-codereviews
https://golang.org/cl/61520049

変更の背景

この変更の主な背景は、Go言語の標準ライブラリにおいて、Syscall9という低レベルなシステムコール呼び出しメカニズムを使用するテストが不足していた点にあります。特に、Unix系システム全体でSyscall9を使用するパッケージテストが存在しない状況でした。

Go言語の内部では、特定の最適化やプラットフォーム固有の処理のためにアセンブリコードが使用されることがあります。これらのアセンブリコードは「アセンブリフラグメント」と呼ばれ、Goのコンパイラやリンカによって生成されるバイナリの一部となります。アセンブリフラグメントが破損すると、プログラムの予期せぬ動作やクラッシュにつながる可能性があります。

このコミットは、Syscall9を介してMmapMunmapというメモリ管理に関連するシステムコールを呼び出すテストを追加することで、このようなアセンブリフラグメントの破損を早期に検出するための「カナリア」として機能させることを目的としています。カナリアとは、システムが正常に機能しているかを確認するためのシンプルなテストや指標を指します。

ただし、コミットメッセージにもあるように、runtimeパッケージが自身のアセンブリフラグメントを管理しているため、このテストはruntimeパッケージ内部の破損を検出するようには設計されていません。これは、runtimeパッケージがGoプログラムの実行環境そのものを担当しており、その低レベルな性質上、通常のsyscallパッケージのテストとは異なるメカニズムが必要となるためです。

前提知識の解説

このコミットを理解するためには、以下の概念について理解しておく必要があります。

  1. syscallパッケージ: Go言語のsyscallパッケージは、オペレーティングシステム(OS)の低レベルなプリミティブへのインターフェースを提供します。これにより、GoプログラムはファイルI/O、プロセス管理、ネットワーク通信などの特権的な操作をOSカーネルに直接要求するシステムコールを呼び出すことができます。これは、Goの標準ライブラリが提供する高レベルなAPI(例: osパッケージのファイル操作)の基盤となっています。

  2. システムコール: プログラムがOSカーネルに特定のサービスを要求するためのメカニズムです。例えば、ファイルの読み書き、メモリの確保、プロセスの作成などがシステムコールを通じて行われます。システムコールはOSとアプリケーションの間の重要な境界であり、セキュリティと安定性を確保するために厳密に管理されています。

  3. MmapMunmap:

    • Mmap (Memory Map): ファイルやデバイスをプロセスのアドレス空間にマッピング(割り当て)するためのシステムコールです。これにより、ファイルの内容をメモリのように直接アクセスできるようになります。また、匿名メモリ領域(ファイルに関連付けられていないメモリ)を確保するためにも使用されます。
    • Munmap (Memory Unmap): Mmapによってマッピングされたメモリ領域を解放するためのシステムコールです。
  4. Syscall9: syscallパッケージ内で提供される関数の一つで、最大9つの引数を取るシステムコールを呼び出すために使用されます。そのシグネチャはfunc Syscall9(num, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno)です。

    • num: システムコール番号。OSとアーキテクチャに固有の値です。
    • a1a9: システムコールに渡される引数。通常はuintptr型で、メモリアドレスや整数値を表します。
    • r1, r2: システムコールからの戻り値。
    • err: エラーを示すErrno型。 Syscall9のような低レベルな関数は、OSの内部動作に深く関わるため、プラットフォーム間で動作が大きく異なる可能性があります。Go 1.4以降、syscallパッケージは「ロックダウン」され、新しいコードではgolang.org/x/sysパッケージの使用が推奨されています。また、Go 1.18では可変長引数を取るSyscallNが追加され、Syscall9は非推奨となっています。
  5. アセンブリフラグメント: Go言語のコンパイラは、特定の最適化やOSとのインターフェースのために、Goコードの一部を直接アセンブリコードに変換したり、既存のアセンブリコードを組み込んだりすることがあります。これらのアセンブリコードの断片を「アセンブリフラグメント」と呼びます。これらはGoのランタイムや標準ライブラリの低レベルな部分で特に重要です。

  6. カナリアテスト (Canary Test): 鉱山で有毒ガスを検出するためにカナリアを使用していたことに由来する用語です。ソフトウェア開発においては、システム全体の健全性を監視するために、特定の脆弱な部分や重要な機能に対して行われる小規模で独立したテストを指します。このテストが失敗した場合、より広範な問題が発生している可能性を示唆します。

技術的詳細

このコミットで追加されたテストは、syscallパッケージのTestMmap関数です。この関数は、syscall.Mmapsyscall.Munmapを呼び出すことで、メモリマッピングとアンマッピングの基本的な機能が正しく動作するかを確認します。

特筆すべきは、このテストがSyscall9を直接使用しているわけではない点です。コミットメッセージの「calls Mmap and Munmap through Syscall9」という記述は、syscall.Mmapsyscall.Munmapといった高レベルなsyscallパッケージの関数が、内部的にSyscall9(またはそれに類する低レベルなシステムコールラッパー)を呼び出していることを示唆しています。つまり、このテストはsyscallパッケージの公開APIを通じてMmapMunmapをテストしていますが、その裏でSyscall9が使われているため、Syscall9が関与するアセンブリフラグメントの健全性を間接的に検証している、という意図です。

テストの具体的な内容は以下の通りです。

  1. syscall.Mmap(-1, 0, syscall.Getpagesize(), syscall.PROT_NONE, syscall.MAP_ANON|syscall.MAP_PRIVATE)を呼び出します。
    • -1: ファイルディスクリプタ。通常、匿名メモリマッピング(ファイルに関連付けないメモリ)を作成する際に使用されます。
    • 0: オフセット。匿名マッピングでは通常0です。
    • syscall.Getpagesize(): OSのメモリページサイズを取得します。このサイズのメモリ領域を確保します。
    • syscall.PROT_NONE: ページへのアクセス権限を設定します。PROT_NONEはアクセスを許可しないことを意味します。これは、単にメモリ領域を確保し、後で解放できることを確認するためのテストであるため、アクセス権限は重要ではありません。
    • syscall.MAP_ANON|syscall.MAP_PRIVATE: マッピングのフラグを設定します。
      • MAP_ANON: ファイルに関連付けられていない匿名メモリ領域を作成します。
      • MAP_PRIVATE: プライベートマッピングを作成します。これにより、このプロセスが行うメモリへの変更は他のプロセスには見えません。
  2. Mmapの呼び出しがエラーを返した場合、テストは失敗します。
  3. Mmapが成功した場合、返されたメモリ領域bsyscall.Munmap(b)で解放します。
  4. Munmapの呼び出しがエラーを返した場合も、テストは失敗します。

このテストは、MmapMunmapという基本的なメモリ管理システムコールが、Syscall9を介した低レベルな呼び出しパスを含めて、正しく機能していることを確認します。これにより、Goのコンパイラやリンカが生成するアセンブリフラグメントに予期せぬ破損がないかを検出する「カナリア」としての役割を果たします。

ただし、コミットメッセージにあるように、「runtimeパッケージが自身のアセンブリフラグメントを所有しているため、このカナリアはランタイムの破損には決して機能しない」という重要な注意点があります。runtimeパッケージはGoプログラムの実行時環境を管理する非常に低レベルな部分であり、ガベージコレクション、スケジューラ、スタック管理など、OSとの密接な連携を必要とする機能を含んでいます。これらの機能は、syscallパッケージとは異なる、より特殊なアセンブリコードやOS固有のインターフェースを使用しているため、syscallパッケージのテストではその健全性を完全に保証することはできません。

コアとなるコードの変更箇所

--- a/src/pkg/syscall/syscall_unix_test.go
+++ b/src/pkg/syscall/syscall_unix_test.go
@@ -77,6 +77,16 @@ func TestFcntlFlock(t *testing.T) {
 	}\n
 }\n
 \n
+func TestMmap(t *testing.T) {
+\tb, err := syscall.Mmap(-1, 0, syscall.Getpagesize(), syscall.PROT_NONE, syscall.MAP_ANON|syscall.MAP_PRIVATE)\n
+\tif err != nil {
+\t\tt.Fatalf(\"Mmap: %v\", err)\n
+\t}\n
+\tif err := syscall.Munmap(b); err != nil {
+\t\tt.Fatalf(\"Munmap: %v\", err)\n
+\t}\n
+}\n
+\n
 // TestPassFD tests passing a file descriptor over a Unix socket.\n
 //\n
 // This test involved both a parent and child process. The parent\n

コアとなるコードの解説

追加されたコードは、src/pkg/syscall/syscall_unix_test.goファイル内のTestMmap関数です。

func TestMmap(t *testing.T) {
	// Mmapを呼び出して、匿名でプライベートなメモリ領域を確保する
	// -1: ファイルディスクリプタ(匿名マッピングのため)
	// 0: オフセット
	// syscall.Getpagesize(): OSのページサイズ分のメモリを確保
	// syscall.PROT_NONE: アクセス権限なし(テスト目的のため)
	// syscall.MAP_ANON|syscall.MAP_PRIVATE: 匿名かつプライベートなマッピング
	b, err := syscall.Mmap(-1, 0, syscall.Getpagesize(), syscall.PROT_NONE, syscall.MAP_ANON|syscall.MAP_PRIVATE)
	if err != nil {
		// Mmapが失敗した場合、テストを失敗させる
		t.Fatalf("Mmap: %v", err)
	}
	// 確保したメモリ領域をMunmapで解放する
	if err := syscall.Munmap(b); err != nil {
		// Munmapが失敗した場合、テストを失敗させる
		t.Fatalf("Munmap: %v", err)
	}
}

このテスト関数は、以下の手順を実行します。

  1. syscall.Mmap関数を呼び出し、OSから匿名でプライベートなメモリページを1ページ分(syscall.Getpagesize()で取得されるサイズ)確保しようとします。この際、アクセス権限はPROT_NONE(アクセス不可)に設定されています。これは、実際にメモリを読み書きするのではなく、単にメモリの確保と解放のメカニズムが機能するかどうかを確認するためです。
  2. Mmapの呼び出しがエラーを返した場合、t.Fatalfを使用してテストを即座に失敗させ、エラーメッセージを出力します。
  3. Mmapが成功し、メモリ領域bが返された場合、次にsyscall.Munmap関数を呼び出して、そのメモリ領域を解放します。
  4. Munmapの呼び出しがエラーを返した場合も、同様にt.Fatalfでテストを失敗させます。

このシンプルなテストは、MmapMunmapという重要なシステムコールが、Goのsyscallパッケージを通じて正しく呼び出され、期待通りに動作することを確認します。特に、これらの関数が内部的に利用する可能性のあるSyscall9のような低レベルなアセンブリフラグメントが、コンパイラやリンカの変更によって破損していないかを検出する役割を担っています。

関連リンク

参考にした情報源リンク