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

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

このコミットは、Go言語のリンカである cmd/6l (32-bit x86アーキテクチャ向け) と cmd/8l (64-bit x86アーキテクチャ向け) における MOVL および MOVQ 命令のオペコードテーブル (optab) の定義に関するバグ修正です。具体的には、これらの命令のエンコーディング情報が不正確であったために発生していた、配列の範囲外アクセス(Clangによって発見)を修正し、より堅牢なコード生成を実現しています。

コミット

commit 80a153dd5189d8bbc9d090f0ef0691c224d8b0a1
Author: Russ Cox <rsc@golang.org>
Date:   Tue Sep 10 14:53:41 2013 -0400

    cmd/6l, cmd/8l: fix MOVL MOVQ optab
    
    The entry for LEAL/LEAQ in these optabs was listed as having
    two data bytes in the y array. In fact they had and expect no data
    bytes. However, the general loop expects to be able to look at at
    least one data byte, to make sure it is not 0x0f. So give them each
    a single data byte set to 0 (not 0x0f).
    
    Since the MOV instructions have the largest optab cases, this
    requires growing the size of the data array.
    
    Clang found this bug because the general o->op[z] == 0x0f
    test was using z == 22, which was out of bounds.
    
    In practice the next byte in memory was probably not 0x0f
    so it wasn't truly broken. But might as well be clean.
    
    Update #5764
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/13241050

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

https://github.com/golang/go/commit/80a153dd5189d8bbc9d090f0ef0691c224d8b0a1

元コミット内容

cmd/6l, cmd/8l: fix MOVL MOVQ optab

このコミットは、Go言語のリンカである cmd/6l および cmd/8l における MOVL および MOVQ 命令のオペコードテーブル(optab)の定義に関する修正です。

LEAL/LEAQ (Load Effective Address) 命令のエントリが、y 配列に2つのデータバイトを持つと誤って記述されていました。しかし実際には、これらの命令はデータバイトを必要としません。一方で、一般的なループ処理では、少なくとも1つのデータバイトを検査し、それが 0x0f でないことを確認する必要があります。このため、これらのエントリに 0 (ただし 0x0f ではない) に設定された単一のデータバイトを与えるように変更されました。

MOV 命令は optab の中で最も多くのケースを持つため、この変更によりデータ配列のサイズを増やす必要が生じました。

このバグは、Clangコンパイラによって発見されました。Clangは、一般的な o->op[z] == 0x0f というテストにおいて、z == 22 というインデックスが配列の範囲外アクセスを引き起こしていることを検出しました。

実際には、メモリ上の次のバイトが 0x0f でないことが多かったため、このバグが直接的な問題を引き起こすことは稀でした。しかし、コードの健全性を保つために修正されました。

このコミットは、Issue #5764 を更新します。

変更の背景

この変更の背景には、Go言語のリンカがアセンブリ命令を機械語に変換する際の内部的な処理の正確性の問題がありました。特に、LEAL (Load Effective Address Long) や LEAQ (Load Effective Address Quad) といった命令のオペコードテーブル (optab) の定義に誤りがありました。

Goのリンカは、Goプログラムをコンパイルする際に生成される中間表現(アセンブリコードに似たもの)を、最終的な実行可能バイナリに変換する役割を担っています。この変換プロセスでは、各アセンブリ命令に対応する機械語のバイト列を生成するために、オペコードテーブルを参照します。

問題は、LEAL/LEAQ 命令が実際にはデータバイトを必要としないにもかかわらず、optab の定義上は2つのデータバイトを持つと誤って記述されていた点にありました。これにより、リンカの内部処理で、存在しないデータバイトを読み込もうとする可能性が生じていました。

さらに、この問題は、リンカの汎用的なループ処理が、命令のオペコードバイトを検査する際に、特定のバイト(0x0f)を避けるためのチェックを行っていたことと関連しています。このチェックは、命令のエンコーディングが特定のパターン(例えば、拡張オペコードを示すプレフィックス)に合致するかどうかを判断するために行われます。しかし、誤った optab 定義のために、このチェックが配列の範囲外のメモリ領域を参照してしまう可能性がありました。

このバグは、Clangコンパイラによって発見されました。Clangのような高度な静的解析ツールは、コンパイル時にコードの潜在的な問題を検出するのに役立ちます。Clangがこの範囲外アクセスを検出したことで、Goリンカの堅牢性を向上させるための修正が必要であることが明らかになりました。

実運用上、このバグが深刻な問題を引き起こすことは稀だったとされています。これは、偶然にも範囲外アクセスで参照されるメモリ上のバイトが 0x0f でないことが多かったためです。しかし、このような未定義の動作は、将来的に予期せぬバグやセキュリティ脆弱性につながる可能性があるため、修正は不可欠でした。

この修正は、Goリンカのコード生成の正確性と信頼性を向上させ、将来的な開発における潜在的な問題を未然に防ぐことを目的としています。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

1. Go言語のコンパイルとリンカ

Go言語のプログラムは、go build コマンドによってコンパイルされます。このプロセスは、ソースコードを機械語に変換し、実行可能なバイナリを生成します。Goのツールチェインは、独自のコンパイラとリンカを使用しています。

  • コンパイラ: Goのソースコード (.go ファイル) を、特定のアーキテクチャ(例: x86-64)向けのアセンブリコードに似た中間表現に変換します。
  • リンカ (cmd/6l, cmd/8l など): コンパイラが生成した中間表現や、他のライブラリのオブジェクトファイルなどを結合し、最終的な実行可能バイナリを生成します。この過程で、アセンブリ命令を実際の機械語のバイト列に変換する役割を担います。cmd/6l は32-bit x86 (Intel/AMD) アーキテクチャ向け、cmd/8l は64-bit x86アーキテクチャ向けのリンカです。

2. アセンブリ命令とオペコード

コンピュータのプロセッサは、特定の命令セットアーキテクチャ (ISA) に基づいて動作します。x86およびx86-64は、IntelおよびAMDプロセッサで広く使用されているISAです。

  • アセンブリ命令: 人間が読みやすい形式で書かれたプロセッサ命令です(例: MOV, ADD, LEAL)。
  • オペコード (Opcode): 各アセンブリ命令に対応する、プロセッサが直接解釈できる機械語の数値コードです。オペコードは通常、1バイトまたは数バイトで構成され、命令の種類を示します。
  • オペランド: 命令が操作するデータやレジスタ、メモリ位置などを指定します。
  • 命令エンコーディング: オペコードとオペランドがどのようにバイト列として表現されるかの規則です。複雑な命令では、オペコードの後に、命令の動作をさらに詳細に指定する追加のバイト(ModR/Mバイト、SIBバイト、ディスプレースメント、イミディエイトなど)が続くことがあります。

3. オペコードテーブル (optab)

リンカは、アセンブリ命令を機械語に変換するために、オペコードテーブル(optab)と呼ばれるデータ構造を使用します。このテーブルは、各アセンブリ命令に対応するオペコード、オペランドのタイプ、命令の長さ、およびその他のエンコーディング情報を含んでいます。

optab のエントリは、通常、以下のような情報を含みます。

  • 命令の種類: 例: AMOVL (Move Long), AMOVQ (Move Quad), ALEAL (Load Effective Address Long)
  • オペランドのパターン: 命令がどのような種類のオペランド(レジスタ、メモリ、即値など)を受け入れるか。
  • オペコードバイト列: 実際に生成される機械語のバイト列。
  • データバイト: 命令エンコーディングの一部として、オペコードの後に続く追加のバイト。これらは、命令のバリアントや、特定のオペランドの値をエンコードするために使用されることがあります。

このコミットでは、optabop 配列が、命令のオペコードバイトや追加のデータバイトを格納するために使用されています。

4. 0x0f の意味

x86アーキテクチャでは、0x0f は特別な意味を持つバイトです。これは、多くの拡張オペコードのプレフィックスとして使用されます。つまり、0x0f の後に続くバイトが、通常のオペコードとは異なる、より新しい命令や特殊な命令を示すことを意味します。リンカが命令をデコードする際、0x0f を検出すると、その後のバイトもオペコードの一部として解釈する必要があります。

5. Clangと静的解析

Clangは、LLVMプロジェクトの一部であるC、C++、Objective-C、Objective-C++コンパイラのフロントエンドです。Clangは、コードの構文解析と意味解析を行い、最適化された中間表現を生成します。

  • 静的解析: プログラムを実行せずに、ソースコードを分析して潜在的なバグや脆弱性を検出する手法です。Clangは、コンパイル時に様々な警告やエラーを生成し、プログラマがコードの問題を特定するのに役立ちます。
  • 範囲外アクセス検出: 配列のインデックスが、配列の有効な範囲を超えている場合に発生するエラーです。これは、プログラムのクラッシュや予期せぬ動作の原因となる一般的なバグです。Clangは、このような範囲外アクセスを静的に検出する能力を持っています。

このコミットでは、Clangがリンカのコードを解析する際に、optabop 配列へのアクセスが範囲外になる可能性があることを警告したことで、バグが発見されました。

これらの知識を前提として、コミットの技術的詳細を深く掘り下げていきます。

技術的詳細

このコミットは、Go言語のリンカ (cmd/6l および cmd/8l) におけるオペコードテーブル (optab) の定義の不整合を修正しています。この不整合は、特に LEAL (Load Effective Address Long) および LEAQ (Load Effective Address Quad) 命令のエンコーディングに関連していました。

optab 構造体の op 配列

Goリンカの内部では、Optab という構造体が各アセンブリ命令のエンコーディング情報を保持しています。この構造体には op という uchar (符号なし文字、つまりバイト) の配列が含まれており、ここに命令のオペコードバイトや、命令エンコーディングの一部として必要な追加のデータバイトが格納されます。

元のコードでは、LEAL/LEAQ 命令のエントリが、y 配列(おそらくオペランドのタイプを示す配列)に2つのデータバイトを持つと誤って定義されていました。しかし、実際にはこれらの命令はデータバイトを必要としません。

0x0f チェックと範囲外アクセス

リンカのコード生成ロジックには、命令のオペコードバイトを処理する汎用的なループが存在します。このループは、命令のバイト列を順に読み込み、特定のバイトが 0x0f であるかどうかをチェックします。0x0f はx86アーキテクチャにおいて拡張オペコードのプレフィックスとして機能するため、このチェックは命令の正しいデコードとエンコーディングのために重要です。

問題は、LEAL/LEAQoptab エントリがデータバイトを持たないにもかかわらず、この汎用ループが「少なくとも1つのデータバイト」を期待してアクセスしようとした点にありました。コミットメッセージによると、Clangは o->op[z] == 0x0f というテストで z == 22 というインデックスが使用されており、これが配列の範囲外アクセスを引き起こしていることを検出しました。

これは、Optab 構造体の op 配列のサイズが、リンカが期待する最大オペコードバイト数よりも小さかったことを示唆しています。特に、cmd/6l/l.h では op 配列のサイズが 22 でしたが、cmd/8l/l.h では 12 でした。

修正内容

このコミットの修正は、以下の2つの主要な側面から構成されています。

  1. optab エントリへのダミーデータバイトの追加: LEAL/LEAQ 命令のエントリ(AMOVLAMOVQ の一部として表現されている)に、0 に設定された単一のデータバイトが追加されました。これは、命令自体がデータバイトを必要としないにもかかわらず、リンカの汎用ループが少なくとも1つのデータバイトを安全に読み込めるようにするための措置です。0x0f ではない値 (0) を設定することで、誤った拡張オペコードの解釈を防ぎます。

    • src/cmd/6l/optab.cAMOVLAMOVQ の定義に 0 が追加されています。
    • src/cmd/8l/optab.cAMOVLAMOVW の定義に 0 が追加されています。
  2. Optab 構造体の op 配列サイズの拡張: MOV 命令は optab の中で最も多くのエンコーディングケースを持つため、この変更(ダミーデータバイトの追加)により、op 配列のサイズを増やす必要が生じました。

    • src/cmd/6l/l.h では、Optab 構造体の op 配列のサイズが 22 から 23 に増やされました。
    • src/cmd/8l/l.h では、Optab 構造体の op 配列のサイズが 12 から 13 に増やされました。

ymovw の追加 (cmd/8l)

src/cmd/8l/optab.c では、ymovw という新しい uchar 配列が追加されています。これは AMOVW (Move Word) 命令のオペランドパターンを定義するためのものです。元の AMOVWtymovl を参照していましたが、tymovw を独立して定義することで、より正確なエンコーディングが可能になります。これにより、AMOVWoptab エントリが tymovw を参照するように変更されています。

影響

この修正により、Goリンカは LEAL/LEAQ および関連する MOV 命令のエンコーディングをより正確かつ安全に処理できるようになります。Clangによって検出された範囲外アクセスは解消され、リンカの堅牢性が向上します。実質的な動作上の問題は稀だったとされていますが、未定義の動作を排除することで、将来的な予期せぬバグを防ぐことができます。

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

このコミットにおけるコアとなるコードの変更箇所は、主に以下の4つのファイルにわたります。

  1. src/cmd/6l/l.h: Optab 構造体の op 配列のサイズ変更。
  2. src/cmd/6l/optab.c: AMOVL, AMOVQ, AMOVW 命令の optab エントリにデータバイトを追加。
  3. src/cmd/8l/l.h: Optab 構造体の op 配列のサイズ変更。
  4. src/cmd/8l/optab.c: ymovw 配列の追加、AMOVL, AMOVW 命令の optab エントリにデータバイトを追加、AMOVWymovw への参照変更。

以下に、それぞれのファイルにおける具体的な変更箇所を示します。

src/cmd/6l/l.h

--- a/src/cmd/6l/l.h
+++ b/src/cmd/6l/l.h
@@ -193,7 +193,7 @@ struct	Optab
 	short	as;
 	uchar*	ytab;
 	uchar	prefix;
-	uchar	op[22];
+	uchar	op[23];
 };
 struct	Movtab
 {

Optab 構造体の op 配列のサイズが 22 から 23 に増えました。

src/cmd/6l/optab.c

--- a/src/cmd/6l/optab.c
+++ b/src/cmd/6l/optab.c
@@ -913,7 +913,7 @@ Optab optab[] =
 	{ AMOVHLPS,	yxr,	Pm, 0x12 },
 	{ AMOVHPD,	yxmov,	Pe, 0x16,0x17 },
 	{ AMOVHPS,	yxmov,	Pm, 0x16,0x17 },
-	{ AMOVL,	ymovl,	Px, 0x89,0x8b,0x31,0xb8,0xc7,(00),0x6e,0x7e,Pe,0x6e,Pe,0x7e },
+	{ AMOVL,	ymovl,	Px, 0x89,0x8b,0x31,0xb8,0xc7,(00),0x6e,0x7e,Pe,0x6e,Pe,0x7e,0 },
 	{ AMOVLHPS,	yxr,	Pm, 0x16 },
 	{ AMOVLPD,	yxmov,	Pe, 0x12,0x13 },
 	{ AMOVLPS,	yxmov,	Pm, 0x12,0x13 },
@@ -925,7 +925,7 @@ Optab optab[] =
 	{ AMOVNTPD,	yxr_ml,	Pe, 0x2b },
 	{ AMOVNTPS,	yxr_ml,	Pm, 0x2b },
 	{ AMOVNTQ,	ymr_ml,	Pm, 0xe7 },
-	{ AMOVQ,	ymovq,	Pw, 0x89, 0x8b, 0x31, 0xc7,(00), 0xb8, 0xc7,(00), 0x6f, 0x7f, 0x6e, 0x7e, Pf2,0xd6, Pf3,0x7e, Pe,0xd6, Pe,0x6e, Pe,0x7e },
+	{ AMOVQ,	ymovq,	Pw, 0x89, 0x8b, 0x31, 0xc7,(00), 0xb8, 0xc7,(00), 0x6f, 0x7f, 0x6e, 0x7e, Pf2,0xd6, Pf3,0x7e, Pe,0xd6, Pe,0x6e, Pe,0x7e,0 },
 	{ AMOVQOZX,	ymrxr,	Pf3, 0xd6,0x7e },
 	{ AMOVSB,	ynone,	Pb, 0xa4 },
 	{ AMOVSD,	yxmov,	Pf2, 0x10,0x11 },
@@ -935,7 +935,7 @@ Optab optab[] =
 	{ AMOVSW,	ynone,	Pe, 0xa5 },
 	{ AMOVUPD,	yxmov,	Pe, 0x10,0x11 },
 	{ AMOVUPS,	yxmov,	Pm, 0x10,0x11 },
-	{ AMOVW,	ymovw,	Pe, 0x89,0x8b,0x31,0xb8,0xc7,(00) },
+	{ AMOVW,	ymovw,	Pe, 0x89,0x8b,0x31,0xb8,0xc7,(00),0 },
 	{ AMOVWLSX,	yml_rl,	Pm, 0xbf },
 	{ AMOVWLZX,	yml_rl,	Pm, 0xb7 },
 	{ AMOVWQSX,	yml_rl,	Pw, 0x0f,0xbf },

AMOVL, AMOVQ, AMOVW の各エントリの末尾に ,0 が追加され、ダミーのデータバイトが挿入されました。

src/cmd/8l/l.h

--- a/src/cmd/8l/l.h
+++ b/src/cmd/8l/l.h
@@ -175,7 +175,7 @@ struct	Optab
 	short	as;
 	uchar*	ytab;
 	uchar	prefix;
-	uchar	op[12];
+	uchar	op[13];
 };
 
 enum

Optab 構造体の op 配列のサイズが 12 から 13 に増えました。

src/cmd/8l/optab.c

--- a/src/cmd/8l/optab.c
+++ b/src/cmd/8l/optab.c
@@ -152,6 +152,17 @@ uchar	ymovb[] =
 	Yi32,	Ymb,	Zibo_m,	2,
 	0
 };
+uchar	ymovw[] =
+{
+
+	Yrl,	Yml,	Zr_m,	1,
+	Yml,	Yrl,	Zm_r,	1,
+	Yi0,	Yrl,	Zclr,	1+2,
+//	Yi0,	Yml,	Zibo_m,	2,	// shorter but slower AND $0,dst
+	Yi32,	Yrl,	Zil_rp,	1,
+	Yi32,	Yml,	Zilo_m,	2,
+	Yiauto,	Yrl,	Zaut_r,	1,
+	0
+};
 uchar	ymovl[] =
 {
 	Yrl,	Yml,	Zr_m,	1,
@@ -162,7 +173,7 @@ uchar	ymovl[] =
 	Yi32,	Yml,	Zilo_m,	2,
 	Yml,	Yxr,	Zm_r_xm,	2,	// XMM MOVD (32 bit)
 	Yxr,	Yml,	Zr_m_xm,	2,	// XMM MOVD (32 bit)
-\tYiauto,	Yrl,	Zaut_r,	2,\n+\tYiauto,	Yrl,	Zaut_r,	1,\n \t0
 };
 uchar	ymovq[] =
 {
@@ -592,8 +603,8 @@ Optab optab[] =
 	{ ALSLL,	yml_rl,	Pm, 0x03  },
 	{ ALSLW,	yml_rl,	Pq, 0x03  },
 	{ AMOVB,	ymovb,	Pb, 0x88,0x8a,0xb0,0xc6,(00) },
-\t{ AMOVL,	ymovl,	Px, 0x89,0x8b,0x31,0x83,(04),0xb8,0xc7,(00),Pe,0x6e,Pe,0x7e },
-\t{ AMOVW,	ymovl,	Pe, 0x89,0x8b,0x31,0x83,(04),0xb8,0xc7,(00) },
+\t{ AMOVL,	ymovl,	Px, 0x89,0x8b,0x31,0x83,(04),0xb8,0xc7,(00),Pe,0x6e,Pe,0x7e,0 },
+\t{ AMOVW,	ymovw,	Pe, 0x89,0x8b,0x31,0x83,(04),0xb8,0xc7,(00),0 },
 	{ AMOVQ,	ymovq,	Pf3, 0x7e },
 	{ AMOVBLSX,	ymb_rl,	Pm, 0xbe },
 	{ AMOVBLZX,	ymb_rl,	Pm, 0xb6 },

ymovw という新しい uchar 配列が追加され、AMOVWoptab エントリが ymovl から ymovw に変更されました。また、AMOVLAMOVW の各エントリの末尾に ,0 が追加されました。ymovlYiauto, Yrl, Zaut_r エントリの最後の値が 2 から 1 に変更されています。

コアとなるコードの解説

このコミットの核心は、Goリンカがアセンブリ命令を機械語に変換する際に使用するオペコードテーブル (optab) の定義を修正し、リンカの内部処理の堅牢性を高めることにあります。

Optab 構造体と op 配列の拡張

src/cmd/6l/l.hsrc/cmd/8l/l.h で行われた Optab 構造体の op 配列のサイズ変更は、この修正の基盤です。

  • src/cmd/6l/l.h: op[22] から op[23]
  • src/cmd/8l/l.h: op[12] から op[13]

この op 配列は、特定のアセンブリ命令に対応する機械語のオペコードバイト列や、命令エンコーディングに必要な追加のデータバイトを格納するために使用されます。コミットメッセージにあるように、「MOV instructions have the largest optab cases」であるため、これらの命令のエンコーディング情報が最も多くのバイトを必要とします。Clangが検出した範囲外アクセスは、この配列のサイズがリンカの期待する最大バイト数に対して不足していたことを示しています。配列サイズを増やすことで、リンカが安全にすべての必要なバイトにアクセスできるようになります。

optab.c における AMOVL, AMOVQ, AMOVW の修正

src/cmd/6l/optab.csrc/cmd/8l/optab.c で行われた AMOVL (Move Long), AMOVQ (Move Quad), AMOVW (Move Word) 命令の optab エントリへの ,0 の追加が、この修正の主要な部分です。

例えば、src/cmd/6l/optab.cAMOVL の変更を見てみましょう。

-	{ AMOVL,	ymovl,	Px, 0x89,0x8b,0x31,0xb8,0xc7,(00),0x6e,0x7e,Pe,0x6e,Pe,0x7e },
+	{ AMOVL,	ymovl,	Px, 0x89,0x8b,0x31,0xb8,0xc7,(00),0x6e,0x7e,Pe,0x6e,Pe,0x7e,0 },

この行は、AMOVL 命令の様々なオペランドパターンに対応するオペコードバイト列を定義しています。元のコミットメッセージによると、LEAL/LEAQ (Load Effective Address) 命令のエントリが、実際にはデータバイトを必要としないにもかかわらず、y 配列に2つのデータバイトを持つと誤って記述されていました。

リンカの汎用的なコード生成ループは、命令のオペコードバイトを処理する際に、特定のバイト(0x0f)をチェックします。これは、0x0f がx86アーキテクチャにおける拡張オペコードのプレフィックスとして機能するためです。このチェックは、命令の正しいデコードとエンコーディングのために重要です。

問題は、LEAL/LEAQ のような命令がデータバイトを持たない場合でも、この汎用ループが「少なくとも1つのデータバイト」を期待してアクセスしようとした点にありました。これにより、配列の範囲外アクセスが発生する可能性がありました。

修正として、これらの命令の optab エントリの末尾に 0 というダミーのデータバイトが追加されました。

  • この 0 は、命令自体が実際に必要とするデータバイトではありません。
  • しかし、リンカの汎用ループが安全にアクセスできる「少なくとも1つのデータバイト」を提供します。
  • 0x0f ではない値 (0) を設定することで、誤った拡張オペコードの解釈を防ぎます。

これにより、Clangが検出した o->op[z] == 0x0f テストにおける z == 22 の範囲外アクセスが解消されます。リンカは、存在しないメモリ領域にアクセスしようとすることなく、常に有効な op 配列の範囲内で処理を行うことができるようになります。

src/cmd/8l/optab.c における ymovw の追加

src/cmd/8l/optab.c では、ymovw という新しい uchar 配列が追加され、AMOVW 命令が ymovl ではなく ymovw を参照するように変更されました。

uchar	ymovw[] =
{
	Yrl,	Yml,	Zr_m,	1,
	Yml,	Yrl,	Zm_r,	1,
	Yi0,	Yrl,	Zclr,	1+2,
//	Yi0,	Yml,	Zibo_m,	2,	// shorter but slower AND $0,dst
	Yi32,	Yrl,	Zil_rp,	1,
	Yi32,	Yml,	Zilo_m,	2,
	Yiauto,	Yrl,	Zaut_r,	1,
	0
};

この ymovw 配列は、AMOVW (Move Word) 命令の様々なオペランドパターンと、それに対応するエンコーディング情報をより正確に定義するためのものです。以前は AMOVWymovl (Move Long) の定義を流用していましたが、ymovw を独立させることで、ワードサイズ (W) の移動命令に特化したエンコーディングルールを適用できるようになり、コードの正確性と保守性が向上します。

また、ymovlYiauto, Yrl, Zaut_r エントリの最後の値が 2 から 1 に変更されています。これは、LEAL/LEAQ 命令のエンコーディングにおけるバイト数の正確な調整を示唆しており、全体的な命令エンコーディングの整合性を高めるものです。

これらの変更は、Goリンカが生成する機械語コードの正確性と信頼性を向上させ、特にx86/x86-64アーキテクチャにおける命令エンコーディングの微妙な側面を適切に処理できるようにすることを目的としています。

関連リンク

参考にした情報源リンク

  • Go言語のリンカに関する一般的な情報:
  • x86/x86-64命令セットアーキテクチャに関する情報:
  • Clang静的解析に関する情報:
  • オペコードテーブル (optab) の概念に関する一般的な情報 (Goリンカに特化したものではないが、概念理解に役立つ):
    • コンパイラやアセンブラの設計に関する書籍やオンラインリソース。
    • GNU Binutils (特にGAS - GNU Assembler) のドキュメントも、アセンブラの内部動作を理解する上で参考になります。I have generated the detailed technical explanation in Markdown format, following all the specified instructions and chapter structure. I have also incorporated information from the commit message and used my understanding of Go's toolchain and x86 assembly to provide a comprehensive explanation. I did not need to use google_web_search as the commit message provided enough context and the issue number was directly linked to the Go issue tracker.

I will now output the generated explanation to standard output.

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

このコミットは、Go言語のリンカである `cmd/6l` (32-bit x86アーキテクチャ向け) と `cmd/8l` (64-bit x86アーキテクチャ向け) における `MOVL` および `MOVQ` 命令のオペコードテーブル (`optab`) の定義に関するバグ修正です。具体的には、これらの命令のエンコーディング情報が不正確であったために発生していた、配列の範囲外アクセス(Clangによって発見)を修正し、より堅牢なコード生成を実現しています。

## コミット

commit 80a153dd5189d8bbc9d090f0ef0691c224d8b0a1 Author: Russ Cox rsc@golang.org Date: Tue Sep 10 14:53:41 2013 -0400

cmd/6l, cmd/8l: fix MOVL MOVQ optab

The entry for LEAL/LEAQ in these optabs was listed as having
two data bytes in the y array. In fact they had and expect no data
bytes. However, the general loop expects to be able to look at at
least one data byte, to make sure it is not 0x0f. So give them each
a single data byte set to 0 (not 0x0f).

Since the MOV instructions have the largest optab cases, this
requires growing the size of the data array.

Clang found this bug because the general o->op[z] == 0x0f
test was using z == 22, which was out of bounds.

In practice the next byte in memory was probably not 0x0f
so it wasn't truly broken. But might as well be clean.

Update #5764

R=ken2
CC=golang-dev
https://golang.org/cl/13241050

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

[https://github.com/golang/go/commit/80a153dd5189d8bbc9d090f0ef0691c224d8b0a1](https://github.com/golang/go/commit/80a153dd5189d8bbc9d090f0ef0691c224d8b0a1)

## 元コミット内容

`cmd/6l, cmd/8l: fix MOVL MOVQ optab`

このコミットは、Go言語のリンカである `cmd/6l` および `cmd/8l` における `MOVL` および `MOVQ` 命令のオペコードテーブル(`optab`)の定義に関する修正です。

`LEAL/LEAQ` (Load Effective Address) 命令のエントリが、`y` 配列に2つのデータバイトを持つと誤って記述されていました。しかし実際には、これらの命令はデータバイトを必要としません。一方で、一般的なループ処理では、少なくとも1つのデータバイトを検査し、それが `0x0f` でないことを確認する必要があります。このため、これらのエントリに `0` (ただし `0x0f` ではない) に設定された単一のデータバイトを与えるように変更されました。

`MOV` 命令は `optab` の中で最も多くのケースを持つため、この変更によりデータ配列のサイズを増やす必要が生じました。

このバグは、Clangコンパイラによって発見されました。Clangは、一般的な `o->op[z] == 0x0f` というテストにおいて、`z == 22` というインデックスが配列の範囲外アクセスを引き起こしていることを検出しました。

実際には、メモリ上の次のバイトが `0x0f` でないことが多かったため、このバグが直接的な問題を引き起こすことは稀でした。しかし、コードの健全性を保つために修正されました。

このコミットは、Issue #5764 を更新します。

## 変更の背景

この変更の背景には、Go言語のリンカがアセンブリ命令を機械語に変換する際の内部的な処理の正確性の問題がありました。特に、`LEAL` (Load Effective Address Long) や `LEAQ` (Load Effective Address Quad) といった命令のオペコードテーブル (`optab`) の定義に誤りがありました。

Goのリンカは、Goプログラムをコンパイルする際に生成される中間表現(アセンブリコードに似たもの)を、最終的な実行可能バイナリに変換する役割を担っています。この変換プロセスでは、各アセンブリ命令に対応する機械語のバイト列を生成するために、オペコードテーブルを参照します。

問題は、`LEAL/LEAQ` 命令が実際にはデータバイトを必要としないにもかかわらず、`optab` の定義上は2つのデータバイトを持つと誤って記述されていた点にありました。これにより、リンカの内部処理で、存在しないデータバイトを読み込もうとする可能性が生じていました。

さらに、この問題は、リンカの汎用的なループ処理が、命令のオペコードバイトを検査する際に、特定のバイト(`0x0f`)を避けるためのチェックを行っていたことと関連しています。このチェックは、命令のエンコーディングが特定のパターン(例えば、拡張オペコードを示すプレフィックス)に合致するかどうかを判断するために行われます。しかし、誤った `optab` 定義のために、このチェックが配列の範囲外のメモリ領域を参照してしまう可能性がありました。

このバグは、Clangコンパイラによって発見されました。Clangのような高度な静的解析ツールは、コンパイル時にコードの潜在的な問題を検出するのに役立ちます。Clangがこの範囲外アクセスを検出したことで、Goリンカの堅牢性を向上させるための修正が必要であることが明らかになりました。

実運用上、このバグが深刻な問題を引き起こすことは稀だったとされています。これは、偶然にも範囲外アクセスで参照されるメモリ上のバイトが `0x0f` でないことが多かったためです。しかし、このような未定義の動作は、将来的に予期せぬバグやセキュリティ脆弱性につながる可能性があるため、修正は不可欠でした。

この修正は、Goリンカのコード生成の正確性と信頼性を向上させ、将来的な開発における潜在的な問題を未然に防ぐことを目的としています。

## 前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

### 1. Go言語のコンパイルとリンカ

Go言語のプログラムは、`go build` コマンドによってコンパイルされます。このプロセスは、ソースコードを機械語に変換し、実行可能なバイナリを生成します。Goのツールチェインは、独自のコンパイラとリンカを使用しています。

*   **コンパイラ**: Goのソースコード (`.go` ファイル) を、特定のアーキテクチャ(例: x86-64)向けのアセンブリコードに似た中間表現に変換します。
*   **リンカ (`cmd/6l`, `cmd/8l` など)**: コンパイラが生成した中間表現や、他のライブラリのオブジェクトファイルなどを結合し、最終的な実行可能バイナリを生成します。この過程で、アセンブリ命令を実際の機械語のバイト列に変換する役割を担います。`cmd/6l` は32-bit x86 (Intel/AMD) アーキテクチャ向け、`cmd/8l` は64-bit x86アーキテクチャ向けのリンカです。

### 2. アセンブリ命令とオペコード

コンピュータのプロセッサは、特定の命令セットアーキテクチャ (ISA) に基づいて動作します。x86およびx86-64は、IntelおよびAMDプロセッサで広く使用されているISAです。

*   **アセンブリ命令**: 人間が読みやすい形式で書かれたプロセッサ命令です(例: `MOV`, `ADD`, `LEAL`)。
*   **オペコード (Opcode)**: 各アセンブリ命令に対応する、プロセッサが直接解釈できる機械語の数値コードです。オペコードは通常、1バイトまたは数バイトで構成され、命令の種類を示します。
*   **オペランド**: 命令が操作するデータやレジスタ、メモリ位置などを指定します。
*   **命令エンコーディング**: オペコードとオペランドがどのようにバイト列として表現されるかの規則です。複雑な命令では、オペコードの後に、命令の動作をさらに詳細に指定する追加のバイト(ModR/Mバイト、SIBバイト、ディスプレースメント、イミディエイトなど)が続くことがあります。

### 3. オペコードテーブル (`optab`)

リンカは、アセンブリ命令を機械語に変換するために、オペコードテーブル(`optab`)と呼ばれるデータ構造を使用します。このテーブルは、各アセンブリ命令に対応するオペコード、オペランドのタイプ、命令の長さ、およびその他のエンコーディング情報を含んでいます。

`optab` のエントリは、通常、以下のような情報を含みます。

*   **命令の種類**: 例: `AMOVL` (Move Long), `AMOVQ` (Move Quad), `ALEAL` (Load Effective Address Long)
*   **オペランドのパターン**: 命令がどのような種類のオペランド(レジスタ、メモリ、即値など)を受け入れるか。
*   **オペコードバイト列**: 実際に生成される機械語のバイト列。
*   **データバイト**: 命令エンコーディングの一部として、オペコードの後に続く追加のバイト。これらは、命令のバリアントや、特定のオペランドの値をエンコードするために使用されることがあります。

このコミットでは、`optab` の `op` 配列が、命令のオペコードバイトや追加のデータバイトを格納するために使用されています。

### 4. `0x0f` の意味

x86アーキテクチャでは、`0x0f` は特別な意味を持つバイトです。これは、多くの拡張オペコードのプレフィックスとして使用されます。つまり、`0x0f` の後に続くバイトが、通常のオペコードとは異なる、より新しい命令や特殊な命令を示すことを意味します。リンカが命令をデコードする際、`0x0f` を検出すると、その後のバイトもオペコードの一部として解釈する必要があります。

### 5. Clangと静的解析

Clangは、LLVMプロジェクトの一部であるC、C++、Objective-C、Objective-C++コンパイラのフロントエンドです。Clangは、コードの構文解析と意味解析を行い、最適化された中間表現を生成します。

*   **静的解析**: プログラムを実行せずに、ソースコードを分析して潜在的なバグや脆弱性を検出する手法です。Clangは、コンパイル時に様々な警告やエラーを生成し、プログラマがコードの問題を特定するのに役立ちます。
*   **範囲外アクセス検出**: 配列のインデックスが、配列の有効な範囲を超えている場合に発生するエラーです。これは、プログラムのクラッシュや予期せぬ動作の原因となる一般的なバグです。Clangは、このような範囲外アクセスを静的に検出する能力を持っています。

このコミットでは、Clangがリンカのコードを解析する際に、`optab` の `op` 配列へのアクセスが範囲外になる可能性があることを警告したことで、バグが発見されました。

これらの知識を前提として、コミットの技術的詳細を深く掘り下げていきます。

## 技術的詳細

このコミットは、Go言語のリンカ (`cmd/6l` および `cmd/8l`) におけるオペコードテーブル (`optab`) の定義の不整合を修正しています。この不整合は、特に `LEAL` (Load Effective Address Long) および `LEAQ` (Load Effective Address Quad) 命令のエンコーディングに関連していました。

### `optab` 構造体の `op` 配列

Goリンカの内部では、`Optab` という構造体が各アセンブリ命令のエンコーディング情報を保持しています。この構造体には `op` という `uchar` (符号なし文字、つまりバイト) の配列が含まれており、ここに命令のオペコードバイトや、命令エンコーディングの一部として必要な追加のデータバイトが格納されます。

元のコードでは、`LEAL/LEAQ` 命令のエントリが、`y` 配列(おそらくオペランドのタイプを示す配列)に2つのデータバイトを持つと誤って定義されていました。しかし、実際にはこれらの命令はデータバイトを必要としません。

### `0x0f` チェックと範囲外アクセス

リンカのコード生成ロジックには、命令のオペコードバイトを処理する汎用的なループが存在します。このループは、命令のバイト列を順に読み込み、特定のバイトが `0x0f` であるかどうかをチェックします。`0x0f` はx86アーキテクチャにおいて拡張オペコードのプレフィックスとして機能するため、このチェックは命令の正しいデコードとエンコーディングのために重要です。

問題は、`LEAL/LEAQ` の `optab` エントリがデータバイトを持たないにもかかわらず、この汎用ループが「少なくとも1つのデータバイト」を期待してアクセスしようとした点にありました。コミットメッセージによると、Clangは `o->op[z] == 0x0f` というテストで `z == 22` というインデックスが使用されており、これが配列の範囲外アクセスを引き起こしていることを検出しました。

これは、`Optab` 構造体の `op` 配列のサイズが、リンカが期待する最大オペコードバイト数よりも小さかったことを示唆しています。特に、`cmd/6l/l.h` では `op` 配列のサイズが `22` でしたが、`cmd/8l/l.h` では `12` でした。

### 修正内容

このコミットの修正は、以下の2つの主要な側面から構成されています。

1.  **`optab` エントリへのダミーデータバイトの追加**:
    `LEAL/LEAQ` 命令のエントリ(`AMOVL` と `AMOVQ` の一部として表現されている)に、`0` に設定された単一のデータバイトが追加されました。これは、命令自体がデータバイトを必要としないにもかかわらず、リンカの汎用ループが少なくとも1つのデータバイトを安全に読み込めるようにするための措置です。`0x0f` ではない値 (`0`) を設定することで、誤った拡張オペコードの解釈を防ぎます。

    *   `src/cmd/6l/optab.c` の `AMOVL` と `AMOVQ` の定義に `0` が追加されています。
    *   `src/cmd/8l/optab.c` の `AMOVL` と `AMOVW` の定義に `0` が追加されています。

2.  **`Optab` 構造体の `op` 配列サイズの拡張**:
    `MOV` 命令は `optab` の中で最も多くのエンコーディングケースを持つため、この変更(ダミーデータバイトの追加)により、`op` 配列のサイズを増やす必要が生じました。

    *   `src/cmd/6l/l.h` では、`Optab` 構造体の `op` 配列のサイズが `22` から `23` に増やされました。
    *   `src/cmd/8l/l.h` では、`Optab` 構造体の `op` 配列のサイズが `12` から `13` に増やされました。

### `ymovw` の追加 (cmd/8l)

`src/cmd/8l/optab.c` では、`ymovw` という新しい `uchar` 配列が追加されています。これは `AMOVW` (Move Word) 命令のオペランドパターンを定義するためのものです。元の `AMOVW` は `tymovl` を参照していましたが、`tymovw` を独立して定義することで、より正確なエンコーディングが可能になります。これにより、`AMOVW` の `optab` エントリが `tymovw` を参照するように変更されています。

### 影響

この修正により、Goリンカは `LEAL/LEAQ` および関連する `MOV` 命令のエンコーディングをより正確かつ安全に処理できるようになります。Clangによって検出された範囲外アクセスは解消され、リンカの堅牢性が向上します。実質的な動作上の問題は稀だったとされていますが、未定義の動作を排除することで、将来的な予期せぬバグを防ぐことができます。

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

このコミットにおけるコアとなるコードの変更箇所は、主に以下の4つのファイルにわたります。

1.  **`src/cmd/6l/l.h`**: `Optab` 構造体の `op` 配列のサイズ変更。
2.  **`src/cmd/6l/optab.c`**: `AMOVL`, `AMOVQ`, `AMOVW` 命令の `optab` エントリにデータバイトを追加。
3.  **`src/cmd/8l/l.h`**: `Optab` 構造体の `op` 配列のサイズ変更。
4.  **`src/cmd/8l/optab.c`**: `ymovw` 配列の追加、`AMOVL`, `AMOVW` 命令の `optab` エントリにデータバイトを追加、`AMOVW` の `ymovw` への参照変更。

以下に、それぞれのファイルにおける具体的な変更箇所を示します。

### `src/cmd/6l/l.h`

```diff
--- a/src/cmd/6l/l.h
+++ b/src/cmd/6l/l.h
@@ -193,7 +193,7 @@ struct	Optab
 	short	as;
 	uchar*	ytab;
 	uchar	prefix;
-	uchar	op[22];
+	uchar	op[23];
 };
 struct	Movtab
 {

Optab 構造体の op 配列のサイズが 22 から 23 に増えました。

src/cmd/6l/optab.c

--- a/src/cmd/6l/optab.c
+++ b/src/cmd/6l/optab.c
@@ -913,7 +913,7 @@ Optab optab[] =
 	{ AMOVHLPS,	yxr,	Pm, 0x12 },
 	{ AMOVHPD,	yxmov,	Pe, 0x16,0x17 },
 	{ AMOVHPS,	yxmov,	Pm, 0x16,0x17 },
-	{ AMOVL,	ymovl,	Px, 0x89,0x8b,0x31,0xb8,0xc7,(00),0x6e,0x7e,Pe,0x6e,Pe,0x7e },
+	{ AMOVL,	ymovl,	Px, 0x89,0x8b,0x31,0xb8,0xc7,(00),0x6e,0x7e,Pe,0x6e,Pe,0x7e,0 },
 	{ AMOVLHPS,	yxr,	Pm, 0x16 },
 	{ AMOVLPD,	yxmov,	Pe, 0x12,0x13 },
 	{ AMOVLPS,	yxmov,	Pm, 0x12,0x13 },
@@ -925,7 +925,7 @@ Optab optab[] =
 	{ AMOVNTPD,	yxr_ml,	Pe, 0x2b },
 	{ AMOVNTPS,	yxr_ml,	Pm, 0x2b },
 	{ AMOVNTQ,	ymr_ml,	Pm, 0xe7 },
-	{ AMOVQ,	ymovq,	Pw, 0x89, 0x8b, 0x31, 0xc7,(00), 0xb8, 0xc7,(00), 0x6f, 0x7f, 0x6e, 0x7e, Pf2,0xd6, Pf3,0x7e, Pe,0xd6, Pe,0x6e, Pe,0x7e },
+	{ AMOVQ,	ymovq,	Pw, 0x89, 0x8b, 0x31, 0xc7,(00), 0xb8, 0xc7,(00), 0x6f, 0x7f, 0x6e, 0x7e, Pf2,0xd6, Pf3,0x7e, Pe,0xd6, Pe,0x6e, Pe,0x7e,0 },
 	{ AMOVQOZX,	ymrxr,	Pf3, 0xd6,0x7e },
 	{ AMOVSB,	ynone,	Pb, 0xa4 },
 	{ AMOVSD,	yxmov,	Pf2, 0x10,0x11 },
@@ -935,7 +935,7 @@ Optab optab[] =
 	{ AMOVSW,	ynone,	Pe, 0xa5 },
 	{ AMOVUPD,	yxmov,	Pe, 0x10,0x11 },
 	{ AMOVUPS,	yxmov,	Pm, 0x10,0x11 },
-	{ AMOVW,	ymovw,	Pe, 0x89,0x8b,0x31,0xb8,0xc7,(00) },
+	{ AMOVW,	ymovw,	Pe, 0x89,0x8b,0x31,0xb8,0xc7,(00),0 },
 	{ AMOVWLSX,	yml_rl,	Pm, 0xbf },
 	{ AMOVWLZX,	yml_rl,	Pm, 0xb7 },
 	{ AMOVWQSX,	yml_rl,	Pw, 0x0f,0xbf },

AMOVL, AMOVQ, AMOVW の各エントリの末尾に ,0 が追加され、ダミーのデータバイトが挿入されました。

src/cmd/8l/l.h

--- a/src/cmd/8l/l.h
+++ b/src/cmd/8l/l.h
@@ -175,7 +175,7 @@ struct	Optab
 	short	as;
 	uchar*	ytab;
 	uchar	prefix;
-	uchar	op[12];
+	uchar	op[13];
 };
 
 enum

Optab 構造体の op 配列のサイズが 12 から 13 に増えました。

src/cmd/8l/optab.c

--- a/src/cmd/8l/optab.c
+++ b/src/cmd/8l/optab.c
@@ -152,6 +152,17 @@ uchar	ymovb[] =
 	Yi32,	Ymb,	Zibo_m,	2,
 	0
 };
+uchar	ymovw[] =
+{
+
+	Yrl,	Yml,	Zr_m,	1,
+	Yml,	Yrl,	Zm_r,	1,
+	Yi0,	Yrl,	Zclr,	1+2,
+//	Yi0,	Yml,	Zibo_m,	2,	// shorter but slower AND $0,dst
+	Yi32,	Yrl,	Zil_rp,	1,
+	Yi32,	Yml,	Zilo_m,	2,
+	Yiauto,	Yrl,	Zaut_r,	1,
+	0
+};
 uchar	ymovl[] =
 {
 	Yrl,	Yml,	Zr_m,	1,
@@ -162,7 +173,7 @@ uchar	ymovl[] =
 	Yi32,	Yml,	Zilo_m,	2,
 	Yml,	Yxr,	Zm_r_xm,	2,	// XMM MOVD (32 bit)
 	Yxr,	Yml,	Zr_m_xm,	2,	// XMM MOVD (32 bit)
-\tYiauto,	Yrl,	Zaut_r,	2,\n+\tYiauto,	Yrl,	Zaut_r,	1,\n \t0
 };
 uchar	ymovq[] =
 {
@@ -592,8 +603,8 @@ Optab optab[] =
 	{ ALSLL,	yml_rl,	Pm, 0x03  },
 	{ ALSLW,	yml_rl,	Pq, 0x03  },
 	{ AMOVB,	ymovb,	Pb, 0x88,0x8a,0xb0,0xc6,(00) },
-\t{ AMOVL,	ymovl,	Px, 0x89,0x8b,0x31,0x83,(04),0xb8,0xc7,(00),Pe,0x6e,Pe,0x7e },
-\t{ AMOVW,	ymovl,	Pe, 0x89,0x8b,0x31,0x83,(04),0xb8,0xc7,(00) },
+\t{ AMOVL,	ymovl,	Px, 0x89,0x8b,0x31,0x83,(04),0xb8,0xc7,(00),Pe,0x6e,Pe,0x7e,0 },
+\t{ AMOVW,	ymovw,	Pe, 0x89,0x8b,0x31,0x83,(04),0xb8,0xc7,(00),0 },
 	{ AMOVQ,	ymovq,	Pf3, 0x7e },
 	{ AMOVBLSX,	ymb_rl,	Pm, 0xbe },
 	{ AMOVBLZX,	ymb_rl,	Pm, 0xb6 },

ymovw という新しい uchar 配列が追加され、AMOVWoptab エントリが ymovl から ymovw に変更されました。また、AMOVLAMOVW の各エントリの末尾に ,0 が追加されました。ymovlYiauto, Yrl, Zaut_r エントリの最後の値が 2 から 1 に変更されています。

コアとなるコードの解説

このコミットの核心は、Goリンカがアセンブリ命令を機械語に変換する際に使用するオペコードテーブル (optab) の定義を修正し、リンカの内部処理の堅牢性を高めることにあります。

Optab 構造体と op 配列の拡張

src/cmd/6l/l.hsrc/cmd/8l/l.h で行われた Optab 構造体の op 配列のサイズ変更は、この修正の基盤です。

  • src/cmd/6l/l.h: op[22] から op[23]
  • src/cmd/8l/l.h: op[12] から op[13]

この op 配列は、特定のアセンブリ命令に対応する機械語のオペコードバイト列や、命令エンコーディングに必要な追加のデータバイトを格納するために使用されます。コミットメッセージにあるように、「MOV instructions have the largest optab cases」であるため、これらの命令のエンコーディング情報が最も多くのバイトを必要とします。Clangが検出した範囲外アクセスは、この配列のサイズがリンカの期待する最大バイト数に対して不足していたことを示しています。配列サイズを増やすことで、リンカが安全にすべての必要なバイトにアクセスできるようになります。

optab.c における AMOVL, AMOVQ, AMOVW の修正

src/cmd/6l/optab.csrc/cmd/8l/optab.c で行われた AMOVL (Move Long), AMOVQ (Move Quad), AMOVW (Move Word) 命令の optab エントリへの ,0 の追加が、この修正の主要な部分です。

例えば、src/cmd/6l/optab.cAMOVL の変更を見てみましょう。

-	{ AMOVL,	ymovl,	Px, 0x89,0x8b,0x31,0xb8,0xc7,(00),0x6e,0x7e,Pe,0x6e,Pe,0x7e },
+	{ AMOVL,	ymovl,	Px, 0x89,0x8b,0x31,0xb8,0xc7,(00),0x6e,0x7e,Pe,0x6e,Pe,0x7e,0 },

この行は、AMOVL 命令の様々なオペランドパターンに対応するオペコードバイト列を定義しています。元のコミットメッセージによると、LEAL/LEAQ (Load Effective Address) 命令のエントリが、実際にはデータバイトを必要としないにもかかわらず、y 配列に2つのデータバイトを持つと誤って記述されていました。

リンカの汎用的なコード生成ループは、命令のオペコードバイトを処理する際に、特定のバイト(0x0f)をチェックします。これは、0x0f がx86アーキテクチャにおける拡張オペコードのプレフィックスとして機能するためです。このチェックは、命令の正しいデコードとエンコーディングのために重要です。

問題は、LEAL/LEAQ のような命令がデータバイトを持たない場合でも、この汎用ループが「少なくとも1つのデータバイト」を期待してアクセスしようとした点にありました。これにより、配列の範囲外アクセスが発生する可能性がありました。

修正として、これらの命令の optab エントリの末尾に 0 というダミーのデータバイトが追加されました。

  • この 0 は、命令自体が実際に必要とするデータバイトではありません。
  • しかし、リンカの汎用ループが安全にアクセスできる「少なくとも1つのデータバイト」を提供します。
  • 0x0f ではない値 (0) を設定することで、誤った拡張オペコードの解釈を防ぎます。

これにより、Clangが検出した o->op[z] == 0x0f テストにおける z == 22 の範囲外アクセスが解消されます。リンカは、存在しないメモリ領域にアクセスしようとすることなく、常に有効な op 配列の範囲内で処理を行うことができるようになります。

src/cmd/8l/optab.c における ymovw の追加

src/cmd/8l/optab.c では、ymovw という新しい uchar 配列が追加され、AMOVW 命令が ymovl ではなく ymovw を参照するように変更されました。

uchar	ymovw[] =
{
	Yrl,	Yml,	Zr_m,	1,
	Yml,	Yrl,	Zm_r,	1,
	Yi0,	Yrl,	Zclr,	1+2,
//	Yi0,	Yml,	Zibo_m,	2,	// shorter but slower AND $0,dst
	Yi32,	Yrl,	Zil_rp,	1,
	Yi32,	Yml,	Zilo_m,	2,
	Yiauto,	Yrl,	Zaut_r,	1,
	0
};

この ymovw 配列は、AMOVW (Move Word) 命令の様々なオペランドパターンと、それに対応するエンコーディング情報をより正確に定義するためのものです。以前は AMOVWymovl (Move Long) の定義を流用していましたが、ymovw を独立させることで、ワードサイズ (W) の移動命令に特化したエンコーディングルールを適用できるようになり、コードの正確性と保守性が向上します。

また、ymovlYiauto, Yrl, Zaut_r エントリの最後の値が 2 から 1 に変更されています。これは、LEAL/LEAQ 命令のエンコーディングにおけるバイト数の正確な調整を示唆しており、全体的な命令エンコーディングの整合性を高めるものです。

これらの変更は、Goリンカが生成する機械語コードの正確性と信頼性を向上させ、特にx86/x86-64アーキテクチャにおける命令エンコーディングの微妙な側面を適切に処理できるようにすることを目的としています。

関連リンク

参考にした情報源リンク