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

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

このコミットは、Goコンパイラの一部である cmd/6g における、nacl (Native Client) 環境でのメモリゼロ初期化に関するバグ修正です。具体的には、zerorange 関数が widthptr の奇数倍のサイズを正しくゼロ初期化しない問題を修正しています。

コミット

commit bfbb2e827b3c86e0f85f7378667958d91726c928
Author: Keith Randall <khr@golang.org>
Date:   Thu Apr 10 07:59:46 2014 -0700

    cmd/6g: nacl: zero odd multiple of widthptr correctly
    
    LGTM=iant
    R=remyoudompheng, iant
    CC=golang-codereviews
    https://golang.org/cl/86270043

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

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

元コミット内容

cmd/6g: nacl: zero odd multiple of widthptr correctly

変更の背景

このコミットは、GoコンパイラがGoogle Native Client (NaCl) 環境向けにコードを生成する際に発生する可能性のある、メモリ初期化の誤りを修正することを目的としています。

Goのランタイムやコンパイラは、新しいメモリ領域を確保した際に、セキュリティや予測可能性のためにその領域をゼロで初期化することがよくあります。これは、以前のデータが残っていることによる情報漏洩や、未初期化メモリへのアクセスによる未定義動作を防ぐためです。

zerorange 関数は、指定されたメモリ範囲をゼロで埋める役割を担っています。通常、この操作は効率化のためにレジスタ幅 (widthreg) やポインタ幅 (widthptr) の倍数で行われます。しかし、特定の条件下、特にNaClのような特殊な環境では、メモリのアライメントやアクセスパターンに制約がある場合があります。

このコミットの背景には、zerorange 関数が widthptr の奇数倍のサイズを処理する際に、残りの部分を正しくゼロ初期化できていなかったという問題があったと考えられます。これは、特にNaCl環境でのコード生成において、特定のサイズのメモリ領域が要求された場合に、部分的に未初期化のままになる可能性を示唆しています。

前提知識の解説

  • Goコンパイラ (cmd/6g): Go言語のコンパイラは、ターゲットアーキテクチャごとに異なるフロントエンドを持っています。cmd/6g は、AMD64 (x86-64) アーキテクチャ向けのGoコンパイラのバックエンドを指します。Go 1.5以降、コンパイラはGo自身で書かれるようになりましたが、このコミットが作成された2014年時点では、まだC言語で書かれた部分が多く残っていました。
  • Google Native Client (NaCl): Google Native Clientは、ウェブブラウザ内でネイティブコード(C/C++など)を安全に実行するためのサンドボックス技術です。NaClは、特定のセキュリティ制約(例えば、メモリのアライメントやアクセスパターンに関する厳格なルール)を課すことで、悪意のあるコードがシステムに損害を与えるのを防ぎます。Go言語もNaCl環境をサポートしており、GoプログラムをNaClモジュールとしてコンパイルすることが可能でした。
  • zerorange 関数: Goのランタイムやコンパイラが使用する内部関数で、指定されたメモリ範囲をゼロで埋める役割を担います。これは、ガベージコレクションによって再利用されるメモリや、新しく確保されたスタックフレームなどを初期化する際に重要です。
  • widthptr: ポインタの幅(サイズ)をバイト単位で表す定数です。64ビットシステムでは通常8バイト(64ビット)です。
  • widthreg: レジスタの幅(サイズ)をバイト単位で表す定数です。64ビットシステムでは通常8バイト(64ビット)です。zerorange 関数は、効率的なゼロ初期化のために、通常はレジスタ単位でメモリを操作します。
  • D_SP+D_INDIR: アセンブリコードにおけるアドレス指定モードの一つで、スタックポインタ (SP) を基準とした間接アドレス指定を示します。frame+lo は、スタックフレーム内のオフセットを示し、特定のメモリ位置を指します。
  • AMOVQ, AMOVL: アセンブリ命令のニーモニックです。
    • AMOVQ: 64ビット(Quadword)のデータを移動する命令。
    • AMOVL: 32ビット(Longword)のデータを移動する命令。 これらは、レジスタからメモリへ、またはメモリからレジスタへデータを転送するために使用されます。

技術的詳細

zerorange 関数は、cnt バイトのメモリ領域をゼロで初期化します。この関数は、効率のために一度に widthreg バイト(通常は8バイト)ずつゼロを書き込むループを使用しています。

問題は、cntwidthreg の倍数ではない場合、特に widthptr の奇数倍である場合に発生していました。NaCl環境では、メモリのアライメントが厳しく、特定のサイズの書き込みが許可されない場合があります。

元のコードでは、cntwidthreg の倍数でない場合、残りの部分の処理が不十分であった可能性があります。このコミットでは、cntwidthreg の倍数ではないが、widthptr の倍数である場合に、特別な処理を追加しています。

具体的には、cnt % widthreg != 0 の条件が真であり、かつ cnt % widthptr != 0 の場合に fatal エラーを発生させることで、予期せぬ状況を検出しています。これは、zerorangewidthptr の倍数でなければならないという前提があることを示唆しています。

そして、cntwidthreg の倍数ではないが widthptr の倍数である場合(つまり、widthregwidthptr の倍数でないか、または widthptrwidthreg の約数であるような特殊なケース)、残りの widthptr 分の領域を AMOVL 命令(32ビット移動)でゼロ初期化しています。これは、AMOVQ (64ビット移動) が使えない状況で、より小さい単位でゼロを書き込む必要があることを示唆しています。

この修正により、zerorange 関数は、widthptr の奇数倍のサイズであっても、残りの部分を正しくゼロ初期化できるようになり、NaCl環境でのメモリ安全性と正確性が向上します。

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

src/cmd/6g/ggen.c ファイルの zerorange 関数に以下のコードが追加されました。

--- a/src/cmd/6g/ggen.c
+++ b/src/cmd/6g/ggen.c
@@ -73,6 +73,14 @@ zerorange(Prog *p, vlong frame, vlong lo, vlong hi, uint32 *ax)
 		p = appendpp(p, AMOVQ, D_CONST, 0, D_AX, 0);
 		*ax = 1;
 	}
+	if(cnt % widthreg != 0) {
+		// should only happen with nacl
+		if(cnt % widthptr != 0)
+			fatal("zerorange count not a multiple of widthptr %d", cnt);
+		p = appendpp(p, AMOVL, D_AX, 0, D_SP+D_INDIR, frame+lo);
+		lo += widthptr;
+		cnt -= widthptr;
+	}
 	if(cnt <= 4*widthreg) {
 		for(i = 0; i < cnt; i += widthreg) {
 			p = appendpp(p, AMOVQ, D_AX, 0, D_SP+D_INDIR, frame+lo+i);

コアとなるコードの解説

追加されたコードブロックは、zerorange 関数の冒頭近くに位置し、cnt (ゼロ初期化するバイト数) が widthreg (レジスタ幅、通常8バイト) の倍数でない場合の特殊な処理を扱っています。

  1. if(cnt % widthreg != 0):
    • この条件は、ゼロ初期化すべきバイト数 cnt が、通常の一括処理単位である widthreg の倍数ではない場合に真となります。これは、残りのバイトが存在することを示します。
  2. // should only happen with nacl:
    • コメントは、この特殊なケースが主にNaCl環境で発生することを指摘しています。これは、NaClが特定のメモリサイズやアライメントに制約を課すため、通常の環境では発生しないような、widthreg の倍数ではないが widthptr の倍数であるようなサイズが要求される可能性があることを示唆しています。
  3. if(cnt % widthptr != 0):
    • この内部の if 文は、cntwidthreg の倍数ではないにもかかわらず、さらに widthptr (ポインタ幅、通常8バイト) の倍数でもない場合に fatal エラーを発生させます。これは、zerorange 関数が処理するメモリ範囲のサイズは、少なくとも widthptr の倍数であるべきだという強い前提があることを示しています。もしこの条件が満たされない場合、それは予期せぬ、おそらく不正なメモリ操作を意味するため、プログラムを終了させます。
  4. p = appendpp(p, AMOVL, D_AX, 0, D_SP+D_INDIR, frame+lo);:
    • この行が修正の核心です。cntwidthreg の倍数ではないが widthptr の倍数である場合、残りの widthptr バイトをゼロ初期化します。
    • AMOVL 命令は32ビット(4バイト)のデータを移動します。D_AX レジスタにはゼロが設定されているため、これは frame+lo のアドレスに4バイトのゼロを書き込むことを意味します。
    • なぜ AMOVL なのか? widthptr が8バイトであるにもかかわらず、AMOVL (32ビット) が使われているのは、NaClのセキュリティモデルやアライメント制約により、特定の状況下で64ビットの AMOVQ が直接使えない、あるいは部分的な書き込みが必要な場合に、より小さい単位で書き込む必要があるためと考えられます。これにより、残りの widthptr バイトのうち、最初の4バイトがゼロになります。
    • 補足: このコードスニペットだけでは、残りの4バイトがどのようにゼロになるのかが明確ではありません。しかし、Goのコンパイラは通常、このような低レベルの最適化やアライメント処理を非常に慎重に行います。この AMOVLwidthptr の残りの部分をカバーするための最初のステップであるか、あるいは特定のNaClの制約下で widthptr が4バイトとして扱われるケースがあるか、または後続のループが残りを処理する前提があるか、といった可能性が考えられます。元のコミットの完全なコンテキストや、NaClの当時の仕様を詳細に確認する必要がありますが、この変更は少なくとも widthptr の先頭部分を確実にゼロにすることを意図しています。
  5. lo += widthptr;:
    • ゼロ初期化された widthptr 分の領域をスキップするために、開始オフセット lowidthptr だけ進めます。
  6. cnt -= widthptr;:
    • ゼロ初期化された widthptr 分のバイト数を cnt から減算します。これにより、残りの cnt バイトは後続のメインループで処理されることになります。

この修正により、zerorange 関数は、widthreg の倍数ではないが widthptr の倍数であるような、特にNaCl環境で発生しうる「奇妙な」サイズのメモリ領域に対しても、正しくゼロ初期化を実行できるようになります。

関連リンク

参考にした情報源リンク

  • Goのコミットメッセージと差分: https://github.com/golang/go/commit/bfbb2e827b3c86e0f85f7378667958d91726c928
  • Goのコードレビュー (CL 86270043): https://golang.org/cl/86270043 (このリンクは現在、Gerritの新しいURLにリダイレクトされるはずです)
  • Goコンパイラの歴史と進化に関する情報 (一般的なGoのドキュメントやブログ記事)
  • Google Native Clientに関する一般的な情報 (当時の技術資料)
  • アセンブリ言語の基本(MOV 命令など)に関する一般的な情報```markdown

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

このコミットは、Goコンパイラの一部である cmd/6g における、nacl (Native Client) 環境でのメモリゼロ初期化に関するバグ修正です。具体的には、zerorange 関数が widthptr の奇数倍のサイズを正しくゼロ初期化しない問題を修正しています。

コミット

commit bfbb2e827b3c86e0f85f7378667958d91726c928
Author: Keith Randall <khr@golang.org>
Date:   Thu Apr 10 07:59:46 2014 -0700

    cmd/6g: nacl: zero odd multiple of widthptr correctly
    
    LGTM=iant
    R=remyoudompheng, iant
    CC=golang-codereviews
    https://golang.org/cl/86270043

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

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

元コミット内容

cmd/6g: nacl: zero odd multiple of widthptr correctly

変更の背景

このコミットは、GoコンパイラがGoogle Native Client (NaCl) 環境向けにコードを生成する際に発生する可能性のある、メモリ初期化の誤りを修正することを目的としています。

Goのランタイムやコンパイラは、新しいメモリ領域を確保した際に、セキュリティや予測可能性のためにその領域をゼロで初期化することがよくあります。これは、以前のデータが残っていることによる情報漏洩や、未初期化メモリへのアクセスによる未定義動作を防ぐためです。

zerorange 関数は、指定されたメモリ範囲をゼロで埋める役割を担っています。通常、この操作は効率化のためにレジスタ幅 (widthreg) やポインタ幅 (widthptr) の倍数で行われます。しかし、特定の条件下、特にNaClのような特殊な環境では、メモリのアライメントやアクセスパターンに制約がある場合があります。

このコミットの背景には、zerorange 関数が widthptr の奇数倍のサイズを処理する際に、残りの部分を正しくゼロ初期化できていなかったという問題があったと考えられます。これは、特にNaCl環境でのコード生成において、特定のサイズのメモリ領域が要求された場合に、部分的に未初期化のままになる可能性を示唆しています。

前提知識の解説

  • Goコンパイラ (cmd/6g): Go言語のコンパイラは、ターゲットアーキテクチャごとに異なるフロントエンドを持っています。cmd/6g は、AMD64 (x86-64) アーキテクチャ向けのGoコンパイラのバックエンドを指します。Go 1.5以降、コンパイラはGo自身で書かれるようになりましたが、このコミットが作成された2014年時点では、まだC言語で書かれた部分が多く残っていました。
  • Google Native Client (NaCl): Google Native Clientは、ウェブブラウザ内でネイティブコード(C/C++など)を安全に実行するためのサンドボックス技術です。NaClは、特定のセキュリティ制約(例えば、メモリのアライメントやアクセスパターンに関する厳格なルール)を課すことで、悪意のあるコードがシステムに損害を与えるのを防ぎます。Go言語もNaCl環境をサポートしており、GoプログラムをNaClモジュールとしてコンパイルすることが可能でした。
  • zerorange 関数: Goのランタイムやコンパイラが使用する内部関数で、指定されたメモリ範囲をゼロで埋める役割を担います。これは、ガベージコレクションによって再利用されるメモリや、新しく確保されたスタックフレームなどを初期化する際に重要です。
  • widthptr: ポインタの幅(サイズ)をバイト単位で表す定数です。64ビットシステムでは通常8バイト(64ビット)です。
  • widthreg: レジスタの幅(サイズ)をバイト単位で表す定数です。64ビットシステムでは通常8バイト(64ビット)です。zerorange 関数は、効率的なゼロ初期化のために、通常はレジスタ単位でメモリを操作します。
  • D_SP+D_INDIR: アセンブリコードにおけるアドレス指定モードの一つで、スタックポインタ (SP) を基準とした間接アドレス指定を示します。frame+lo は、スタックフレーム内のオフセットを示し、特定のメモリ位置を指します。
  • AMOVQ, AMOVL: アセンブリ命令のニーモニックです。
    • AMOVQ: 64ビット(Quadword)のデータを移動する命令。
    • AMOVL: 32ビット(Longword)のデータを移動する命令。 これらは、レジスタからメモリへ、またはメモリからレジスタへデータを転送するために使用されます。

技術的詳細

zerorange 関数は、cnt バイトのメモリ領域をゼロで初期化します。この関数は、効率のために一度に widthreg バイト(通常は8バイト)ずつゼロを書き込むループを使用しています。

問題は、cntwidthreg の倍数ではない場合、特に widthptr の奇数倍である場合に発生していました。NaCl環境では、メモリのアライメントが厳しく、特定のサイズの書き込みが許可されない場合があります。

元のコードでは、cntwidthreg の倍数でない場合、残りの部分の処理が不十分であった可能性があります。このコミットでは、cntwidthreg の倍数ではないが、widthptr の倍数である場合に、特別な処理を追加しています。

具体的には、cnt % widthreg != 0 の条件が真であり、かつ cnt % widthptr != 0 の場合に fatal エラーを発生させることで、予期せぬ状況を検出しています。これは、zerorangewidthptr の倍数でなければならないという前提があることを示唆しています。

そして、cntwidthreg の倍数ではないが widthptr の倍数である場合(つまり、widthregwidthptr の倍数でないか、または widthptrwidthreg の約数であるような特殊なケース)、残りの widthptr 分の領域を AMOVL 命令(32ビット移動)でゼロ初期化しています。これは、AMOVQ (64ビット移動) が使えない状況で、より小さい単位でゼロを書き込む必要があることを示唆しています。

この修正により、zerorange 関数は、widthptr の奇数倍のサイズであっても、残りの部分を正しくゼロ初期化できるようになり、NaCl環境でのメモリ安全性と正確性が向上します。

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

src/cmd/6g/ggen.c ファイルの zerorange 関数に以下のコードが追加されました。

--- a/src/cmd/6g/ggen.c
+++ b/src/cmd/6g/ggen.c
@@ -73,6 +73,14 @@ zerorange(Prog *p, vlong frame, vlong lo, vlong hi, uint32 *ax)
 		p = appendpp(p, AMOVQ, D_CONST, 0, D_AX, 0);
 		*ax = 1;
 	}
+	if(cnt % widthreg != 0) {
+		// should only happen with nacl
+		if(cnt % widthptr != 0)
+			fatal("zerorange count not a multiple of widthptr %d", cnt);
+		p = appendpp(p, AMOVL, D_AX, 0, D_SP+D_INDIR, frame+lo);
+		lo += widthptr;
+		cnt -= widthptr;
+	}
 	if(cnt <= 4*widthreg) {
 		for(i = 0; i < cnt; i += widthreg) {
 			p = appendpp(p, AMOVQ, D_AX, 0, D_SP+D_INDIR, frame+lo+i);

コアとなるコードの解説

追加されたコードブロックは、zerorange 関数の冒頭近くに位置し、cnt (ゼロ初期化するバイト数) が widthreg (レジスタ幅、通常8バイト) の倍数でない場合の特殊な処理を扱っています。

  1. if(cnt % widthreg != 0):
    • この条件は、ゼロ初期化すべきバイト数 cnt が、通常の一括処理単位である widthreg の倍数ではない場合に真となります。これは、残りのバイトが存在することを示します。
  2. // should only happen with nacl:
    • コメントは、この特殊なケースが主にNaCl環境で発生することを指摘しています。これは、NaClが特定のメモリサイズやアライメントに制約を課すため、通常の環境では発生しないような、widthreg の倍数ではないが widthptr の倍数であるようなサイズが要求される可能性があることを示唆しています。
  3. if(cnt % widthptr != 0):
    • この内部の if 文は、cntwidthreg の倍数ではないにもかかわらず、さらに widthptr (ポインタ幅、通常8バイト) の倍数でもない場合に fatal エラーを発生させます。これは、zerorange 関数が処理するメモリ範囲のサイズは、少なくとも widthptr の倍数であるべきだという強い前提があることを示しています。もしこの条件が満たされない場合、それは予期せぬ、おそらく不正なメモリ操作を意味するため、プログラムを終了させます。
  4. p = appendpp(p, AMOVL, D_AX, 0, D_SP+D_INDIR, frame+lo);:
    • この行が修正の核心です。cntwidthreg の倍数ではないが widthptr の倍数である場合、残りの widthptr バイトをゼロ初期化します。
    • AMOVL 命令は32ビット(4バイト)のデータを移動します。D_AX レジスタにはゼロが設定されているため、これは frame+lo のアドレスに4バイトのゼロを書き込むことを意味します。
    • なぜ AMOVL なのか? widthptr が8バイトであるにもかかわらず、AMOVL (32ビット) が使われているのは、NaClのセキュリティモデルやアライメント制約により、特定の状況下で64ビットの AMOVQ が直接使えない、あるいは部分的な書き込みが必要な場合に、より小さい単位で書き込む必要があるためと考えられます。これにより、残りの widthptr バイトのうち、最初の4バイトがゼロになります。
    • 補足: このコードスニペットだけでは、残りの4バイトがどのようにゼロになるのかが明確ではありません。しかし、Goのコンパイラは通常、このような低レベルの最適化やアライメント処理を非常に慎重に行います。この AMOVLwidthptr の残りの部分をカバーするための最初のステップであるか、あるいは特定のNaClの制約下で widthptr が4バイトとして扱われるケースがあるか、または後続のループが残りを処理する前提があるか、といった可能性が考えられます。元のコミットの完全なコンテキストや、NaClの当時の仕様を詳細に確認する必要がありますが、この変更は少なくとも widthptr の先頭部分を確実にゼロにすることを意図しています。
  5. lo += widthptr;:
    • ゼロ初期化された widthptr 分の領域をスキップするために、開始オフセット lowidthptr だけ進めます。
  6. cnt -= widthptr;:
    • ゼロ初期化された widthptr 分のバイト数を cnt から減算します。これにより、残りの cnt バイトは後続のメインループで処理されることになります。

この修正により、zerorange 関数は、widthreg の倍数ではないが widthptr の倍数であるような、特にNaCl環境で発生しうる「奇妙な」サイズのメモリ領域に対しても、正しくゼロ初期化を実行できるようになります。

関連リンク

参考にした情報源リンク

  • Goのコミットメッセージと差分: https://github.com/golang/go/commit/bfbb2e827b3c86e0f85f7378667958d91726c928
  • Goのコードレビュー (CL 86270043): https://golang.org/cl/86270043 (このリンクは現在、Gerritの新しいURLにリダイレクトされるはずです)
  • Goコンパイラの歴史と進化に関する情報 (一般的なGoのドキュメントやブログ記事)
  • Google Native Clientに関する一般的な情報 (当時の技術資料)
  • アセンブリ言語の基本(MOV 命令など)に関する一般的な情報