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

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

このコミットは、Go言語のリンカ(cmd/ld)における外部リンキング時の挙動を修正するものです。具体的には、go buildコマンドに-ldflags="-s"オプションが渡された際に、外部リンカであるgccにも適切に-sオプション(シンボル情報を除去するオプション)を渡すように変更しています。これにより、生成されるバイナリからデバッグ情報が正しく取り除かれ、バイナリサイズが削減されることが期待されます。

コミット

commit e50e4f7ec1c25017dc8e906c752375e43a8a38a3
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed Jun 12 06:56:50 2013 +0800

    cmd/ld: supply -s to gcc if -s is passed.
    Fixes #5463.
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/9239045

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

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

元コミット内容

cmd/ld: supply -s to gcc if -s is passed.
Fixes #5463.

変更の背景

Go言語のビルドプロセスにおいて、go build -ldflags="-s"オプションを使用すると、生成される実行可能ファイルからデバッグ情報やシンボルテーブルが除去され、バイナリサイズが削減されます。これは、特にリリースビルドやデプロイメントにおいて、バイナリのフットプリントを小さくするために一般的に行われる最適化です。

しかし、このコミットが修正している問題(Issue 5463)は、Goリンカが外部リンカ(この場合はgcc)を使用する際に、Goの-sオプションがgccに適切に伝達されていなかったことに起因します。その結果、Goリンカはシンボル情報を除去しようとするものの、gccがデバッグ情報を生成するオプション(例えば-gdwarf-2)をデフォルトで有効にしている場合、最終的なバイナリには依然としてデバッグ情報が含まれてしまい、バイナリサイズの削減効果が期待通りに得られないという問題がありました。

このコミットは、この不整合を解消し、Goの-sオプションが指定された場合には、外部リンカにも対応する-sオプションを渡すことで、一貫したシンボル除去の挙動を実現することを目的としています。

前提知識の解説

Goリンカ (cmd/ld)

Go言語のビルドツールチェーンの一部であり、Goのソースコードから生成されたオブジェクトファイルやアーカイブファイルを結合して、実行可能ファイルや共有ライブラリを生成する役割を担います。Goリンカは、Go独自の内部リンキングモードと、C/C++コードとの連携や特定のプラットフォーム要件のために外部リンカ(例: gcc)を使用する外部リンキングモードの両方をサポートしています。

外部リンキング (External Linking)

GoプログラムがC言語で書かれたライブラリ(cgoを使用する場合など)に依存している場合や、特定のシステムライブラリにリンクする必要がある場合、Goリンカは最終的なリンク処理をシステムにインストールされている外部リンカ(通常はgcc)に委ねることがあります。このモードを外部リンキングと呼びます。

シンボル情報とデバッグ情報

  • シンボル情報: プログラム内の関数名、変数名、型名などの識別子と、それらがメモリ上のどこに配置されているかを示す情報です。デバッグ時やスタックトレースの解析時に、人間が理解できる形でプログラムの状態を把握するために不可欠です。
  • デバッグ情報: プログラムの実行フロー、変数の中身、ソースコードの行番号など、デバッグを支援するための詳細な情報です。DWARF (Debugging With Attributed Record Formats) は、Unix系システムで広く使われているデバッグ情報フォーマットの一種です。-gdwarf-2オプションは、GCCがDWARFバージョン2形式のデバッグ情報を生成することを指示します。

-sオプション (Goリンカ)

go build -ldflags="-s"のように指定されるGoリンカのオプションで、生成される実行可能ファイルからシンボルテーブルを完全に除去します。これにより、バイナリサイズが大幅に削減されますが、デバッグが困難になります。

-sオプション (GCC)

GCCのリンカオプションの一つで、生成される実行可能ファイルからすべてのシンボル情報を除去します。これは、Goリンカの-sオプションと目的が似ていますが、異なるツールチェーンのオプションです。

-gdwarf-2オプション (GCC)

GCCのコンパイルオプションの一つで、DWARFバージョン2形式のデバッグ情報を生成することを指示します。このデバッグ情報には、ソースコードの行番号、変数情報、型情報などが含まれ、デバッガがプログラムの実行を詳細に追跡するために使用します。

技術的詳細

このコミットの核心は、Goリンカが外部リンキングを行う際に、Goのビルドオプション(特に-s)を外部リンカに適切に「翻訳」して渡すことです。

変更前は、Goリンカが-sオプションを受け取ると、内部的にはデバッグ情報の生成を抑制しようとしますが、外部リンカに処理を委ねる場合、外部リンカがデフォルトでデバッグ情報を生成するオプション(例: -gdwarf-2)を使用していると、その情報が最終バイナリに含まれてしまう可能性がありました。

このコミットでは、以下の変更が加えられています。

  1. debug_s変数の導入: src/cmd/ld/lib.hEXTERN int debug_s; // backup old value of debug['s']という行が追加されました。これは、debug['s'](Goリンカ内部で-sオプションの状態を管理するフラグ)の元の値を一時的に保存するための変数です。
  2. elf.cでのdebug['s']の一時的な無効化と復元: src/cmd/ld/elf.cdoelf関数内で、linkmode == LinkExternal(外部リンキングモードの場合)のブロックに以下の行が追加されました。
    debug_s = debug['s'];
    debug['s'] = 0;
    debug['d'] = 1;
    
    ここで、debug['s']の現在の値がdebug_sにバックアップされ、その後debug['s']0に設定されます。これは、Goリンカが内部的にシンボル除去を試みる際に、外部リンカに処理を委ねる前にGoリンカ自身のシンボル除去ロジックが干渉しないようにするためと考えられます。debug['d'] = 1;は、デバッグ情報を有効にするフラグですが、これは外部リンカにデバッグ情報の生成を任せるための設定と解釈できます。
  3. lib.cでのgccオプションの条件付き追加: src/cmd/ld/lib.chostlink関数内で、gccに渡すオプションを決定するロジックが変更されました。 変更前:
    if(!debug['s'])
        argv[argc++] = "-gdwarf-2";
    
    変更後:
    if(!debug['s'] && !debug_s) {
        argv[argc++] = "-gdwarf-2"; 
    } else {
        argv[argc++] = "-s";
    }
    
    この変更が最も重要です。
    • !debug['s'] && !debug_s: これは、「Goリンカの現在のdebug['s']フラグがオフ(シンボル除去が指示されていない)であり、かつ、元のdebug['s']フラグもオフであった場合」を意味します。つまり、ユーザーが-sオプションを指定していない場合です。この場合、gccには-gdwarf-2オプションが渡され、デバッグ情報が生成されます。
    • else: 上記の条件が偽の場合、つまり「Goリンカの現在のdebug['s']フラグがオンであるか、または元のdebug_sフラグがオンであった場合」です。これは、ユーザーが-sオプションを指定した場合に該当します。この場合、gccには-sオプションが渡され、gccが生成するバイナリからシンボル情報が除去されます。

このロジックにより、go build -ldflags="-s"が指定された場合、Goリンカはgcc-sオプションを渡し、gccが生成する最終的なバイナリからデバッグ情報が適切に除去されるようになります。

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

src/cmd/ld/elf.c

--- a/src/cmd/ld/elf.c
+++ b/src/cmd/ld/elf.c
@@ -909,6 +909,7 @@ doelf(void)
 	addstring(shstrtab, ".gopclntab");
 	
 	if(linkmode == LinkExternal) {
+		debug_s = debug['s'];
 		debug['s'] = 0;
 		debug['d'] = 1;

src/cmd/ld/lib.c

--- a/src/cmd/ld/lib.c
+++ b/src/cmd/ld/lib.c
@@ -666,8 +666,11 @@ hostlink(void)
 		argv[argc++] = "-m64";
 		break;
 	}
-\tif(!debug['s'])\n+\tif(!debug['s'] && !debug_s) {\n \t\targv[argc++] = "-gdwarf-2"; \n+\t} else {\n+\t\targv[argc++] = "-s";\n+\t}\n \tif(HEADTYPE == Hdarwin)\n \t\targv[argc++] = "-Wl,-no_pie,-pagezero_size,4000000";
 \targv[argc++] = "-o";

src/cmd/ld/lib.h

--- a/src/cmd/ld/lib.h
+++ b/src/cmd/ld/lib.h
@@ -159,6 +159,7 @@ EXTERN	char*\tinterpreter;\n EXTERN	char*\ttmpdir;\n EXTERN	char*\textld;\n EXTERN	char*\textldflags;\n+EXTERN	int	debug_s; // backup old value of debug['s']
 \n enum
 \n {

コアとなるコードの解説

src/cmd/ld/elf.cの変更

doelf関数はELF形式のバイナリを生成する際の主要な処理を行う関数です。この中で、linkmode == LinkExternalのブロックは、外部リンカを使用する場合の処理を定義しています。 追加されたdebug_s = debug['s'];は、Goリンカが内部的に持っている-sオプションの状態(debug['s'])を、新しく導入されたグローバル変数debug_sに退避させています。 その後のdebug['s'] = 0;は、Goリンカ自身のシンボル除去ロジックを一時的に無効にしています。これは、外部リンカにシンボル除去を任せるため、Goリンカが二重に処理を行わないようにするためと考えられます。 debug['d'] = 1;は、デバッグ情報を有効にするフラグですが、これは外部リンカにデバッグ情報の生成を委ねるための設定と解釈できます。

src/cmd/ld/lib.cの変更

hostlink関数は、外部リンカ(gccなど)を呼び出す際のコマンドライン引数を構築する役割を担っています。 変更の核心は、gccに渡すデバッグ関連のオプションを決定する条件分岐です。 変更前は、!debug['s'](Goリンカの-sオプションが指定されていない場合)にのみ-gdwarf-2を渡していました。 変更後は、!debug['s'] && !debug_sという条件が追加されました。これは、Goリンカの現在のdebug['s']フラグがオフであり、かつ、elf.cで退避させた元のdebug_sフラグもオフであった場合、つまりユーザーがgo build -ldflags="-s"を指定していない場合にのみ-gdwarf-2を渡すことを意味します。 elseブロックでは、上記の条件が偽の場合、つまりユーザーが-sオプションを指定した場合に、gcc-sオプションを渡すように変更されています。これにより、gccが生成する最終バイナリからシンボル情報が除去されることが保証されます。

src/cmd/ld/lib.hの変更

lib.hはリンカの内部で使用されるグローバル変数や定数を定義するヘッダファイルです。 EXTERN int debug_s; // backup old value of debug['s']の追加により、debug_sというグローバル変数が宣言され、debug['s']の元の値を保存するための場所が提供されました。これにより、elf.cで値を退避させ、lib.cでその退避された値を利用して適切なgccオプションを選択することが可能になります。

関連リンク

参考にした情報源リンク