[インデックス 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++における動的リンクとは異なる文脈で使われることがあります。
LinkExternal
とLinkInternal
: 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']
(デバッグモードでない) の場合に、linkmode
が LinkInternal
であっても .tbss
セクションが生成されるようになります。debug['d']
はデバッグシンボルを生成しないオプションに関連していると考えられます。
.tbss
セクションは、SHT_NOBITS
タイプ(ファイル内に実体を持たず、実行時にゼロ初期化される)で、SHF_ALLOC
(メモリにロードされる)、SHF_TLS
(TLSセクションである)、SHF_WRITE
(書き込み可能) のフラグが設定されます。sh->size
は -tlsoffset
となっており、これはTLS領域のオフセットに基づいてサイズが計算されることを示唆しています。
コアとなるコードの変更箇所
src/cmd/ld/elf.c
ファイルの doelf
関数と elfobj
関数に修正が加えられています。
-
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
を追加する条件を修正しています。 -
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
のドキュメント (特にreadelf
やobjdump
の出力に関する情報)
参考にした情報源リンク
- Go言語のソースコード (
src/cmd/ld/elf.c
) - ELFファイルフォーマットに関する一般的な情報源 (例: Wikipedia, Linux man pages)
- スレッドローカルストレージに関する一般的な情報源
- Go issue #5200 (ただし、Web検索では直接的な情報が見つからなかったため、コミットメッセージからの推測に基づく)