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

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

このコミットは、Go言語のリンカ(cmd/5l, cmd/6l, cmd/8l)において、GCCでコンパイルされたソースコードから生成される関数シンボルの扱いに関するバグを修正するものです。具体的には、動的シンボルテーブルにエクスポートされるシンボルのタイプを決定する際のロジックが改善されています。

コミット

commit c0927a6797e0b21775fcc5181698574da638a205
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Tue Dec 18 23:17:39 2012 +0800

    cmd/5l, cmd/6l, cmd/8l: fix function symbol generation from gcc compiled source code
    For CL 6853059.
    
    R=jsing, rsc
    CC=golang-dev
    https://golang.org/cl/6938076

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

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

元コミット内容

cmd/5l, cmd/6l, cmd/8l: fix function symbol generation from gcc compiled source codecmd/5l, cmd/6l, cmd/8l: GCCでコンパイルされたソースコードからの関数シンボル生成を修正) For CL 6853059. (CL 6853059のため。)

変更の背景

このコミットは、Go言語のリンカがGCCでコンパイルされたC/C++コードなど、外部のオブジェクトファイルとリンクする際に発生していた問題に対処しています。Goプログラムは、Cgoを通じてC/C++コードを呼び出すことができ、その際にはGCCなどの外部コンパイラによって生成されたオブジェクトファイルがGoのリンカによって処理されます。

問題は、リンカがシンボルの種類(関数か、オブジェクト(データ)か)を正しく識別できない場合に発生していました。特に、シンボルのtypeフィールドが、シンボルの基本的な種類(テキスト、データなど)だけでなく、追加のフラグ情報も含むことがあり、単純なs->type == STEXTという比較では、関数シンボルであるにもかかわらず正しく認識されないケースがありました。これにより、動的リンキングの際に、関数シンボルが誤ってデータシンボルとして扱われ、プログラムの実行時に問題を引き起こす可能性がありました。

この修正は、GoのIssue 4400に関連している可能性が高いです。Issue 4400では、go build -ldflags="-linkmode external" を使用して外部リンカ(gcc)を使用した場合に、_cgo_init などのシンボルが STT_FUNC ではなく STT_OBJECT としてエクスポートされる問題が報告されていました。これは、Goのリンカがシンボルタイプを誤って解釈していることに起因していました。

前提知識の解説

  • Goリンカ (cmd/5l, cmd/6l, cmd/8l): Go言語のビルドシステムの一部であり、Goのソースコードや外部のオブジェクトファイルを結合して実行可能ファイルを生成する役割を担います。5lはARMアーキテクチャ、6lはx86-64アーキテクチャ、8lはx86アーキテクチャに対応するリンカです。
  • シンボル (Symbol): プログラム内の関数、変数、ラベルなどの名前付きエンティティを表すものです。リンカはこれらのシンボルを解決し、適切なメモリアドレスに配置します。
  • 動的リンキング (Dynamic Linking): プログラムの実行時に共有ライブラリ(DLLや.soファイル)をロードし、リンクする仕組みです。これにより、実行可能ファイルのサイズを小さくし、複数のプログラムで同じライブラリを共有できます。
  • ELF (Executable and Linkable Format): Unix系システムで広く使われている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。シンボル情報もELFフォーマットの一部として格納されます。
  • シンボルテーブル (Symbol Table): オブジェクトファイルや実行可能ファイル内に含まれる、シンボルとその属性(名前、アドレス、種類など)を記述したデータ構造です。
  • 動的シンボルテーブル (.dynsym): 動的リンキングに必要なシンボル情報のみを含むシンボルテーブルです。
  • STB_GLOBAL: シンボルの結合属性(binding)の一つで、シンボルがグローバルに可視であることを示します。
  • STT_FUNC: シンボルの種類(type)の一つで、シンボルが関数であることを示します。
  • STT_OBJECT: シンボルの種類の一つで、シンボルがデータオブジェクト(変数など)であることを示します。
  • Sym構造体: Goリンカ内部でシンボル情報を表現するために使用されるデータ構造です。typeフィールドはシンボルの種類を示します。
  • STEXT: Goリンカ内部で定義されているシンボルタイプの一つで、テキストセクション(コード)内のシンボル、つまり関数を表します。
  • SMASK: シンボルタイプからフラグ情報をマスクするためのビットマスクです。シンボルタイプフィールドが、基本的なタイプ情報と追加のフラグ情報をビット単位で結合して保持している場合に、純粋なタイプ情報のみを抽出するために使用されます。

技術的詳細

この修正の核心は、シンボルのtypeフィールドの解釈方法にあります。Goリンカの内部では、Sym構造体のtypeフィールドがシンボルの種類を保持しています。従来、このフィールドがSTEXT(テキストセクション、つまり関数)であるかどうかを直接比較していました。

しかし、s->typeフィールドは、シンボルの基本的な種類(STEXT, SDATAなど)だけでなく、シンボルの属性や状態を示す追加のビットフラグも含むことがあります。例えば、シンボルが外部から参照されるべきか、特定のセクションに属するか、などの情報が同じtypeフィールドにパックされている場合があります。

STEXTは、純粋なシンボルタイプを示す定数です。もしs->typeSTEXT以外のビットも立てている場合、s->type == STEXTという比較はfalseとなり、たとえそのシンボルが本質的に関数(テキストセクション)のものであっても、正しく関数として認識されませんでした。

この問題を解決するために、s->type & SMASKという操作が導入されました。SMASKは、s->typeフィールドから純粋なシンボルタイプ情報のみを抽出するためのビットマスクです。このマスクを適用することで、s->typeに含まれる余分なフラグビットを無視し、シンボルの基本的な種類だけをSTEXTと比較できるようになります。

つまり、s->dynexport && (s->type & SMASK) == STEXTという条件は、「シンボルが動的にエクスポートされるべきであり、かつ、そのシンボルの基本的な種類が関数(テキストセクション)である」ということを正確に判断できるようになります。これにより、GCCでコンパイルされたコードから生成された関数シンボルが、Goのリンカによって正しくSTT_FUNCとして扱われるようになります。

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

変更は、src/cmd/5l/asm.c, src/cmd/6l/asm.c, src/cmd/8l/asm.c の3つのファイルに共通して行われています。それぞれのファイルの adddynsym 関数内の以下の行が変更されています。

--- a/src/cmd/5l/asm.c
+++ b/src/cmd/5l/asm.c
@@ -450,7 +450,7 @@ adddynsym(Sym *s)
 
 		/* type */
 		t = STB_GLOBAL << 4;
-		if(s->dynexport && s->type == STEXT)
+		if(s->dynexport && (s->type&SMASK) == STEXT)
 			t |= STT_FUNC;
 		else
 			t |= STT_OBJECT;

同様の変更が src/cmd/6l/asm.csrc/cmd/8l/asm.c にも適用されています。

コアとなるコードの解説

変更されたコードは、adddynsym 関数内にあります。この関数は、動的シンボルテーブル(.dynsym)にシンボルを追加する際に、そのシンボルのELFタイプ(STT_FUNCまたはSTT_OBJECT)を決定するロジックを含んでいます。

元のコード:

if(s->dynexport && s->type == STEXT)
    t |= STT_FUNC;
else
    t |= STT_OBJECT;

このコードでは、シンボルsが動的にエクスポートされるべき(s->dynexportが真)であり、かつ、そのシンボルのタイプが厳密にSTEXTである場合に、そのシンボルを関数(STT_FUNC)としてマークしていました。それ以外の場合は、オブジェクト(STT_OBJECT)としてマークしていました。

修正後のコード:

if(s->dynexport && (s->type&SMASK) == STEXT)
    t |= STT_FUNC;
else
    t |= STT_OBJECT;

修正後では、s->type == STEXTの部分が (s->type&SMASK) == STEXT に変更されています。

  • s->type: シンボルの種類と追加のフラグを含むフィールド。
  • SMASK: シンボルの種類を示すビットのみを抽出するためのマスク。
  • s->type & SMASK: s->typeからSMASKで指定されたビット(純粋なシンボルタイプ情報)のみを抽出するビット演算。

この変更により、s->typeフィールドにSTEXT以外のフラグビットが含まれていても、そのシンボルが本質的に関数であるならば、正しくSTT_FUNCとして識別されるようになりました。これにより、GCCでコンパイルされたソースコードから生成された関数シンボルが、Goのリンカによって適切に処理され、動的リンキングの際に正しいシンボルタイプが設定されるようになります。

関連リンク

参考にした情報源リンク

  • ELF Specification (Symbol Table Entry): https://docs.oracle.com/cd/E19683-01/817-3677/6m74b2000/index.html (一般的なELFシンボルテーブルの構造理解のため)
  • Go言語のリンカのソースコード (GoのGitHubリポジトリ): https://github.com/golang/go (Goリンカの内部構造や定数定義の確認のため)
  • GCCのコンパイルとリンキングに関するドキュメント (一般的な知識のため)
  • Cgoに関するGoのドキュメント (GoとC/C++の連携理解のため)
  • Go言語のIssueトラッカー (関連するバグ報告の確認のため)