[インデックス 143] ファイルの概要
このコミットは、Go言語の初期開発段階におけるコンパイラ(6g
)とリンカ(6l
)の内部的な変更を扱っています。特に、生成されるELF(Executable and Linkable Format)バイナリの構造と、浮動小数点数比較の処理方法に焦点を当てた修正が含まれています。
コミット
commit 36f21e00f5d054741a070974b3e583087d13ca86
Author: Ken Thompson <ken@golang.org>
Date: Tue Jun 10 12:42:37 2008 -0700
mode elf tossing
SVN=121962
---
src/cmd/6g/gsubr.c | 24 +++++-----\n src/cmd/6l/asm.c | 126 +++++++++++++++++++++++++++++++----------------------
2 files changed, 86 insertions(+), 64 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/36f21e00f5d054741a070974b3e583087d13ca86
元コミット内容
コミットメッセージは「mode elf tossing」と非常に簡潔です。これは、ELFバイナリの生成モードや構造に関する内部的な調整が行われたことを示唆しています。具体的には、src/cmd/6g/gsubr.c
とsrc/cmd/6l/asm.c
の2つのファイルが変更されており、6g
(Goコンパイラ)と6l
(Goリンカ)におけるELFファイルの取り扱いと、浮動小数点数比較のロジックに修正が加えられています。
変更の背景
このコミットは、Go言語がオープンソースとして公開される前の、非常に初期の段階(2008年)に行われたものです。当時のGoコンパイラとリンカは、現在のものとは大きく異なり、Plan 9のツールチェインをベースにしていました。6g
はx86-64アーキテクチャ向けのGoコンパイラ、6l
はそのリンカを指します。
「mode elf tossing」という表現は、ELFバイナリの内部構造、特にセグメントやセクションの配置、アライメント、フラグ設定などを「いじる」「調整する」といった意味合いで使われていると考えられます。初期のツールチェイン開発では、ターゲットOS(この場合はLinux)のバイナリフォーマット(ELF)に正確に準拠し、かつ効率的でセキュアなバイナリを生成するための試行錯誤が頻繁に行われます。
gsubr.c
における浮動小数点数比較の変更は、コンパイラが生成するアセンブリコードにおいて、浮動小数点数の等価性、不等価性、大小比較をどのように扱うかに関する調整です。浮動小数点数の比較は、NaN(Not a Number)の存在や符号付きゼロ(+0と-0)の扱いなど、整数とは異なる特性を持つため、正確なコード生成には細心の注意が必要です。
前提知識の解説
ELF (Executable and Linkable Format)
ELFは、Unix系OS(Linuxを含む)で広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準バイナリフォーマットです。ELFファイルは主に以下の要素で構成されます。
- ELFヘッダ: ファイルの種類(実行可能、オブジェクトなど)、ターゲットアーキテクチャ、エントリポイントなどの基本的な情報。
- プログラムヘッダテーブル (Program Header Table): リンカによって生成され、OSのローダーがプログラムをメモリにロードする方法を記述します。各エントリは「セグメント」を定義し、ファイル内のどの部分をメモリのどこにロードするか、そのパーミッション(読み取り、書き込み、実行)などを指定します。
- セクションヘッダテーブル (Section Header Table): コンパイラやアセンブラによって生成され、リンカがオブジェクトファイルを結合する際に使用します。コード(
.text
)、初期化済みデータ(.data
)、初期化されていないデータ(.bss
)、シンボルテーブル(.symtab
)、文字列テーブル(.strtab
)など、プログラムの論理的な構成要素である「セクション」を記述します。 - セグメントとセクション: セクションはリンカが扱う論理的な単位であり、セグメントはOSのローダーがメモリにロードする物理的な単位です。複数のセクションが1つのセグメントにまとめられることがよくあります(例:
.text
セクションは実行可能セグメントの一部となる)。
浮動小数点数比較
IEEE 754標準に準拠する浮動小数点数(float32
, float64
)の比較は、整数比較とは異なる振る舞いをします。特に重要なのは以下の点です。
- NaN (Not a Number):
NaN
は、不定な結果(例: 0/0、無限大-無限大)を表す特殊な値です。NaN
は自分自身を含むいかなる値とも比較してfalse
になります(NaN == NaN
もfalse
)。 - 符号付きゼロ:
+0.0
と-0.0
は異なる表現を持ちますが、通常は等しいと見なされます(+0.0 == -0.0
はtrue
)。 - 非順序 (Unordered): 比較対象のいずれかまたは両方が
NaN
である場合、比較結果は「非順序」となり、通常の大小関係(より小さい、より大きい、等しい)は適用されません。
コンパイラは、これらの特性を考慮して、適切なアセンブリ命令(例: x86のUCOMISS
/UCOMISD
命令とそれに続く条件分岐命令)を生成する必要があります。
Goツールチェインの初期構造
Go言語の初期のツールチェインは、Rob Pike、Ken Thompson、Robert Griesemerらが開発したPlan 9オペレーティングシステムのツールチェイン(8c
, 8l
など)から派生しました。6g
や6l
の「6」は、x86-64アーキテクチャを指すPlan 9の慣例に由来します。これらのツールは、Goのソースコードをコンパイルし、実行可能なバイナリを生成する役割を担っていました。
技術的詳細
src/cmd/6g/gsubr.c
の変更
このファイルは、6g
コンパイラのバックエンドの一部であり、Goの抽象構文木(AST)のノードをアセンブリ命令に変換する処理(またはその前段階の処理)に関連しています。変更点は、optoas
関数内のswitch
文における浮動小数点数型(TFLOAT32
, TFLOAT64
)の比較演算子(OEQ
, ONE
, OLT
, OLE
, OGT
, OGE
)のCASE
文の順序変更です。
具体的には、OEQ
(等しい)、ONE
(等しくない)のケースでは、浮動小数点数型がポインタ型(TPTR32
, TPTR64
)の後に移動しています。
OLT
(より小さい)、OLE
(以下)、OGT
(より大きい)、OGE
(以上)のケースでは、浮動小数点数型が整数型やポインタ型から完全に分離され、それぞれ対応する逆の比較演算子(例: OLT
のケースにOGE
の浮動小数点数型が追加)と共に配置されています。
これは、浮動小数点数比較が整数やポインタの比較とは異なる条件コードや分岐命令を使用する必要があるため、コード生成ロジックをより明確に分離し、正確性を向上させるためのリファクタリングである可能性が高いです。特に、非順序の概念があるため、浮動小数点数の「より小さい」や「より大きい」といった比較は、整数とは異なるアセンブリレベルの処理を必要とします。この変更は、これらの特殊なケースを適切に処理するための準備、または既存のバグ修正の一環と考えられます。
src/cmd/6l/asm.c
の変更
このファイルは、6l
リンカのELFバイナリ生成部分を扱っています。変更の核心は、ELFのプログラムヘッダとセクションヘッダの生成ロジックの調整です。
- 変数宣言の調整:
asmb
関数内で、vlong vl, va;
がvlong vl, va, fo, w;
に変更され、w
変数が追加されています。これは、ELF構造の計算に必要な新しい変数を導入したことを示します。 myseek
の導入:seek(cout, v, 0);
がmyseek(cout, v);
に置き換えられています。これは、ファイルシーク操作をラップするカスタム関数myseek
を導入したことを示唆しており、エラーハンドリングの強化や、特定のファイルシステム操作の抽象化を目的としている可能性があります。linuxphdr
の変更:- テキストセグメント:
file offset
がHEADR
から0
に、vaddr
とpaddr
がINITTEXT
からva
に、file size
とmemory size
がtextsize
からw
にそれぞれ変更されています。これは、セグメントのオフセットとサイズをより動的に計算し、ELFファイルのレイアウトを柔軟にするための変更です。 - データセグメント:
data - flags
が1L+2L+4L
(PF_X+PF_W+PF_R、実行可能+書き込み可能+読み取り可能)から2L+4L
(PF_W+PF_R、書き込み可能+読み取り可能)に変更されています。これは非常に重要な変更です。 データセグメントから実行可能フラグ(PF_X)を削除することで、データ領域からのコード実行を防ぎ、セキュリティを向上させます(W^Xポリシーの適用)。 - すべての
linuxphdr
呼び出しで、file offset
,vaddr
,paddr
,file size
,memory size
の引数が、固定値からfo
,va
,w
といった動的に計算される変数に置き換えられています。これにより、ELFファイルのセグメント配置がより柔軟かつ正確になります。
- テキストセグメント:
linuxshdr
の変更:.text
,.data
,.bss
,.shstrtab
セクションのaddr
、off
、size
の引数が、固定値からva
,fo
,w
といった動的に計算される変数に置き換えられています。これにより、セクションの配置もより柔軟になります。- すべてのセクションの
align
(アライメント)が4
から8
に変更されています。これは、64ビットシステムにおいて、メモリのアライメントを8バイトにすることで、パフォーマンスを向上させたり、特定のCPU命令の要件を満たしたりするためによく行われる変更です。 .gosymtab
セクションの生成がコメントアウトされています。これは、Goのシンボルテーブルの扱いが変更されたことを意味します。デバッグ情報の形式や格納方法が変更されたか、あるいは初期段階ではこのセクションが不要と判断された可能性があります。linuxheadr
とlinuxstrtable
からも関連する記述がコメントアウトされています。
これらの変更は、Goが生成するELFバイナリが、Linuxシステム上でより標準的かつセキュアに動作するようにするための、根本的な構造調整を示しています。特に、データセグメントの実行可能フラグの削除と、セクションのアライメントの変更は、現代のシステムにおけるバイナリのベストプラクティスに沿ったものです。
コアとなるコードの変更箇所
src/cmd/6g/gsubr.c
--- a/src/cmd/6g/gsubr.c
+++ b/src/cmd/6g/gsubr.c
@@ -1154,10 +1154,10 @@ optoas(int op, Type *t)
case CASE(OEQ, TUINT32):
case CASE(OEQ, TINT64):
case CASE(OEQ, TUINT64):
- case CASE(OEQ, TFLOAT32):
- case CASE(OEQ, TFLOAT64):
case CASE(OEQ, TPTR32):
case CASE(OEQ, TPTR64):
+ case CASE(OEQ, TFLOAT32):
+ case CASE(OEQ, TFLOAT64):
a = AJEQ;
break;
@@ -1170,10 +1170,10 @@ optoas(int op, Type *t)
case CASE(ONE, TUINT32):
case CASE(ONE, TINT64):
case CASE(ONE, TUINT64):
- case CASE(ONE, TFLOAT32):
- case CASE(ONE, TFLOAT64):
case CASE(ONE, TPTR32):
case CASE(ONE, TPTR64):
+ case CASE(ONE, TFLOAT32):
+ case CASE(ONE, TFLOAT64):
a = AJNE;
break;
@@ -1181,8 +1181,6 @@ optoas(int op, Type *t)
case CASE(OLT, TINT16):
case CASE(OLT, TINT32):
case CASE(OLT, TINT64):
- case CASE(OLT, TFLOAT32):
- case CASE(OLT, TFLOAT64):
a = AJLT;
break;
@@ -1190,6 +1188,8 @@ optoas(int op, Type *t)
case CASE(OLT, TUINT16):
case CASE(OLT, TUINT32):
case CASE(OLT, TUINT64):
+ case CASE(OGE, TFLOAT32):
+ case CASE(OGE, TFLOAT64):
a = AJCS;
break;
@@ -1197,8 +1197,6 @@ optoas(int op, Type *t)
case CASE(OLE, TINT16):
case CASE(OLE, TINT32):
case CASE(OLE, TINT64):
- case CASE(OLE, TFLOAT32):
- case CASE(OLE, TFLOAT64):
a = AJLE;
break;
@@ -1206,6 +1204,8 @@ optoas(int op, Type *t)
case CASE(OLE, TUINT16):
case CASE(OLE, TUINT32):
case CASE(OLE, TUINT64):
+ case CASE(OGT, TFLOAT32):
+ case CASE(OGT, TFLOAT64):
a = AJLS;
break;
@@ -1213,8 +1213,6 @@ optoas(int op, Type *t)
case CASE(OGT, TINT16):
case CASE(OGT, TINT32):
case CASE(OGT, TINT64):
- case CASE(OGT, TFLOAT32):
- case CASE(OGT, TFLOAT64):
a = AJGT;
break;
@@ -1222,6 +1220,8 @@ optoas(int op, Type *t)
case CASE(OGT, TUINT16):
case CASE(OGT, TUINT32):
case CASE(OGT, TUINT64):
+ case CASE(OLE, TFLOAT32):
+ case CASE(OLE, TFLOAT64):
a = AJHI;
break;
@@ -1229,8 +1229,6 @@ optoas(int op, Type *t)
case CASE(OGE, TINT16):
case CASE(OGE, TINT32):
case CASE(OGE, TINT64):
- case CASE(OGE, TFLOAT32):
- case CASE(OGE, TFLOAT64):
a = AJGE;
break;
@@ -1238,6 +1236,8 @@ optoas(int op, Type *t)
case CASE(OGE, TUINT16):
case CASE(OGE, TUINT32):
case CASE(OGE, TUINT64):
+ case CASE(OLT, TFLOAT32):
+ case CASE(OLT, TFLOAT64):
a = AJCC;
break;
src/cmd/6l/asm.c
--- a/src/cmd/6l/asm.c
+++ b/src/cmd/6l/asm.c
@@ -120,10 +120,10 @@ void
asmb(void)
{
Prog *p;
- long v, magic, w;
+ long v, magic;
int a;
uchar *op1;
- vlong vl, va;
+ vlong vl, va, fo, w;
if(debug['v'])
Bprint(&bso, "%5.2f asmb\n", cputime());
@@ -166,6 +166,8 @@ asmb(void)
cbc -= a;
}
cflush();
+
+
switch(HEADTYPE) {
default:
diag("unknown header type %ld", HEADTYPE);
@@ -183,9 +185,10 @@ asmb(void)
}
cflush();
break;
+
case 7:
v = rnd(HEADR+textsize, INITRND);
- seek(cout, v, 0);
+ myseek(cout, v);
break;
}
@@ -402,23 +405,31 @@ debug['s'] = 1;
wputl(5); /* # of Shdrs */
wputl(4); /* Shdr with strings */
+fo = 0;
+va = INITRND;
+w = HEADR+textsize;
+
+
linuxphdr(1, /* text - type = PT_LOAD */
1L+4L, /* text - flags = PF_X+PF_R */
- HEADR, /* file offset */
- INITTEXT, /* vaddr */
- INITTEXT, /* paddr */
- textsize, /* file size */
- textsize, /* memory size */
+ 0, /* file offset */
+ va, /* vaddr */
+ va, /* paddr */
+ w, /* file size */
+ w, /* memory size */
INITRND); /* alignment */
- v = rnd(HEADR+textsize, INITRND);
+fo = rnd(fo+w, INITRND);
+va = rnd(va+w, INITRND);
+w = datsize;
+
linuxphdr(1, /* data - type = PT_LOAD */
- 1L+2L+4L, /* data - flags = PF_X+PF_W+PF_R */
- v, /* file offset */
- INITDAT, /* vaddr */
- INITDAT, /* paddr */
- datsize, /* file size */
- datsize+bsssize, /* memory size */
+ 2L+4L, /* data - flags = PF_W+PF_R */
+ fo, /* file offset */
+ va, /* vaddr */
+ va, /* paddr */
+ w, /* file size */
+ w+bsssize, /* memory size */
INITRND); /* alignment */
linuxphdr(0x6474e551, /* gok - type = gok */
@@ -441,69 +452,80 @@ debug['s'] = 1;
0, /* align */
0); /* entsize */
- stroffset = 1;
- v = HEADR;
+stroffset = 1;
+fo = 0;
+va = INITRND;
+w = HEADR+textsize;
+
linuxshdr(".text", /* name */
1, /* type */
6, /* flags */
- INITTEXT, /* addr */
- v, /* off */
- textsize, /* size */
+ va, /* addr */
+ fo, /* off */
+ w, /* size */
0, /* link */
0, /* info */
- 4, /* align */
+ 8, /* align */
0); /* entsize */
- v += textsize;
+fo = rnd(fo+w, INITRND);
+va = rnd(va+w, INITRND);
+w = datsize;
+
linuxshdr(".data", /* name */
1, /* type */
3, /* flags */
- INITDAT, /* addr */
- v, /* off */
- datsize, /* size */
+ va, /* addr */
+ fo, /* off */
+ w, /* size */
0, /* link */
0, /* info */
- 4, /* align */
+ 8, /* align */
0); /* entsize */
- v += datsize;
+fo += w;
+va += w;
+w = bsssize;
+
linuxshdr(".bss", /* name */
8, /* type */
3, /* flags */
- INITDAT, /* addr */
- v, /* off */
- bsssize, /* size */
+ va, /* addr */
+ fo, /* off */
+ w, /* size */
0, /* link */
0, /* info */
- 4, /* align */
+ 8, /* align */
0); /* entsize */
- v += 0;
- va = stroffset +
- strlen(".shstrtab")+1 +
- strlen(".gosymtab")+1;
+fo = HEADR+textsize+datsize;
+w = stroffset +
+ strlen(".shstrtab")+1;
+// strlen(".gosymtab")+1;
+
linuxshdr(".shstrtab", /* name */
3, /* type */
0, /* flags */
0, /* addr */
- v, /* off */
- va, /* size */
+ fo, /* off */
+ w, /* size */
0, /* link */
0, /* info */
- 4, /* align */
+ 8, /* align */
0); /* entsize */
- v += va;
- linuxshdr(".gosymtab", /* name */
- 2, /* type */
- 0, /* flags */
- 0, /* addr */
- v, /* off */
- 0, /* size */
- 0, /* link */
- 0, /* info */
- 4, /* align */
- 0); /* entsize */
+//fo += w;
+//
+// linuxshdr(".gosymtab", /* name */
+// 2, /* type */
+// 0, /* flags */
+// 0, /* addr */
+// fo, /* off */
+// 0, /* size */
+// 0, /* link */
+// 0, /* info */
+// 8, /* align */
+// 0); /* entsize */
break;
}
cflush();
@@ -775,7 +797,7 @@ linuxheadr(void)
a += 64; /* .data seg */
a += 64; /* .bss sect */
a += 64; /* .shstrtab sect - strings for headers */
- a += 64; /* .gosymtab sect */
+// a += 64; /* .gosymtab sect */
return a;
}
@@ -831,6 +853,6 @@ linuxstrtable(void)
strnput(name, strlen(name)+1);
name = ".shstrtab";
strnput(name, strlen(name)+1);
- name = ".gosymtab";
- strnput(name, strlen(name)+1);
+// name = ".gosymtab";
+// strnput(name, strlen(name)+1);
}
コアとなるコードの解説
src/cmd/6g/gsubr.c
の変更点
optoas
関数は、Goの内部表現における演算子(op
)と型(t
)の組み合わせに基づいて、対応するアセンブリ命令(a
)を決定する役割を担っています。この変更は、浮動小数点数型(TFLOAT32
, TFLOAT64
)の比較演算子(OEQ
, ONE
, OLT
, OLE
, OGT
, OGE
)のCASE
文の順序を調整しています。
OEQ
(Equal) とONE
(Not Equal): 浮動小数点数型の比較が、整数型やポインタ型の後に移動しました。これは、浮動小数点数の等価性・不等価性比較が、NaNの特殊な振る舞いを考慮する必要があるため、他の型とは異なる処理パスを暗示している可能性があります。OLT
(Less Than),OLE
(Less Than or Equal),OGT
(Greater Than),OGE
(Greater Than or Equal): これらの比較演算子では、浮動小数点数型が整数型やポインタ型から完全に分離され、それぞれ対応する逆の比較演算子(例:OLT
のケースにOGE
の浮動小数点数型が追加)と共に配置されています。これは、浮動小数点数の大小比較が、NaNの存在により「非順序」の状態を考慮する必要があるため、より複雑な条件分岐ロジックや、特定の浮動小数点比較命令(例:UCOMISS
/UCOMISD
)とそれに続く条件コードの評価が必要となることを示唆しています。この再配置は、コンパイラがより正確で効率的な浮動小数点数比較のアセンブリコードを生成するための、内部的なロジックの整理と最適化の一環と考えられます。
src/cmd/6l/asm.c
の変更点
このファイルは、GoリンカがELFバイナリを生成する際の低レベルな処理を定義しています。
-
動的なオフセットとサイズ計算:
linuxphdr
およびlinuxshdr
関数への引数として、HEADR
,INITTEXT
,INITDAT
,textsize
,datsize
,bsssize
といった固定値の代わりに、fo
,va
,w
といった動的に計算される変数が導入されました。fo
(file offset),va
(virtual address),w
(size) は、ELFファイルのセグメントやセクションの配置をより柔軟かつ正確に制御するために使用されます。これにより、バイナリのレイアウトがより最適化され、将来的な変更にも対応しやすくなります。
-
データセグメントの実行可能フラグ削除:
- データセグメントの
linuxphdr
呼び出しにおいて、data - flags
が1L+2L+4L
(PF_X+PF_W+PF_R)から2L+4L
(PF_W+PF_R)に変更されました。 PF_X
(実行可能フラグ)が削除されたことは、データ領域からのコード実行を防ぐための重要なセキュリティ強化です。これはW^X(Write XOR Execute)ポリシーとして知られ、現代のOSにおけるセキュリティの基本原則の一つです。これにより、バッファオーバーフローなどの脆弱性を悪用したコードインジェクション攻撃のリスクが低減されます。
- データセグメントの
-
セクションのアライメント変更:
.text
,.data
,.bss
,.shstrtab
セクションのalign
が4
から8
に変更されました。- これは、64ビットアーキテクチャにおいて、メモリのアライメントを8バイトにすることで、CPUのキャッシュ効率を向上させたり、特定のSIMD命令などのアライメント要件を満たしたりするためによく行われる最適化です。これにより、生成されるバイナリの実行性能が向上する可能性があります。
-
.gosymtab
セクションのコメントアウト:.gosymtab
セクションの生成に関するコードが、linuxshdr
,linuxheadr
,linuxstrtable
の各関数でコメントアウトされました。- これは、Goのシンボルテーブルの扱いが変更されたことを意味します。初期のGoツールチェインでは、デバッグ情報やシンボル情報をELFのカスタムセクションとして格納していた可能性がありますが、この変更により、そのアプローチが変更されたか、あるいはより洗練されたデバッグ情報形式(例: DWARF)への移行の準備であった可能性が考えられます。あるいは、単にこの時点ではこのセクションが不要と判断されたのかもしれません。
これらの変更は、Go言語のツールチェインが、より堅牢でセキュア、かつ効率的なELFバイナリを生成するための基盤を構築する上で重要なステップであったことを示しています。
関連リンク
- Go言語の歴史 - Wikipedia
- ELF (Executable and Linkable Format) - Wikipedia
- IEEE 754 - Wikipedia
- W^X - Wikipedia
参考にした情報源リンク
- Go Source Code - GitHub (特に初期のコミット履歴)
- Plan 9 from Bell Labs - Wikipedia (Goツールチェインのルーツに関する情報)
- ELFファイルフォーマットに関する一般的な技術文書やチュートリアル
- 浮動小数点数比較に関するプログラミング言語やアセンブリのドキュメント