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

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

コミット

このコミットは、Go言語のリンカ (cmd/ld) において、動的内部リンク時に .tbss セクションを出力するように変更を加えるものです。これにより、binutils などのツールが PT_TLS (Thread Local Storage Program Header) のサイズを正しく計算できるようになり、関連する問題(Go issue #5200)が修正されます。

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

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

元コミット内容

commit a8484753242a47ba43786395315a9edf0d8de
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Sat May 18 02:41:49 2013 +0800

    cmd/ld: emit .tbss section when doing dynamic internal linking
    Fixes #5200.
    
    R=iant, dave
    CC=golang-dev
    https://golang.org/cl/9383043

変更の背景

この変更は、Go言語のリンカが生成する実行ファイルにおいて、スレッドローカルストレージ (TLS) に関連するセクションの扱いを改善することを目的としています。特に、動的内部リンク(Goランタイムが自身のリンカを使用して動的にライブラリをロードするシナリオ)の場合に、.tbss セクションが適切に生成されないことが問題となっていました。

.tbss (Thread BSS) セクションは、初期化されていないスレッドローカル変数を格納するために使用されます。これらの変数は、プログラムの実行開始時にはゼロで初期化されることが期待されます。ELF (Executable and Linkable Format) 形式の実行ファイルにおいて、TLSセクションのサイズ情報は PT_TLS プログラムヘッダに格納されます。binutils のような標準的なツールチェーンのユーティリティは、この PT_TLS の情報に基づいてTLS領域のサイズを計算します。

Goリンカが動的内部リンク時に.tbssセクションを適切に生成しないと、PT_TLSのサイズが誤って計算され、結果としてTLS変数のアクセスや初期化に問題が生じる可能性がありました。Go issue #5200 はこの問題に対処するためのものであり、このコミットはその修正を実装しています。

前提知識の解説

  • リンカ (Linker): コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含む)を結合し、実行可能なプログラムやライブラリを作成するツールです。シンボル解決(関数や変数の定義と参照を結びつける)や、セクションの配置などを行います。
  • ELF (Executable and Linkable Format): Unix系システムで広く使われている実行ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。プログラムのコード、データ、シンボル情報、デバッグ情報などがセクションやセグメントとして格納されます。
  • セクション (Section): ELFファイル内の論理的なデータブロックです。例えば、.text セクションは実行可能なコードを、.data セクションは初期化されたデータを、.bss セクションは初期化されていないデータを格納します。
  • .tbss セクション (Thread BSS): スレッドローカルストレージ (TLS) のうち、初期化されていないデータを格納するためのセクションです。各スレッドが独自のコピーを持つ変数がここに配置されます。
  • スレッドローカルストレージ (TLS: Thread Local Storage): マルチスレッドプログラミングにおいて、各スレッドが独立した変数を持つための仕組みです。これにより、グローバル変数や静的変数をスレッド間で共有することなく、スレッドごとに異なる値を保持できます。
  • PT_TLS プログラムヘッダ: ELFファイルのプログラムヘッダの一つで、スレッドローカルストレージの初期化に関する情報(TLS領域のサイズ、アライメントなど)を記述します。リンカやローダーがTLS領域を適切に設定するために使用します。
  • binutils: GNUプロジェクトが提供するバイナリユーティリティの集合体です。アセンブラ (as)、リンカ (ld)、オブジェクトファイル操作ツール (objdump, readelf) などが含まれ、ELFファイルを解析したり操作したりするために広く利用されます。
  • 動的内部リンク (Dynamic Internal Linking): Go言語特有の概念で、Goランタイム自身がプログラムの実行中に動的にコードをロード・リンクするメカニズムを指します。これは、C/C++における動的リンクとは異なる文脈で使われることがあります。
  • LinkExternalLinkInternal: Goリンカのリンクモードです。
    • LinkExternal: 外部リンカ(通常はGCCなど)を使用して最終的な実行ファイルを生成するモードです。この場合、Goリンカはオブジェクトファイルを生成し、外部リンカに渡します。
    • LinkInternal: Goリンカ自身が最終的な実行ファイルを生成するモードです。

技術的詳細

このコミットの主要な目的は、GoリンカがELF形式の実行ファイルを生成する際に、スレッドローカルストレージ (TLS) の情報を正しく扱うことです。具体的には、動的内部リンク (linkmode == LinkInternal) の場合に、.tbss セクションがELFファイルのセクションヘッダテーブルに適切に追加されるようにします。

以前のコードでは、.tbss セクションの追加は LinkExternal モードの場合に限定されていました(かつ、OpenBSDを除く)。しかし、PT_TLS プログラムヘッダのサイズを正確に計算するためには、動的内部リンクの場合でも .tbss セクションの情報が必要となります。binutils のようなツールは、ELFファイルのセクション情報(特にTLS関連のセクション)を参照して PT_TLS のサイズを決定するため、この情報が欠落していると問題が発生します。

この変更により、HEADTYPE != Hopenbsd (OpenBSD以外のシステム) かつ !debug['d'] (デバッグモードでない) の場合に、linkmodeLinkInternal であっても .tbss セクションが生成されるようになります。debug['d'] はデバッグシンボルを生成しないオプションに関連していると考えられます。

.tbss セクションは、SHT_NOBITS タイプ(ファイル内に実体を持たず、実行時にゼロ初期化される)で、SHF_ALLOC (メモリにロードされる)、SHF_TLS (TLSセクションである)、SHF_WRITE (書き込み可能) のフラグが設定されます。sh->size-tlsoffset となっており、これはTLS領域のオフセットに基づいてサイズが計算されることを示唆しています。

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

src/cmd/ld/elf.c ファイルの doelf 関数と elfobj 関数に修正が加えられています。

  1. doelf 関数内 (addstring の呼び出し箇所):

    --- a/src/cmd/ld/elf.c
    +++ b/src/cmd/ld/elf.c
    @@ -887,7 +887,12 @@ doelf(void)
     	addstring(shstrtab, ".data");
     	addstring(shstrtab, ".bss");
     	addstring(shstrtab, ".noptrbss");
    -	if(linkmode == LinkExternal && HEADTYPE != Hopenbsd)
    +	// generate .tbss section (except for OpenBSD where it's not supported)
    +	// for dynamic internal linker or external linking, so that various
    +	// binutils could correctly calculate PT_TLS size.
    +	// see http://golang.org/issue/5200.
    +	if(HEADTYPE != Hopenbsd)
    +	if(!debug['d'] || linkmode == LinkExternal)
     		addstring(shstrtab, ".tbss");
     	if(HEADTYPE == Hnetbsd)
     		addstring(shstrtab, ".note.netbsd.ident");
    

    この変更は、セクション名文字列テーブル (shstrtab) に .tbss を追加する条件を修正しています。

  2. elfobj 関数内 (新しいコードブロックの追加):

    --- a/src/cmd/ld/elf.c
    +++ b/src/cmd/ld/elf.c
    @@ -1412,6 +1417,16 @@ elfobj:
     		sh->flags = 0;
     	}
     
    +\t// generate .tbss section for dynamic internal linking (except for OpenBSD)
    +\t// external linking generates .tbss in data.c
    +\tif(linkmode == LinkInternal && !debug['d'] && HEADTYPE != Hopenbsd) {
    +\t\tsh = elfshname(".tbss");
    +\t\tsh->type = SHT_NOBITS;
    +\t\tsh->addralign = PtrSize;
    +\t\tsh->size = -tlsoffset;
    +\t\tsh->flags = SHF_ALLOC | SHF_TLS | SHF_WRITE;
    +\t}
    +\n \tif(!debug['s']) {
     	\tsh = elfshname(".symtab");
     	\tsh->type = SHT_SYMTAB;
    

    この新しいコードブロックは、linkmode == LinkInternal の場合に .tbss セクションのセクションヘッダエントリを実際に生成する部分です。

コアとなるコードの解説

doelf 関数内の変更

元のコードでは、.tbss セクション名を shstrtab (セクション名文字列テーブル) に追加する条件が linkmode == LinkExternal && HEADTYPE != Hopenbsd でした。これは、外部リンカを使用する場合にのみ .tbss セクションの存在をリンカに知らせていたことを意味します。

変更後のコードでは、条件が HEADTYPE != Hopenbsd かつ (!debug['d'] || linkmode == LinkExternal) となっています。

  • HEADTYPE != Hopenbsd: OpenBSDではTLSの扱いが異なるため、引き続き除外されます。
  • !debug['d']: デバッグシンボルを生成しない場合。
  • linkmode == LinkExternal: 外部リンカを使用する場合。

この論理OR (||) により、デバッグモードでなく、かつ動的内部リンク (linkmode == LinkInternal) の場合でも .tbss セクション名が追加されるようになりました。これにより、リンカが .tbss セクションを認識し、後続の処理でそのセクションヘッダを生成できるようになります。

elfobj 関数内の変更

この新しいコードブロックは、実際に .tbss セクションのセクションヘッダエントリを構築します。

  • if(linkmode == LinkInternal && !debug['d'] && HEADTYPE != Hopenbsd): この条件は、動的内部リンクであり、デバッグモードでなく、かつOpenBSD以外のシステムである場合にのみ、以下の処理を実行することを示します。
  • sh = elfshname(".tbss");: .tbss という名前のセクションヘッダを取得または作成します。
  • sh->type = SHT_NOBITS;: セクションのタイプを SHT_NOBITS に設定します。これは、セクションがファイル内に実際のデータを持たず、実行時にメモリ上でゼロ初期化されることを意味します。TLSのBSSセクションは通常このタイプです。
  • sh->addralign = PtrSize;: セクションのアライメントを設定します。PtrSize はポインタのサイズ(32ビットシステムなら4バイト、64ビットシステムなら8バイト)を意味し、TLS変数が適切にアライメントされるようにします。
  • sh->size = -tlsoffset;: セクションのサイズを設定します。-tlsoffset は、TLS領域のオフセットに基づいて計算されるサイズを示唆しており、TLS領域の全体的なサイズ計算に寄与します。
  • sh->flags = SHF_ALLOC | SHF_TLS | SHF_WRITE;: セクションのフラグを設定します。
    • SHF_ALLOC: プログラム実行時にメモリにロードされるセクションであることを示します。
    • SHF_TLS: このセクションがスレッドローカルストレージに関連するものであることを示します。
    • SHF_WRITE: このセクションが書き込み可能であることを示します。

この変更により、動的内部リンクの場合でも .tbss セクションがELFファイルに適切に記述されるようになり、binutils などのツールが PT_TLS のサイズを正確に計算できるようになります。

関連リンク

  • Go言語のリンカに関するドキュメント (公式ドキュメントやソースコード内のコメントを参照)
  • ELFファイルフォーマットの仕様 (特にセクションヘッダ、プログラムヘッダ、TLS関連のセクションについて)
  • binutils のドキュメント (特に readelfobjdump の出力に関する情報)

参考にした情報源リンク

  • Go言語のソースコード (src/cmd/ld/elf.c)
  • ELFファイルフォーマットに関する一般的な情報源 (例: Wikipedia, Linux man pages)
  • スレッドローカルストレージに関する一般的な情報源
  • Go issue #5200 (ただし、Web検索では直接的な情報が見つからなかったため、コミットメッセージからの推測に基づく)