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

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

このコミットは、Go言語のリンカ (ld) におけるELFデータセクションのアライメントに関する修正です。具体的には、src/cmd/5l/l.hsrc/cmd/6l/l.hsrc/cmd/8l/l.hsrc/cmd/ld/data.csrc/cmd/ld/ldelf.c の5つのファイルが変更されています。

コミット

commit bf43161c00d1214983f85a1535d226ab19287aac
Author: Ian Lance Taylor <iant@golang.org>
Date:   Wed Nov 30 13:24:16 2011 -0800

    ld: align ELF data sections
    
    fixes #2506
    
    R=rsc, iant
    CC=golang-dev
    https://golang.org/cl/5440066

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

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

元コミット内容

ld: align ELF data sections

fixes #2506

変更の背景

このコミットの主な目的は、ELF (Executable and Linkable Format) 形式の実行可能ファイルにおけるデータセクションのアライメントを適切に行うことです。コミットメッセージにある fixes #2506 は、このアライメント問題に関連する内部的なバグまたは改善要求を指していると考えられます。

プログラムが実行される際、データはメモリ上に配置されます。特定のデータ型(例えば、64ビット整数や浮動小数点数)は、そのデータ型が効率的にアクセスできるように、特定のメモリ境界に配置される必要があります。これを「アライメント」と呼びます。アライメントが適切でない場合、プロセッサがデータを読み書きする際に余分なサイクルが必要になったり、最悪の場合、アライメント違反エラーが発生してプログラムがクラッシュしたりする可能性があります。

特に、ELF形式の実行可能ファイルでは、データセクション(.data, .rodata, .bss など)がメモリにロードされる際のアドレスが、そのセクション内のデータの要件を満たすようにアライメントされている必要があります。Go言語のリンカは、コンパイルされたGoプログラムを最終的な実行可能ファイルに結合する役割を担っており、この過程でデータセクションの適切なアライメントを保証する必要があります。

このコミット以前は、Goリンカが生成するELFファイルにおいて、データセクションのアライメントが不十分であったか、あるいは特定のケースでアライメント要件が満たされていなかった可能性があります。これにより、パフォーマンスの低下や、特定のアーキテクチャでの互換性問題が発生していたと考えられます。

前提知識の解説

ELF (Executable and Linkable Format)

ELFは、Unix系オペレーティングシステム(Linux、BSDなど)で広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELFファイルは、プログラムのコード、データ、シンボル情報などを構造化して格納します。主要なセクションには以下のようなものがあります。

  • .text: 実行可能なコードが格納されます。
  • .data: 初期化されたグローバル変数や静的変数が格納されます。
  • .rodata: 読み取り専用のデータ(文字列リテラルなど)が格納されます。
  • .bss: 初期化されていないグローバル変数や静的変数が格納されます(実行時にゼロ初期化されます)。
  • .symtab: シンボルテーブル。変数名や関数名とそれらのアドレスのマッピングが含まれます。

データセクションとアライメント

データセクションは、プログラムが使用するデータを格納するメモリ領域です。プロセッサは、メモリからデータを読み込む際に、特定のバイト境界(例えば、4バイト境界、8バイト境界など)にデータが配置されていることを期待する場合があります。この境界を「アライメント」と呼びます。

  • アライメントの必要性:
    • パフォーマンス: 多くのプロセッサは、アライメントされたデータへのアクセスを高速化するハードウェア機構を持っています。アライメントされていないデータへのアクセスは、複数のメモリ読み込み操作を必要としたり、特別な処理が必要になったりするため、パフォーマンスが低下します。
    • 正確性: 一部のプロセッサアーキテクチャでは、アライメントされていないデータへのアクセスが許可されておらず、アライメント違反例外(Segmentation Faultなど)を引き起こす可能性があります。
    • キャッシュ効率: 適切にアライメントされたデータは、CPUキャッシュラインに効率的に収まり、キャッシュミスを減らすことでパフォーマンスを向上させます。

リンカ (ld)

リンカは、コンパイラによって生成された複数のオブジェクトファイル(.o ファイルなど)やライブラリを結合し、最終的な実行可能ファイルや共有ライブラリを生成するツールです。リンカの主な役割は以下の通りです。

  • シンボル解決: 異なるオブジェクトファイル間で参照されているシンボル(関数や変数)を解決し、正しいメモリアドレスにリンクします。
  • セクションの結合と配置: 各オブジェクトファイルのセクション(.text, .data など)を結合し、最終的な実行可能ファイル内の適切なメモリアドレスに配置します。この際、アライメント要件を満たすように配置を調整します。
  • 再配置: コード内のアドレス参照を、最終的な実行可能ファイル内の実際のアドレスに修正します。

Go言語のツールチェーンでは、cmd/5l (ARM), cmd/6l (x86-64), cmd/8l (x86) などが各アーキテクチャ向けのリンカとして機能します。これらのリンカは、共通のリンカロジック (src/cmd/ld) を利用しています。

技術的詳細

このコミットは、Go言語のリンカがELFデータセクションを配置する際に、シンボルが持つアライメント要件を考慮するように変更を加えるものです。

変更の核心は、シンボル構造体 Symalign フィールドを追加し、リンカがデータセクションを配置する際にこの align 値を利用してアドレスを調整することです。

Sym 構造体への align フィールドの追加

src/cmd/5l/l.h, src/cmd/6l/l.h, src/cmd/8l/l.h の各リンカヘッダファイルにおいて、Sym 構造体に int32 align; // if non-zero, required alignment in bytes というフィールドが追加されています。 この align フィールドは、そのシンボルがメモリ上で必要とするアライメントのバイト数を表します。0 の場合は、特別なアライメント要件がないことを意味します。

ldelf.c での align 値の設定

src/cmd/ld/ldelf.cldelf 関数は、ELFオブジェクトファイルからシンボル情報を読み込む際に使用されます。このコミットでは、s->align = sect->align; という行が追加されています。これは、ELFセクションヘッダに定義されているアライメント情報 (sect->align) を、対応するシンボル (s) の align フィールドにコピーすることを意味します。これにより、リンカは個々のシンボルが属するセクションのアライメント要件を認識できるようになります。

data.c でのアライメントの適用

src/cmd/ld/data.c は、リンカがデータセクションを処理する主要なファイルです。このファイル内の dodata 関数と textaddress 関数が変更されています。

  • dodata 関数: この関数は、.rodata (読み取り専用データ) および .data (初期化済みデータ) セクション内のシンボルを配置する責任があります。変更点では、各シンボル s を配置する前に if(s->align != 0) datsize = rnd(datsize, s->align); というチェックが追加されています。

    • datsize は現在のデータセクションのサイズ(またはオフセット)を表します。
    • rnd(value, align) 関数は、valuealign の倍数に切り上げる(アライメントする)ヘルパー関数です。
    • このロジックにより、シンボルが特定のアライメントを要求する場合 (s->align != 0)、そのシンボルが配置されるアドレス (datsize) が、要求されたアライメントの境界に揃えられるようになります。
    • 既存の t & 1t & 2 といったアライメントロジックよりも、シンボル固有の s->align が優先されるように変更されています。これは、より汎用的で正確なアライメント制御を可能にします。
  • textaddress 関数: この関数は、テキストセクション(コード)内のシンボル(関数など)のアドレスを決定します。同様に、if(sym->align != 0) va = rnd(va, sym->align); という行が追加されています。これにより、関数などのコードシンボルも、必要に応じて特定のアライメント境界に配置されるようになります。これは、特にジャンプテーブルや特定の命令セットの要件を満たすために重要となる場合があります。

これらの変更により、Goリンカは、ELFファイル内のデータおよびテキストセクションの各要素が、その要素が持つアライメント要件に従って適切に配置されることを保証できるようになります。これにより、生成される実行可能ファイルの互換性、パフォーマンス、および安定性が向上します。

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

src/cmd/5l/l.h, src/cmd/6l/l.h, src/cmd/8l/l.h

struct Symalign フィールドを追加:

--- a/src/cmd/5l/l.h
+++ b/src/cmd/5l/l.h
@@ -143,6 +143,7 @@ struct	Sym
 	int32	value;
 	int32	sig;
 	int32	size;
+	int32	align;	// if non-zero, required alignment in bytes
 	uchar	special;
 	uchar	fnptr;	// used as fn ptr
 	Sym*	hash;	// in hash table

6l/l.h8l/l.h も同様の変更)

src/cmd/ld/data.c

dodata 関数と textaddress 関数にアライメントロジックを追加/変更:

--- a/src/cmd/ld/data.c
+++ b/src/cmd/ld/data.c
@@ -824,6 +824,8 @@ dodata(void)\
 	datsize = 0;
 	s = datap;
 	for(; s != nil && s->type < SSYMTAB; s = s->next) {
+		if(s->align != 0)
+			datsize = rnd(datsize, s->align);
 		s->type = SRODATA;
 		s->value = datsize;
 		datsize += rnd(s->size, PtrSize);
@@ -855,6 +857,8 @@ dodata(void)\
 	/* read-only ELF sections */
 	for(; s != nil && s->type < SELFSECT; s = s->next) {
 		sect = addsection(&segtext, s->name, 04);
+		if(s->align != 0)
+			datsize = rnd(datsize, s->align);
 		sect->vaddr = datsize;
 		s->type = SRODATA;
 		s->value = datsize;
@@ -866,6 +870,8 @@ dodata(void)\
 	datsize = 0;
 	for(; s != nil && s->type < SDATA; s = s->next) {
 		sect = addsection(&segdata, s->name, 06);
+		if(s->align != 0)
+			datsize = rnd(datsize, s->align);
 		sect->vaddr = datsize;
 		s->type = SDATA;
 		s->value = datsize;
@@ -887,7 +893,9 @@ dodata(void)\
 			t = rnd(t, PtrSize);
 		else if(t > 2)
 			t = rnd(t, 4);
-		if(t & 1) {
+		if(s->align != 0)
+			datsize = rnd(datsize, s->align);
+		else if(t & 1) {
 			;
 		} else if(t & 2)
 			datsize = rnd(datsize, 2);
@@ -913,7 +921,9 @@ dodata(void)\
 			t = rnd(t, PtrSize);
 		else if(t > 2)
 			t = rnd(t, 4);
-		if(t & 1) {
+		if(s->align != 0)
+			datsize = rnd(datsize, s->align);
+		else if(t & 1) {
 			;
 		} else if(t & 2)
 			datsize = rnd(datsize, 2);
@@ -947,6 +957,8 @@ textaddress(void)\
 	for(sym = textp; sym != nil; sym = sym->next) {
 		if(sym->type & SSUB)
 			continue;
+		if(sym->align != 0)
+			va = rnd(va, sym->align);
 		sym->value = 0;
 		for(sub = sym; sub != S; sub = sub->sub) {
 			sub->value += va;

src/cmd/ld/ldelf.c

ldelf 関数でシンボルの align フィールドを設定:

--- a/src/cmd/ld/ldelf.c
+++ b/src/cmd/ld/ldelf.c
@@ -538,6 +538,7 @@ ldelf(Biobuf *f, char *pkg, int64 len, char *pn)\
 			s->np = sect->size;
 		}
 		s->size = sect->size;
+		s->align = sect->align;
 		if(s->type == STEXT) {
 			if(etextp)
 				etextp->next = s;

コアとなるコードの解説

Sym 構造体への align フィールドの追加

Sym 構造体は、リンカが扱うシンボル(関数、変数など)の情報を保持します。align フィールドが追加されたことで、各シンボルがメモリ上で必要とするアライメント情報を直接シンボル自体が持つことができるようになりました。これは、リンカがシンボルを配置する際に、そのシンボル固有のアライメント要件を動的に参照できる基盤となります。

ldelf.c の変更

ldelf 関数は、ELF形式のオブジェクトファイルからシンボルやセクションの情報を読み込み、リンカ内部のデータ構造に変換する役割を担っています。 s->align = sect->align; という行が追加されたことで、オブジェクトファイル内のELFセクションが持つアライメント属性 (sect->align) が、リンカ内部のシンボル構造体 (s->align) に正確に伝播されるようになりました。これにより、リンカはオブジェクトファイルが元々意図していたアライメント要件を、最終的な実行可能ファイルの生成過程で尊重できるようになります。

data.c の変更

data.c は、リンカのデータセクション配置ロジックの核心部分です。

  • dodata 関数:

    • この関数は、読み取り専用データ (SRODATA) や初期化済みデータ (SDATA) のシンボルを、最終的な実行可能ファイル内のデータセクションに配置します。
    • 追加された if(s->align != 0) datsize = rnd(datsize, s->align); の行は、各シンボル s を配置する直前に実行されます。もしシンボルに特定のアライメント要件 (s->align が非ゼロ) があれば、現在のデータセクションのオフセット datsize をそのアライメント境界に切り上げます。これにより、シンボルは常に適切なアライメントで配置されることが保証されます。
    • 既存の if(t & 1)else if(t & 2) といった汎用的なアライメントロジックの前に if(s->align != 0) が追加されたことで、シンボル固有のアライメント要件が優先されるようになりました。これは、よりきめ細やかなアライメント制御を可能にし、特定のデータ構造やハードウェア要件に対応するために重要です。
  • textaddress 関数:

    • この関数は、テキストセクション(実行コード)内のシンボル(主に関数)のアドレスを決定します。
    • if(sym->align != 0) va = rnd(va, sym->align); の追加により、関数などのコードシンボルも、必要に応じて特定のアライメント境界に配置されるようになりました。これは、例えば、特定の命令セットが特定のメモリアライメントを要求する場合や、キャッシュラインに合わせた配置でパフォーマンスを最適化する場合に役立ちます。

これらの変更は、Go言語のリンカが生成するバイナリの品質と互換性を向上させる上で重要な役割を果たします。特に、異なるアーキテクチャやオペレーティングシステム上での実行において、アライメントの問題に起因するバグやパフォーマンス低下を防ぐことができます。

関連リンク

  • Go言語のリンカに関する公式ドキュメントや設計資料は、Goのソースコードリポジトリ内やGoの公式ウェブサイトで参照できる場合があります。
  • ELFフォーマットの仕様書は、UNIX System V Application Binary Interface (ABI) の一部として公開されています。

参考にした情報源リンク

  • Go言語のソースコード (特に src/cmd/ld ディレクトリ)
  • ELF (Executable and Linkable Format) の仕様に関する一般的な情報源
  • メモリのアライメントに関するコンピュータアーキテクチャの基本概念