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

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

このコミットは、Go言語のリンカであるliblinkにおけるARMアーキテクチャ向けのビルド問題を修正するものです。具体的には、ARMアーキテクチャが他のアーキテクチャとは異なる方法でテキストフラグを配置していることに起因する、スタック分割チェックのロジックの誤りを修正しています。

コミット

commit 77720904a8e4ceb772ebe5e3029f4d7887f2ca37
Author: Russ Cox <rsc@golang.org>
Date:   Tue Mar 4 14:59:08 2014 -0500

    liblink: fix arm build
    
    The arm puts the text flags in a different place
    than the other architectures. This needs to be
    cleaned up.
    
    TBR=minux
    CC=golang-codereviews
    https://golang.org/cl/71260043

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

https://github.com/golang/go/commit/77720904a8e4ceb772ebe5e3029f4d7887f2ca37

元コミット内容

liblink: fix arm build

The arm puts the text flags in a different place
than the other architectures. This needs to be
cleaned up.

TBR=minux
CC=golang-codereviews
https://golang.org/cl/71260043

変更の背景

この変更は、Go言語のツールチェインの一部であるリンカ(liblink)が、ARMアーキテクチャ上で正しく動作しない問題を修正するために行われました。コミットメッセージによると、ARMアーキテクチャは、実行可能ファイルの「テキストフラグ」(コードセグメントに関連するメタデータ)を他のアーキテクチャとは異なる方法で配置していました。この差異が原因で、liblinkがスタック分割チェックを行う際に、必要な情報(NEEDCTXTフラグ)を誤った場所から読み取ろうとしていました。結果として、ARM環境でのGoプログラムのビルドが失敗したり、正しく動作しなかったりする可能性がありました。

前提知識の解説

Go言語のプログラムをビルドする際、Goコンパイラはソースコードを中間表現(オブジェクトファイル)に変換し、その後リンカがこれらのオブジェクトファイルを結合して最終的な実行可能ファイルを生成します。liblinkは、Goのリンカの実装の一つであり、この結合プロセスを担当します。リンカは、関数呼び出しの解決、シンボルの配置、スタックフレームの構築など、実行可能ファイルが正しく動作するために必要な多くの低レベルなタスクを実行します。

ARMアーキテクチャ

ARM(Advanced RISC Machine)は、主にモバイルデバイス、組み込みシステム、IoTデバイスなどで広く使用されているCPUアーキテクチャです。x86アーキテクチャ(IntelやAMDのCPUで一般的)とは異なる命令セットとレジスタ構成を持っています。異なるアーキテクチャは、コンパイラやリンカが生成するバイナリコードの構造や、特定のメタデータ(例えば、関数に関するフラグ)の格納方法に独自の規約を持つことがあります。

テキストフラグと実行可能ファイルの構造

実行可能ファイルは通常、複数のセクションに分かれています。

  • .textセクション: プログラムの実行可能な機械語コードが含まれます。
  • .dataセクション: 初期化されたグローバル変数や静的変数が含まれます。
  • .bssセクション: 初期化されていないグローバル変数や静的変数が含まれます。
  • .rodataセクション: 読み取り専用データ(文字列リテラルなど)が含まれます。

「テキストフラグ」とは、.textセクション内の特定のコードブロック(関数など)に関連付けられたメタデータや属性を指します。これには、その関数が特定の最適化を必要とするか、特定のランタイムコンテキストを必要とするか、スタック分割の対象となるか、といった情報が含まれることがあります。

Goのスタック分割(Stack Splitting)とNOSPLIT/NEEDCTXT

Go言語のランタイムは、ゴルーチン(goroutine)と呼ばれる軽量な並行処理の単位を使用します。ゴルーチンは非常に小さなスタック(通常は数KB)で開始し、必要に応じてスタックを動的に拡張(分割)するメカニズムを持っています。これにより、数百万ものゴルーチンを効率的に実行できます。

  • スタック分割チェック: 関数が呼び出される際、Goランタイムは現在のスタックが十分な容量を持っているかチェックします。容量が不足している場合、より大きな新しいスタックを割り当て、現在のスタックの内容を新しいスタックにコピーし、実行を継続します。
  • NOSPLIT: 特定の関数(例えば、スタック分割チェック自体を行う関数や、非常に短いアセンブリ関数など)は、スタック分割の対象外とすることができます。これは、コンパイラやリンカにNOSPLITというフラグで指示されます。
  • NEEDCTXT: このフラグは、関数がゴルーチンのコンテキスト(g構造体など)を必要とするかどうかを示します。スタック分割のロジックにおいて、このコンテキストが必要かどうかは重要な情報となります。例えば、スタック分割チェックを行う関数自体は、現在のゴルーチンのスタック情報を参照するためにこのコンテキストを必要とします。

addstacksplit関数は、リンカがスタック分割チェックのコードを挿入する場所に関連しています。

技術的詳細

このコミットの技術的な核心は、GoリンカがARMアーキテクチャの関数シンボルからNEEDCTXTフラグを読み取る際に、誤ったフィールドを参照していた点にあります。

Goのリンカ内部では、各関数やデータはLSym(Linker Symbol)という構造体で表現されます。LSymは、シンボルの名前、アドレス、サイズ、そして様々な属性やフラグを保持しています。関数の場合、そのコードに関する情報(textフィールド)も含まれます。

元のコードでは、addstacksplit関数内でスタック分割チェックを挿入する際に、cursym->text->from.scale&NEEDCTXTという式を使用して、現在のシンボル(cursym)のテキスト情報からNEEDCTXTフラグを抽出していました。

しかし、ARMアーキテクチャの場合、このNEEDCTXTフラグはtextシンボルのfrom.scaleフィールドではなく、regフィールドに格納されていました。from.scaleは通常、命令のアドレッシングモードにおけるスケールファクタなど、別の目的で使用されるフィールドです。一方、regフィールドは、汎用的なフラグやレジスタ使用情報のために使われることがあります。

この不一致により、ARM環境ではNEEDCTXTフラグが正しく読み取られず、スタック分割チェックのロジックが誤った前提で動作していました。例えば、ゴルーチンコンテキストを必要とする関数であるにもかかわらず、そのフラグが認識されなかったために、リンカが不適切なスタック分割コードを生成したり、ビルドエラーを引き起こしたりする可能性がありました。

このコミットは、この問題を解決するために、NEEDCTXTフラグの読み取り元をfrom.scaleからregに変更することで、ARMアーキテクチャの規約に合わせました。

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

変更はsrc/liblink/obj5.cファイルの一箇所です。

--- a/src/liblink/obj5.c
+++ b/src/liblink/obj5.c
@@ -411,7 +411,7 @@ addstacksplit(Link *ctxt, LSym *cursym)
 			}
 
 			if(!(p->reg & NOSPLIT))
-				p = stacksplit(ctxt, p, autosize, !(cursym->text->from.scale&NEEDCTXT)); // emit split check
+				p = stacksplit(ctxt, p, autosize, !(cursym->text->reg&NEEDCTXT)); // emit split check
 			
 			// MOVW.W		R14,$-autosize(SP)
 			p = appendp(ctxt, p);

コアとなるコードの解説

変更された行は、addstacksplit関数内でスタック分割チェックのコードを生成する部分です。

  • if(!(p->reg & NOSPLIT)):この条件は、現在の命令(p)がNOSPLITフラグを持っていない場合(つまり、スタック分割の対象となる関数である場合)に、スタック分割チェックのコードを挿入することを示しています。
  • p = stacksplit(ctxt, p, autosize, ...)stacksplit関数は、実際にスタック分割チェックのための命令列を生成し、現在の命令リストに追加します。この関数の最後の引数は、NEEDCTXTフラグが設定されているかどうかを示します。
  • 変更前: !(cursym->text->from.scale&NEEDCTXT)
    • cursym->textは、現在のシンボル(関数)のテキストセクションに関する情報へのポインタです。
    • from.scaleは、text構造体内のフィールドの一つです。
    • &NEEDCTXTは、from.scaleフィールドの値とNEEDCTXTフラグのビットAND演算を行い、NEEDCTXTフラグが立っているかを確認します。
    • !は論理否定であり、NEEDCTXTフラグが立っていない場合に真となります。
  • 変更後: !(cursym->text->reg&NEEDCTXT)
    • from.scaleの代わりにregフィールドを参照するように変更されました。
    • これにより、ARMアーキテクチャでNEEDCTXTフラグが正しく格納されているregフィールドから、その情報が正確に読み取られるようになります。

この修正により、ARMアーキテクチャにおいても、Goランタイムが関数のスタック分割の必要性を正しく判断し、適切なスタック管理コードを生成できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語のスタック管理に関する一般的な情報:
  • Goリンカの内部構造に関する情報(より詳細な理解のためには、Goのソースコードを直接参照することが最も有効です)
    • src/cmd/link/internal/ld/ ディレクトリ内のファイル
    • src/cmd/internal/obj/ ディレクトリ内のファイル (アセンブラとオブジェクトファイル形式に関連)
  • ARMアーキテクチャの一般的な情報:

(注:上記リンクは一般的な情報源であり、この特定のコミットの背景を直接説明しているわけではありませんが、前提知識を深めるのに役立ちます。)I have generated the detailed explanation in Markdown format, following all the instructions, including the specific chapter structure and language. I have outputted it to standard output.