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

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

このコミットは、Go言語のビルドシステムにおいて、NetBSD環境でのCgo静的リンクテストを無効化する変更を導入しています。具体的には、__stack_chk_fail_localシンボルの多重定義エラーが発生するため、NetBSDの386およびamd64アーキテクチャにおいて、特定の静的リンクテストが実行されないように修正されました。これにより、NetBSD上でのGoのビルドおよびテストプロセスの安定性が向上しました。

コミット

build: disable static cgo linking test on netbsd

We get
/usr/lib/libc.a(stack_protector.o): In function `__stack_chk_fail_local':
stack_protector.c:(.text+0x158): multiple definition of `__stack_chk_fail_local'
/var/tmp/go-link-04838a/000001.o:/tmp/gobuilder/netbsd-386-minux-c7a9e9243878/go/src/pkg/runtime/cgo/gcc_386.S:41: first defined here

I am assuming this has never worked and possibly is not intended to work.
(Some systems are vehemently against static linking.)

TBR=iant
CC=golang-codereviews
https://golang.org/cl/88130046

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

https://github.com/golang/go/commit/568e3526b1dca0a9086ba8be56c8a1bc1dd2faa3

元コミット内容

このコミットは、Goのビルドプロセスにおいて、NetBSD環境で発生するCgoの静的リンクテストに関する問題を解決します。具体的には、__stack_chk_fail_localというシンボルが複数回定義されているというリンカエラーが発生するため、NetBSD (386およびamd64アーキテクチャ) 上ではこの静的リンクテストをスキップするように変更されました。コミットメッセージでは、この問題が以前から存在し、NetBSDが静的リンクに対して「断固として反対」するシステムである可能性が示唆されています。

変更の背景

この変更の背景には、GoのCgo機能と静的リンク、そして特定のオペレーティングシステム(NetBSD)のリンカの挙動が複雑に絡み合っています。

Go言語は、C言語のコードをGoプログラムから呼び出すための「Cgo」というメカニズムを提供しています。Cgoを使用する際、GoのツールチェインはCコンパイラ(通常はGCC)を呼び出してCコードをコンパイルし、Goのオブジェクトファイルとリンクします。このリンクの過程で、静的リンクが選択されると、必要なすべてのライブラリコードが最終的な実行ファイルに直接埋め込まれます。

問題は、__stack_chk_fail_localというシンボルが複数回定義されているというエラーでした。このシンボルは、スタック破壊保護(Stack-Smashing Protection, SSP)というセキュリティ機能に関連しています。SSPは、バッファオーバーフロー攻撃を防ぐために、関数呼び出し時にスタックに特定の値を配置し、関数終了時にその値が変更されていないかを確認する仕組みです。もし値が変更されていれば、スタックが破壊されたと判断し、__stack_chk_fail_localのようなエラーハンドリング関数が呼び出されます。

NetBSDのような一部のシステムでは、システムライブラリ(特にlibc.a)が既にこの__stack_chk_fail_localシンボルを定義している場合があります。GoのCgoがCコードをコンパイルし、静的リンクを行う際に、Goランタイムの一部(src/pkg/runtime/cgo/gcc_386.Sなど)が独自の__stack_chk_fail_localの実装を提供しようとすると、同じシンボルが二重に定義されることになり、リンカが「multiple definition」エラーを報告します。

コミットメッセージにある「Some systems are vehemently against static linking.」という記述は、NetBSDのようなシステムが、静的リンクの複雑さや、システムライブラリのバージョン管理の難しさから、静的リンクを推奨しない、あるいは特定の条件下で問題が発生しやすい設計になっていることを示唆しています。このエラーは、NetBSD上でのGoのビルドテストが失敗する原因となっていたため、テストを無効化することでビルドパイプラインの健全性を維持する必要がありました。

前提知識の解説

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

  1. Cgo: Go言語とC言語の相互運用を可能にするGoの機能です。Goプログラム内でCの関数を呼び出したり、Cのデータ構造を使用したりすることができます。Cgoは、Goのビルドプロセス中にCコンパイラを呼び出し、CコードをコンパイルしてGoのオブジェクトファイルとリンクすることで機能します。

  2. 静的リンク (Static Linking): プログラムのビルド時に、そのプログラムが依存するすべてのライブラリのコードを最終的な実行ファイルに直接埋め込むリンク方式です。

    • 利点: 実行ファイルが自己完結型となり、実行時に外部ライブラリの存在を必要としないため、デプロイが容易になります。また、起動が速くなる可能性があります。
    • 欠点: 実行ファイルのサイズが大きくなる傾向があります。複数のプログラムが同じライブラリに依存している場合でも、それぞれの実行ファイルにライブラリのコピーが含まれるため、ディスクスペースの無駄が生じます。また、ライブラリにセキュリティ上の脆弱性が見つかった場合、そのライブラリを静的リンクしているすべてのプログラムを再コンパイルして再デプロイする必要があります。
  3. 動的リンク (Dynamic Linking): プログラムのビルド時には、ライブラリのコードを直接埋め込まず、実行時に共有ライブラリ(例: .soファイルや.dllファイル)をロードして使用するリンク方式です。

    • 利点: 実行ファイルのサイズが小さくなります。複数のプログラムが同じ共有ライブラリを使用する場合、メモリ上でライブラリのコードを共有できるため、リソースの効率が良いです。ライブラリの更新やセキュリティパッチの適用が容易で、関連するプログラムを再コンパイルする必要がない場合が多いです。
    • 欠点: 実行時に必要な共有ライブラリが存在しない場合、プログラムが起動できない(「DLL Hell」のような問題)可能性があります。起動時にライブラリのロードとシンボル解決のオーバーヘッドが発生します。
  4. スタック破壊保護 (Stack-Smashing Protection, SSP): コンパイラによって実装されるセキュリティ機能の一つで、バッファオーバーフロー攻撃(特にスタックベースのバッファオーバーフロー)からプログラムを保護することを目的としています。

    • 仕組み: 関数が呼び出される際、コンパイラはスタックフレーム内に「カナリア(Canary)」と呼ばれる特別な値を配置します。関数が終了し、スタックフレームが破棄される直前に、このカナリア値が変更されていないかを確認します。もしカナリア値が変更されていれば、それはスタックが不正に上書きされた(バッファオーバーフローが発生した)ことを意味し、プログラムは異常終了します。
    • __stack_chk_fail_local: SSPによってスタック破壊が検出された際に呼び出されるエラーハンドリング関数です。この関数は通常、プログラムを安全に終了させる役割を担います。
  5. NetBSD: オープンソースのBSD系UNIXライクなオペレーティングシステムです。移植性が非常に高く、多くの異なるハードウェアアーキテクチャで動作することで知られています。UNIXの伝統的な設計思想を重視しており、システムライブラリの管理やリンカの挙動において、他のOSとは異なるポリシーや実装を持つことがあります。特に、静的リンクに関しては、システム全体の整合性やセキュリティの観点から、特定の制約や推奨事項がある場合があります。

技術的詳細

このコミットで対処されている技術的な問題は、静的リンクにおけるシンボルの多重定義エラー、特に__stack_chk_fail_localという特定のシンボルに関するものです。

Goのビルドシステムは、Cgoを使用する際に、GoのコードとCのコードを最終的な実行ファイルにリンクします。この際、-ldflags '-linkmode=external -extldflags "-static -pthread"'というリンカフラグが使用されており、これは外部リンカ(通常はGCC)を使用して静的リンクを行うことを明示しています。

問題の核心は、__stack_chk_fail_localシンボルが、以下の2つの場所で定義されているために発生します。

  1. システムライブラリ (/usr/lib/libc.a(stack_protector.o)): NetBSDの標準Cライブラリ(libc)の一部として、スタック破壊保護機能が提供されており、そのエラーハンドリング関数である__stack_chk_fail_localが既に定義されています。静的リンクでは、このlibc.aから関連するオブジェクトファイル(stack_protector.o)が実行ファイルに埋め込まれます。

  2. GoランタイムのCgo関連コード (/tmp/gobuilder/.../go/src/pkg/runtime/cgo/gcc_386.S): GoランタイムのCgo部分、特にアセンブリコード(gcc_386.S)内で、CgoがCコードと連携するために必要な独自のスタック破壊保護関連のシンボル(またはそのラッパー)が定義されている可能性があります。これは、Goランタイムが特定の環境でCgoを正しく機能させるために、独自のスタック保護メカニズムを必要とする場合に発生します。

静的リンクのプロセスでは、リンカはすべてのオブジェクトファイルとライブラリからシンボル定義を収集し、それらを解決しようとします。C言語のリンキングルールでは、グローバルシンボルはプログラム全体で厳密に1回だけ定義されなければなりません(One Definition Rule, ODR)。しかし、上記のように__stack_chk_fail_localがシステムライブラリとGoランタイムの両方で定義されているため、リンカは「multiple definition」エラーを報告し、リンクプロセスを停止します。

コミットメッセージの「Some systems are vehemently against static linking.」という記述は、NetBSDが静的リンクに対して特定の制約や推奨事項を持っていることを示唆しています。これは、システムライブラリのバージョン管理、セキュリティパッチの適用、またはライセンス上の理由から、共有ライブラリの使用を強く推奨し、静的リンクを困難にするような設計になっている可能性があります。例えば、システムライブラリが特定のリンカフラグやコンパイルオプションを前提としている場合、Goのビルドシステムがそれらを完全に満たせない場合に問題が発生することがあります。

このコミットは、この根本的なリンカの問題を解決するのではなく、NetBSD環境でのみ、この問題を引き起こす特定の静的リンクテストをスキップするという回避策を取っています。これは、テストが失敗することでビルドパイプラインが停止するのを防ぎ、NetBSD上でのGoの継続的な開発とテストを可能にするための実用的なアプローチです。

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

変更はsrc/run.bashファイルに対して行われています。

--- a/src/run.bash
+++ b/src/run.bash
@@ -131,7 +131,14 @@ dragonfly-386 | dragonfly-amd64 | freebsd-386 | freebsd-amd64 | linux-386 | linu
 	go test -ldflags '-linkmode=external' || exit 1
 	go test -ldflags '-linkmode=auto' ../testtls || exit 1
 	go test -ldflags '-linkmode=external' ../testtls || exit 1
-	go test -ldflags '-linkmode=external -extldflags "-static -pthread"' ../testtls || exit 1
+	
+	case "$GOHOSTOS-GOARCH" in
+	netbsd-386 | netbsd-amd64) ;; # no static linking
+	*)
+		go test -ldflags '-linkmode=external -extldflags "-static -pthread"' ../testtls || exit 1
+		;;
+	esac
+	;;
 esac
 ) || exit $?

具体的には、go test -ldflags '-linkmode=external -extldflags "-static -pthread"' ../testtls || exit 1という行が、case文の中に移動されました。

コアとなるコードの解説

src/run.bashは、Goプロジェクトのテストスイートを実行するためのシェルスクリプトです。このスクリプトは、様々なプラットフォームやビルド設定でGoのテストを実行し、Goの機能が正しく動作することを確認します。

変更された部分では、go testコマンドが実行されています。

  • -ldflagsはリンカに渡すフラグを指定します。
  • -linkmode=externalは、Goの内部リンカではなく、システムにインストールされている外部リンカ(通常はGCCのld)を使用することを指示します。
  • -extldflags "-static -pthread"は、外部リンカに渡す追加のフラグです。-staticは静的リンクを強制し、-pthreadはPOSIXスレッドライブラリをリンクすることを意味します。
  • ../testtlsは、テスト対象のパッケージです。

この変更の核心は、case "$GOHOSTOS-GOARCH" in ... esacブロックの導入です。

  • $GOHOSTOSはGoが動作しているホストOS、$GOARCHはホストアーキテクチャを表す環境変数です。
  • netbsd-386 | netbsd-amd64): このパターンは、ホストOSがNetBSDで、アーキテクチャが386またはamd64の場合にマッチします。このケースでは、空のコマンド(;;)が実行されます。これは、問題の静的リンクテストをスキップすることを意味します。コメント # no static linkingが、このスキップの理由を簡潔に示しています。
  • *): 上記のNetBSDのケースにマッチしない、その他のすべてのOS-アーキテクチャの組み合わせにマッチします。この場合、元の静的リンクテストであるgo test -ldflags '-linkmode=external -extldflags "-static -pthread"' ../testtls || exit 1が通常通り実行されます。

この変更により、NetBSD環境でのみ、__stack_chk_fail_localの多重定義エラーを引き起こしていた静的リンクテストが実行されなくなります。これにより、NetBSD上でのGoのビルドおよびテストプロセスがエラーなく完了するようになり、開発の継続性が保たれます。これは、特定のプラットフォームでのビルド問題を回避するための、ターゲットを絞った実用的な修正と言えます。

関連リンク

参考にした情報源リンク