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

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

このコミットは、Go言語のリンカ(ld)における読み取り専用データセクション(.rodata)のアライメント(メモリ配置の整列)に関するバグを修正するものです。具体的には、コードセクションのサイズが適切にアライメントされていなかったため、その後に続く.rodataセクションが任意にずれた位置に配置される可能性があった問題を解決します。

コミット

commit 15d8b05f0ca604e40ba42a3e9f6d30b1a280d1d8
Author: Russ Cox <rsc@golang.org>
Date:   Thu Feb 23 23:01:36 2012 -0500

    ld: fix alignment of rodata section
    
    We were not aligning the code size,
    so read-only data, which follows in the same
    segment, could be arbitrarily misaligned.
    
    Fixes #2506.
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/5693055
---
 src/cmd/ld/data.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/cmd/ld/data.c b/src/cmd/ld/data.c
index 397ae83b23..786c10b64d 100644
--- a/src/cmd/ld/data.c
+++ b/src/cmd/ld/data.c
@@ -1023,6 +1023,11 @@ textaddress(void)\n 		}\n 		va += sym->size;\n 	}\n+\t\n+\t// Align end of code so that rodata starts aligned.\n+\t// 128 bytes is likely overkill but definitely cheap.\n+\tva = rnd(va, 128);\n+\n \tsect->len = va - sect->vaddr;\n }\n \n```

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

[https://github.com/golang/go/commit/15d8b05f0ca604e40ba42a3e9f6d30b1a280d1d8](https://github.com/golang/go/commit/15d8b05f0ca604e40ba42a3e9f6d30b1a280d1d8)

## 元コミット内容

ld: fix alignment of rodata section

We were not aligning the code size, so read-only data, which follows in the same segment, could be arbitrarily misaligned.

Fixes #2506.

R=golang-dev, iant CC=golang-dev https://golang.org/cl/5693055


## 変更の背景

このコミットは、Go言語のリンカ(`ld`)が生成する実行ファイルにおいて、コードセクション(`.text`)と読み取り専用データセクション(`.rodata`)のメモリ配置に問題があったために行われました。

リンカは、コンパイルされたオブジェクトファイルから最終的な実行ファイルを生成する際に、様々なセクション(コード、データ、読み取り専用データなど)をメモリ上に配置します。この際、特定のセクションやデータ型は、パフォーマンス上の理由やハードウェアの要件から、特定のバイト境界に整列(アライメント)される必要があります。例えば、多くのアーキテクチャでは、4バイトの整数は4バイト境界に、8バイトの浮動小数点数は8バイト境界に配置されることが期待されます。

元のリンカの実装では、コードセクションの末尾が適切にアライメントされていませんでした。その結果、同じメモリセグメント内にコードセクションの直後に配置される読み取り専用データセクション(`.rodata`)が、必要なアライメントを満たさない「ずれた」アドレスに配置される可能性がありました。このようなミスアライメントは、以下のような問題を引き起こす可能性があります。

*   **パフォーマンスの低下**: CPUがアライメントされていないデータにアクセスする場合、追加のサイクルが必要になったり、キャッシュミスが増加したりして、プログラムの実行速度が低下することがあります。
*   **クラッシュや未定義の動作**: 一部のアーキテクチャでは、アライメントされていないメモリアクセスがハードウェア例外(バスエラーやアライメントフォルト)を引き起こし、プログラムがクラッシュする原因となります。特に、特定の命令(例: SIMD命令)は厳密なアライメントを要求することがあります。
*   **デバッグの困難さ**: メモリレイアウトが予測不能になることで、デバッグが非常に困難になることがあります。

コミットメッセージにある `Fixes #2506` についてですが、現在のGoプロジェクトの公開Issueトラッカーで #2506 を検索すると、VS Code Go拡張機能に関する別のIssueが表示されます。これは、このコミットが作成された2012年当時のGoプロジェクトのIssueトラッカーが現在とは異なっていたか、あるいは内部的なトラッキング番号であった可能性を示唆しています。同様に、`https://golang.org/cl/5693055` も現在のGo Gerritの公開リポジトリでは直接検索できませんでした。これは、古い変更リスト番号であるか、内部的な変更リストである可能性があります。

この修正は、リンカがコードセクションの末尾に適切なパディング(埋め草)を追加することで、`.rodata`セクションが常に適切なアライメントで開始されるようにすることを目的としています。

## 前提知識の解説

このコミットを理解するためには、以下の概念を把握しておく必要があります。

1.  **リンカ (`ld`)**:
    リンカは、コンパイラによって生成された複数のオブジェクトファイル(`.o`ファイルなど)とライブラリを結合し、実行可能なプログラムや共有ライブラリを生成するツールです。リンカの主な役割は以下の通りです。
    *   **シンボル解決**: 異なるオブジェクトファイル間で参照される関数や変数のアドレスを解決します。
    *   **セクション結合**: オブジェクトファイル内の同じ種類のセクション(例: すべてのコードセクション、すべてのデータセクション)を結合し、最終的な実行ファイル内の連続したメモリ領域に配置します。
    *   **再配置**: コードやデータ内のアドレス参照を、最終的なメモリレイアウトに合わせて調整します。
    *   **メモリレイアウトの決定**: 実行ファイルがメモリにロードされたときに、各セクションがどこに配置されるかを決定します。

2.  **セクション**:
    実行ファイルやオブジェクトファイルは、異なる種類の情報を含む複数の論理的な「セクション」に分割されています。主要なセクションには以下のようなものがあります。
    *   **`.text`セクション(コードセクション)**: 実行可能な機械語コードが含まれます。通常、読み取り専用で実行可能です。
    *   **`.rodata`セクション(読み取り専用データセクション)**: プログラム実行中に変更されない定数データ(文字列リテラル、定数配列など)が含まれます。通常、読み取り専用です。
    *   **`.data`セクション(初期化済みデータセクション)**: プログラム開始時に初期値を持つグローバル変数や静的変数が含まれます。読み書き可能です。
    *   **`.bss`セクション(未初期化データセクション)**: プログラム開始時にゼロで初期化されるグローバル変数や静的変数が含まれます。ファイルサイズを削減するため、ファイル内には実際のデータは含まれず、実行時にメモリ上でゼロクリアされます。

3.  **メモリセグメント**:
    リンカは、関連するセクションをまとめて「セグメント」と呼ばれるより大きな単位に編成し、実行ファイル内のプログラムヘッダにその情報を記録します。オペレーティングシステムは、これらのセグメントをメモリにロードします。例えば、`.text`セクションと`.rodata`セクションは、しばしば読み取り専用の単一のセグメント(`PT_LOAD`タイプ)にまとめられます。

4.  **メモリのアライメント(整列)**:
    メモリのアライメントとは、データがメモリ上で特定のバイト境界に配置されることを保証するプロセスです。例えば、「4バイトアライメント」とは、データの開始アドレスが4の倍数である必要があることを意味します。
    *   **なぜアライメントが必要か**:
        *   **ハードウェアの要件**: 多くのCPUアーキテクチャは、特定のデータ型(例: 整数、浮動小数点数)がそのサイズの倍数のアドレスに配置されることを期待します。アライメントされていないアクセスは、パフォーマンスの低下やハードウェア例外を引き起こす可能性があります。
        *   **キャッシュ効率**: CPUのキャッシュラインは通常、特定のサイズ(例: 64バイト)でアライメントされています。データをキャッシュラインの境界に配置することで、キャッシュミスを減らし、データアクセスを高速化できます。
        *   **アトミック操作**: マルチスレッド環境でのアトミック操作は、多くの場合、アライメントされたデータに対してのみ保証されます。
    *   **パディング**: アライメント要件を満たすために、リンカやコンパイラはセクション間やデータ構造内に「パディング」(埋め草バイト)を挿入することがあります。

5.  **仮想アドレス (`va`)**:
    リンカの処理において、`va`(virtual address)は、リンカが現在処理しているセクションやデータの仮想メモリ上のアドレスを追跡するために使用されるカウンタのようなものです。リンカは、この`va`をインクリメントしながら、各シンボルやセクションを配置していきます。

6.  **`rnd` 関数**:
    `rnd` は "round" の略で、この文脈では指定された値(`va`)を、指定されたアライメント値(例: 128)の最も近い倍数に切り上げる関数を指します。例えば、`rnd(100, 128)` は `128` を返し、`rnd(200, 128)` は `256` を返します。これにより、次のセクションやデータが常に指定された境界から開始されることが保証されます。

## 技術的詳細

このコミットが修正しようとしている問題は、リンカがコードセクション(`.text`)の末尾を適切にアライメントせずに、その直後に読み取り専用データセクション(`.rodata`)を配置していたことに起因します。

Go言語のリンカは、`textaddress` 関数内で、各テキストシンボル(関数など)のサイズを `va` に加算していくことで、コードセクション全体の仮想アドレス空間を計算しています。しかし、この計算の最後に、コードセクションの最終的なサイズが特定のアライメント境界に揃えられていなかったため、`va` の値が任意のアライメントになっていました。

実行ファイルでは、コードセクションと読み取り専用データセクションは、しばしば単一の読み取り専用セグメントとしてメモリにロードされます。このセグメント内で、コードセクションの直後に読み取り専用データセクションが続きます。もしコードセクションの末尾が適切にアライメントされていない場合、その直後に続く読み取り専用データセクションの開始アドレスも、必要なアライメントを満たさない「ずれた」位置になってしまいます。

例えば、あるアーキテクチャで読み取り専用データが8バイトアライメントを必要とするとします。もしコードセクションのサイズが100バイトで、その直後に`.rodata`が続くと、`.rodata`はアドレス100から開始されます。しかし、100は8の倍数ではないため、`.rodata`はミスアライメントされた状態になります。

このミスアライメントは、特に`.rodata`セクション内に配置される文字列リテラルや定数配列、あるいはGo言語のランタイムが使用する内部的な読み取り専用データ構造に影響を与える可能性があります。これらのデータが特定のバイト境界に配置されることを期待するCPU命令や最適化が存在する場合、ミスアライメントはパフォーマンスの低下や、最悪の場合、プログラムのクラッシュを引き起こす可能性があります。

このコミットでは、`textaddress` 関数内でコードセクションの最終的な仮想アドレス `va` を、128バイト境界に強制的にアライメントすることでこの問題を解決しています。`va = rnd(va, 128);` の行がこれを行います。128バイトという値は、一般的なCPUのキャッシュラインサイズ(例: 64バイト)やページサイズ(例: 4KB)を考慮すると、十分なアライメントを提供し、かつ過度なパディングを避けるための妥当な選択と考えられます。コメントにある「128 bytes is likely overkill but definitely cheap.」は、このアライメントがほとんどのケースで必要とされるよりも厳密であるかもしれないが、そのコスト(追加されるパディングの量)は非常に小さいことを示唆しています。

これにより、コードセクションの末尾が常に128バイト境界に揃えられるため、その直後に続く読み取り専用データセクションは、常に128バイト境界から開始されることが保証され、アライメントの問題が解消されます。

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

`src/cmd/ld/data.c` ファイルの `textaddress` 関数に以下の5行が追加されました。

```diff
--- a/src/cmd/ld/data.c
+++ b/src/cmd/ld/data.c
@@ -1023,6 +1023,11 @@ textaddress(void)\n 		}\n 		va += sym->size;\n 	}\n+\t\n+\t// Align end of code so that rodata starts aligned.\n+\t// 128 bytes is likely overkill but definitely cheap.\n+\tva = rnd(va, 128);\n+\n \tsect->len = va - sect->vaddr;\

コアとなるコードの解説

追加されたコードは、textaddress 関数内で、コードセクションの仮想アドレスの計算が完了した直後に実行されます。

	// Align end of code so that rodata starts aligned.
	// 128 bytes is likely overkill but definitely cheap.
	va = rnd(va, 128);
  • va: これは、リンカが現在処理しているセクションの仮想アドレスの現在位置を示す変数です。textaddress 関数内では、各テキストシンボル(関数など)のサイズが va に加算され、コードセクション全体のサイズと次の配置開始アドレスを計算しています。
  • rnd(va, 128): この関数呼び出しは、現在の va の値を、128の倍数に切り上げることを意味します。例えば、va が1000の場合、rnd(1000, 128) は1024(128 * 8)を返します。va が1025の場合、rnd(1025, 128) は1152(128 * 9)を返します。
  • va = ...: rnd 関数によって計算された新しいアライメントされたアドレスが va に再代入されます。

この変更により、コードセクションの末尾(つまり、その直後に続く.rodataセクションの開始位置)が常に128バイト境界に整列されることが保証されます。これにより、.rodataセクション内のデータが適切なアライメントで配置され、前述のパフォーマンス問題やクラッシュのリスクが軽減されます。コメントにあるように、128バイトというアライメントは、ほとんどのアーキテクチャやデータ型にとって十分すぎるほど厳密ですが、そのために発生するパディングの量は通常ごくわずかであり、安全かつ効率的な解決策と見なされています。

関連リンク

  • GitHubコミットページ: https://github.com/golang/go/commit/15d8b05f0ca604e40ba42a3e9f6d30b1a280d1d8
  • コミットメッセージに記載されているIssue #2506は、現在のGoプロジェクトの公開IssueトラッカーではVS Code Go拡張機能に関する別のIssueを指しており、このコミットとは直接関連がないようです。
  • コミットメッセージに記載されている変更リスト https://golang.org/cl/5693055 は、現在のGo Gerritの公開リポジトリでは直接検索できませんでした。

参考にした情報源リンク