[インデックス 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) - オペランドのパターン: 命令がどのような種類のオペランド(レジスタ、メモリ、即値など)を受け入れるか。
- オペコードバイト列: 実際に生成される機械語のバイト列。
- データバイト: 命令エンコーディングの一部として、オペコードの後に続く追加のバイト。これらは、命令のバリアントや、特定のオペランドの値をエンコードするために使用されることがあります。
このコミットでは、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つの主要な側面から構成されています。
-
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
が追加されています。
-
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つのファイルにわたります。
src/cmd/6l/l.h
:Optab
構造体のop
配列のサイズ変更。src/cmd/6l/optab.c
:AMOVL
,AMOVQ
,AMOVW
命令のoptab
エントリにデータバイトを追加。src/cmd/8l/l.h
:Optab
構造体のop
配列のサイズ変更。src/cmd/8l/optab.c
:ymovw
配列の追加、AMOVL
,AMOVW
命令のoptab
エントリにデータバイトを追加、AMOVW
のymovw
への参照変更。
以下に、それぞれのファイルにおける具体的な変更箇所を示します。
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
配列が追加され、AMOVW
の optab
エントリが ymovl
から ymovw
に変更されました。また、AMOVL
と AMOVW
の各エントリの末尾に ,0
が追加されました。ymovl
の Yiauto, Yrl, Zaut_r
エントリの最後の値が 2
から 1
に変更されています。
コアとなるコードの解説
このコミットの核心は、Goリンカがアセンブリ命令を機械語に変換する際に使用するオペコードテーブル (optab
) の定義を修正し、リンカの内部処理の堅牢性を高めることにあります。
Optab
構造体と op
配列の拡張
src/cmd/6l/l.h
と src/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.c
と src/cmd/8l/optab.c
で行われた AMOVL
(Move Long), AMOVQ
(Move Quad), AMOVW
(Move Word) 命令の optab
エントリへの ,0
の追加が、この修正の主要な部分です。
例えば、src/cmd/6l/optab.c
の AMOVL
の変更を見てみましょう。
- { 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) 命令の様々なオペランドパターンと、それに対応するエンコーディング情報をより正確に定義するためのものです。以前は AMOVW
が ymovl
(Move Long) の定義を流用していましたが、ymovw
を独立させることで、ワードサイズ (W
) の移動命令に特化したエンコーディングルールを適用できるようになり、コードの正確性と保守性が向上します。
また、ymovl
の Yiauto, Yrl, Zaut_r
エントリの最後の値が 2
から 1
に変更されています。これは、LEAL/LEAQ
命令のエンコーディングにおけるバイト数の正確な調整を示唆しており、全体的な命令エンコーディングの整合性を高めるものです。
これらの変更は、Goリンカが生成する機械語コードの正確性と信頼性を向上させ、特にx86/x86-64アーキテクチャにおける命令エンコーディングの微妙な側面を適切に処理できるようにすることを目的としています。
関連リンク
- Go Issue 5764:
cmd/6l, cmd/8l: fix MOVL MOVQ optab
- https://go.dev/issue/5764 - Go CL 13241050:
cmd/6l, cmd/8l: fix MOVL MOVQ optab
- https://go.dev/cl/13241050 - GitHub Commit:
80a153dd5189d8bbc9d090f0ef0691c224d8b0a1
- https://github.com/golang/go/commit/80a153dd5189d8bbc9d090f0ef0691c224d8b0a1
参考にした情報源リンク
- Go言語のリンカに関する一般的な情報:
- "Go's linker" by Russ Cox: https://go.dev/doc/asm (Goのアセンブリに関する公式ドキュメント)
- "A Quick Guide to Go's Assembler": https://go.dev/doc/asm
- x86/x86-64命令セットアーキテクチャに関する情報:
- Intel® 64 and IA-32 Architectures Software Developer’s Manuals: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html (特にVolume 2A/2B/2C: Instruction Set Reference, A-Z)
- AMD64 Architecture Programmer’s Manuals: https://developer.amd.com/resources/developer-guides-manuals/
- Clang静的解析に関する情報:
- Clang Static Analyzer: https://clang-analyzer.llvm.org/
- LLVM Project: https://llvm.org/
- オペコードテーブル (
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
配列が追加され、AMOVW
の optab
エントリが ymovl
から ymovw
に変更されました。また、AMOVL
と AMOVW
の各エントリの末尾に ,0
が追加されました。ymovl
の Yiauto, Yrl, Zaut_r
エントリの最後の値が 2
から 1
に変更されています。
コアとなるコードの解説
このコミットの核心は、Goリンカがアセンブリ命令を機械語に変換する際に使用するオペコードテーブル (optab
) の定義を修正し、リンカの内部処理の堅牢性を高めることにあります。
Optab
構造体と op
配列の拡張
src/cmd/6l/l.h
と src/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.c
と src/cmd/8l/optab.c
で行われた AMOVL
(Move Long), AMOVQ
(Move Quad), AMOVW
(Move Word) 命令の optab
エントリへの ,0
の追加が、この修正の主要な部分です。
例えば、src/cmd/6l/optab.c
の AMOVL
の変更を見てみましょう。
- { 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) 命令の様々なオペランドパターンと、それに対応するエンコーディング情報をより正確に定義するためのものです。以前は AMOVW
が ymovl
(Move Long) の定義を流用していましたが、ymovw
を独立させることで、ワードサイズ (W
) の移動命令に特化したエンコーディングルールを適用できるようになり、コードの正確性と保守性が向上します。
また、ymovl
の Yiauto, Yrl, Zaut_r
エントリの最後の値が 2
から 1
に変更されています。これは、LEAL/LEAQ
命令のエンコーディングにおけるバイト数の正確な調整を示唆しており、全体的な命令エンコーディングの整合性を高めるものです。
これらの変更は、Goリンカが生成する機械語コードの正確性と信頼性を向上させ、特にx86/x86-64アーキテクチャにおける命令エンコーディングの微妙な側面を適切に処理できるようにすることを目的としています。
関連リンク
- Go Issue 5764:
cmd/6l, cmd/8l: fix MOVL MOVQ optab
- https://go.dev/issue/5764 - Go CL 13241050:
cmd/6l, cmd/8l: fix MOVL MOVQ optab
- https://go.dev/cl/13241050 - GitHub Commit:
80a153dd5189d8bbc9d090f0ef0691c224d8b0a1
- https://github.com/golang/go/commit/80a153dd5189d8bbc9d090f0ef0691c224d8b0a1
参考にした情報源リンク
- Go言語のリンカに関する一般的な情報:
- "Go's linker" by Russ Cox: https://go.dev/doc/asm (Goのアセンブリに関する公式ドキュメント)
- "A Quick Guide to Go's Assembler": https://go.dev/doc/asm
- x86/x86-64命令セットアーキテクチャに関する情報:
- Intel® 64 and IA-32 Architectures Software Developer’s Manuals: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html (特にVolume 2A/2B/2C: Instruction Set Reference, A-Z)
- AMD64 Architecture Programmer’s Manuals: https://developer.amd.com/resources/developer-guides-manuals/
- Clang静的解析に関する情報:
- Clang Static Analyzer: https://clang-analyzer.llvm.org/
- LLVM Project: https://llvm.org/
- オペコードテーブル (
optab
) の概念に関する一般的な情報 (Goリンカに特化したものではないが、概念理解に役立つ):- コンパイラやアセンブラの設計に関する書籍やオンラインリソース。
- GNU Binutils (特にGAS - GNU Assembler) のドキュメントも、アセンブラの内部動作を理解する上で参考になります。