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

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

このコミットは、Go言語のツールチェイン(具体的にはcmd/cgocmd/cccmd/ld)において、動的リンカのパスを自動的に検出する機能を追加するものです。これにより、新しいLinuxディストリビューション(特にUbuntu ARMのマルチアーキテクチャ環境)やDebian GNU/kFreeBSDのような、従来のハードコードされたパスとは異なる場所に動的リンカが配置されているシステムでのGoプログラムのビルドと実行を可能にします。

コミット

commit dac4c3eee949ccc395bde808832ab7b2bba370da
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Sat May 5 01:54:16 2012 +0800

    cmd/cgo, cmd/cc, cmd/ld: detect dynamic linker automatically
    Some newer Linux distributions (Ubuntu ARM at least) use a new multiarch
    directory organization, where dynamic linker is no longer in the hardcoded
    path in our linker.
    For example, Ubuntu 12.04 ARM hardfloat places its dynamic linker at
    /lib/arm-linux-gnueabihf/ld-linux.so.3
    
    Ref: http://lackof.org/taggart/hacking/multiarch/
    
    Also, to support Debian GNU/kFreeBSD as a FreeBSD variant, we need this capability, so it's part of issue 3533.
    
    This CL add a new pragma (#pragma dynlinker "path") to cc.
    
    R=iant, rsc
    CC=golang-dev
    https://golang.org/cl/6086043

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

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

元コミット内容

cmd/cgo, cmd/cc, cmd/ld: detect dynamic linker automatically
Some newer Linux distributions (Ubuntu ARM at least) use a new multiarch
directory organization, where dynamic linker is no longer in the hardcoded
path in our linker.
For example, Ubuntu 12.04 ARM hardfloat places its dynamic linker at
/lib/arm-linux-gnueabihf/ld-linux.so.3
Ref: http://lackof.org/taggart/hacking/multiarch/
Also, to support Debian GNU/kFreeBSD as a FreeBSD variant, we need this capability, so it's part of issue 3533.
This CL add a new pragma (#pragma dynlinker "path") to cc.

変更の背景

この変更の主な背景には、以下の2つの主要な問題がありました。

  1. Ubuntu ARMのマルチアーキテクチャ環境への対応:

    • 当時のGoリンカは、動的リンカのパスを/lib/ld-linux.so.2のような特定の場所にハードコードしていました。
    • しかし、Ubuntu 12.04 ARM hardfloatなどの新しいLinuxディストリビューションでは、マルチアーキテクチャ(Multiarch)という新しいディレクトリ構成が導入されていました。
    • マルチアーキテクチャは、異なるCPUアーキテクチャ(例: armhfarm64)向けのライブラリやバイナリを単一のファイルシステム上に共存させるための機能です。これにより、例えばUbuntu 12.04 ARM hardfloatでは、動的リンカが/lib/arm-linux-gnueabihf/ld-linux.so.3のような、アーキテクチャ固有のパスに配置されるようになりました。
    • Goのツールチェインがこの新しいパスを認識できないため、Goでビルドされた動的リンクされたプログラムが正しく実行できない問題が発生していました。
  2. Debian GNU/kFreeBSDのサポート:

    • Debian GNU/kFreeBSDは、GNUユーザーランド(glibcを含む)をFreeBSDカーネル上で動作させることを目的としたDebianの派生版です。
    • この環境でも、動的リンカのパスが従来のLinuxシステムとは異なる可能性があり、Goがこの環境をFreeBSDのバリアントとしてサポートするためには、動的リンカの自動検出機能が必要とされていました。
    • これは、GoのIssue 3533(cmd/cgo: GNU/kFreeBSD support)の一部として議論されていました。

これらの問題に対処するため、Goのツールチェインが動的リンカのパスを柔軟に検出できるよう、新しいメカニズムの導入が不可欠となりました。

前提知識の解説

動的リンカ (Dynamic Linker / Program Interpreter)

動的リンカ(またはプログラムインタプリタ)は、LinuxやUnix系システムにおいて、動的にリンクされた実行可能ファイルが起動される際に、その実行に必要な共有ライブラリ(例: libc.so)をメモリにロードし、シンボル解決を行うプログラムです。実行可能ファイルのヘッダ(ELF形式の場合、PT_INTERPセグメント)に、使用すべき動的リンカのパスが記述されています。

例えば、一般的なLinuxシステムでは/lib/ld-linux.so.2が動的リンカとして指定されています。プログラムが実行されると、カーネルはこのパスに指定された動的リンカをロードし、その動的リンカが実際のプログラムと必要な共有ライブラリをロードして実行を開始します。

マルチアーキテクチャ (Multiarch)

マルチアーキテクチャは、単一のシステム上で異なるCPUアーキテクチャ(例: x86_64i386、またはarmhfarm64)向けのバイナリとライブラリを共存させるためのDebian/Ubuntuの機能です。これにより、例えば64ビットシステム上で32ビットアプリケーションを実行したり、クロスコンパイル環境を構築したりすることが容易になります。

マルチアーキテクチャ環境では、ライブラリのパスがアーキテクチャごとに分離されます。例えば、armhfアーキテクチャのライブラリは/usr/lib/arm-linux-gnueabihf/のようなパスに配置され、動的リンカも同様にアーキテクチャ固有のパスに存在します。

PT_INTERPセグメント

ELF (Executable and Linkable Format) 形式の実行可能ファイルには、プログラムヘッダテーブルが含まれています。このテーブルには、プログラムのロード方法や実行に必要な情報が記述されています。その中の一つにPT_INTERPセグメントがあります。

PT_INTERPセグメントは、プログラムインタプリタ(動的リンカ)のパスを格納するために使用されます。カーネルは、実行可能ファイルをロードする際にこのセグメントを読み取り、指定されたパスの動的リンカをロードして実行を開始します。このコミットの目的は、このPT_INTERPに設定されるべき動的リンカのパスを、環境に応じて適切に決定することにあります。

cgoccld

  • cgo: GoプログラムからC言語のコードを呼び出すためのツールです。Cgoは、CコードをGoコードとリンクするために、Cコンパイラ(cc)とリンカ(ld)を内部的に利用します。
  • cc (C Compiler): C言語のソースコードをコンパイルするツールです。Goのツールチェインでは、CgoがCコードをコンパイルする際に使用されます。
  • ld (Linker): コンパイルされたオブジェクトファイルやライブラリを結合して、実行可能ファイルや共有ライブラリを生成するツールです。Goのリンカは、GoのオブジェクトファイルとCgoによって生成されたCのオブジェクトファイルをリンクします。

技術的詳細

このコミットは、動的リンカの自動検出を実現するために、主に以下の技術的変更を導入しています。

  1. #pragma dynlinker "path"の導入:

    • Cコンパイラ(cmd/cc)に新しいプリプロセッサディレクティブ#pragma dynlinker "path"が追加されました。
    • このプラグマは、Cgoが生成するCコード内で使用され、特定の動的リンカのパスをGoのリンカに伝える役割を果たします。
    • src/cmd/cc/cc.hpragdynlinker関数の宣言とdynlinker変数の定義が追加され、src/cmd/cc/dpchk.cpragdynlinkerの実装が追加されました。この実装は、プラグマで指定されたパスをdynlinker変数に格納します。
    • src/cmd/cc/macbodyでは、macprag関数がdynlinkerプラグマを認識し、pragdynlinker関数を呼び出すように変更されました。
  2. cgoによるELFファイルの.interpセクションの読み取り:

    • src/cmd/cgo/out.goが変更され、Cgoがオブジェクトファイル(.oファイル)を処理する際に、ELF形式のオブジェクトファイルの.interpセクションを読み取るようになりました。
    • .interpセクションには、そのオブジェクトファイルが依存する動的リンカのパスが記述されています。
    • Cgoは、この.interpセクションから動的リンカのパスを抽出し、それを#pragma dynlinker "path"形式で標準出力に出力します。この出力は、後続のGoコンパイラやリンカによって処理されます。
  3. Goリンカ(cmd/ld)でのdynlinker情報の処理:

    • Goリンカ(src/cmd/ld/go.c)が、Cgoから渡される#pragma dynlinker情報を解析し、動的リンカのパスを決定するようになりました。
    • loaddynlinkerという新しい静的関数が追加され、パッケージデータ内のdynlinkerセクションを読み込み、interpreter変数に動的リンカのパスを設定します。
    • リンカは、コマンドラインオプションで-I(インタプリタパスのオーバーライド)が指定されていない限り、この#pragma dynlinkerで指定されたパスを優先的に使用します。
    • 複数の#pragma dynlinkerが異なるパスを指定している場合、リンカは競合を検出し、エラーを報告します。

これらの変更により、Goのツールチェインは、ビルド時にCgoが依存する共有ライブラリの動的リンカパスを自動的に検出し、その情報をGoリンカに渡し、最終的に生成される実行可能ファイルのPT_INTERPセグメントに正しい動的リンカのパスを設定できるようになります。これにより、マルチアーキテクチャ環境やDebian GNU/kFreeBSDのような特殊な環境でも、Goの動的リンクされたプログラムが正しく動作するようになります。

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

このコミットで変更された主要なファイルは以下の通りです。

  • src/cmd/5c/swt.c
  • src/cmd/5l/obj.c
  • src/cmd/6c/swt.c
  • src/cmd/6l/obj.c
  • src/cmd/8c/swt.c
  • src/cmd/8l/obj.c
  • src/cmd/cc/cc.h
  • src/cmd/cc/dpchk.c
  • src/cmd/cc/lexbody
  • src/cmd/cc/macbody
  • src/cmd/cgo/out.go
  • src/cmd/ld/go.c

これらのファイルは、Goのコンパイラ(5c, 6c, 8cはそれぞれPlan 9 CコンパイラのARM, AMD64, x86版)、リンカ(5l, 6l, 8lはそれぞれのアーキテクチャ版)、Cコンパイラ(cc)、Cgoツール(cgo)、およびGoリンカ(ld)に関連するものです。

コアとなるコードの解説

src/cmd/5c/swt.c, src/cmd/6c/swt.c, src/cmd/8c/swt.c (Cコンパイラのバックエンド)

これらのファイルは、GoのCコンパイラ(cmd/cc)のバックエンド部分で、生成されるアセンブリコードやメタデータを出力する役割を担っています。 変更点としては、outcode関数内で、dynexportセクションと同様に、dynlinkerセクションが追加され、dynlinker変数がnilでない場合にその値(動的リンカのパス)がファイルに出力されるようになりました。これは、Goリンカが後で読み取るためのメタデータとして機能します。

// src/cmd/5c/swt.c (同様の変更が6c/swt.c, 8c/swt.cにも適用)
Bprint(&outbuf, "\n$$  // dynlinker\n");
if(dynlinker != nil) {
    Bprint(&outbuf, "dynlinker %s\n", dynlinker);
}

src/cmd/5l/obj.c, src/cmd/6l/obj.c, src/cmd/8l/obj.c (Goリンカのアーキテクチャ固有部分)

これらのファイルは、Goリンカのアーキテクチャ固有の初期化やコマンドライン引数処理を行う部分です。 変更点としては、コマンドラインオプション-Iが指定された場合に、debug['I']フラグをセットする行が追加されました。このフラグは、リンカが#pragma dynlinkerで検出されたパスよりもコマンドラインで指定されたパスを優先すべきかどうかを判断するために使用されます。

// src/cmd/5l/obj.c (同様の変更が6l/obj.c, 8l/obj.cにも適用)
case 'I':
    debug['I'] = 1; // denote cmdline interpreter override
    interpreter = EARGF(usage());
    break;

src/cmd/cc/cc.h (Cコンパイラのヘッダ)

#pragma dynlinkerをサポートするための宣言が追加されました。

// src/cmd/cc/cc.h
void	pragdynlinker(void);
EXTERN	char *dynlinker; // 動的リンカのパスを保持する変数

src/cmd/cc/dpchk.c (Cコンパイラのプラグマ処理)

#pragma dynlinkerの具体的な処理が実装されました。getquoted()関数を使って、プラグマに続く引用符で囲まれた文字列(動的リンカのパス)を抽出し、dynlinker変数に設定します。

// src/cmd/cc/dpchk.c
void
pragdynlinker(void)
{
    dynlinker = getquoted();
    if(dynlinker == nil)
        goto err;

    goto out;

err:
    yyerror("usage: #pragma dynlinker \"path\"");

out:
    while(getnsc() != '\n')
        ;
}

src/cmd/cc/macbody (Cコンパイラのマクロ処理)

Cコンパイラが#pragmaディレクティブを解析する際に、dynlinkerというキーワードを認識し、対応するpragdynlinker関数を呼び出すように変更されました。

// src/cmd/cc/macbody
if(s && strcmp(s->name, "dynlinker") == 0) {
    pragdynlinker();
    return;
}

src/cmd/cgo/out.go (Cgoツール)

Cgoが生成するCコードに、動的リンカのパスを埋め込むための重要な変更が加えられました。 Cgoは、入力として与えられたオブジェクトファイル(obj)がELF形式である場合、その.interpセクションを読み取ります。.interpセクションには、そのELFファイルが依存する動的リンカのパスが格納されています。Cgoはこのパスを抽出し、#pragma dynlinker "path"という形式で標準出力に書き出します。この出力は、Goコンパイラによって処理され、最終的にGoリンカに渡されます。

// src/cmd/cgo/out.go
if f, err := elf.Open(obj); err == nil {
    if sec := f.Section(".interp"); sec != nil {
        if data, err := sec.Data(); err == nil && len(data) > 1 {
            // skip trailing \0 in data
            fmt.Fprintf(stdout, "#pragma dynlinker %q\n", string(data[:len(data)-1]))
        }
    }
    // ...
}

src/cmd/ld/go.c (Goリンカの主要部分)

Goリンカが、Cgoから渡されたdynlinker情報を処理するロジックが追加されました。

  • loaddynlinkerという新しい静的関数が追加されました。この関数は、Cgoが生成したメタデータ内のdynlinkerセクションを解析し、動的リンカのパスを抽出します。
  • 抽出されたパスは、リンカの内部変数interpreterに設定されます。
  • リンカは、コマンドラインで-Iオプションが指定されていない限り、この#pragma dynlinkerで検出されたパスを優先します。
  • 複数のdynlinkerパスが検出された場合、競合をチェックし、異なるパスが指定されていればエラーを報告します。
  • ldpkg関数内で、dynlinkerセクションを読み込むための処理が追加されました。
// src/cmd/ld/go.c
static void loaddynlinker(char*, char*, char*, int); // 新しい関数の宣言

// ldpkg関数内でのdynlinkerセクションの読み込み
p0 = strstr(p1, "\n$$  // dynlinker");
if(p0 != nil) {
    // ... セクションの開始と終了を特定 ...
    loaddynlinker(filename, pkg, p0 + 1, p1 - (p0+1));
}

// loaddynlinker関数の実装
static void
loaddynlinker(char *file, char *pkg, char *p, int n)
{
    // ... dynlinker行を解析 ...
    if(!debug['I']) { // コマンドラインでオーバーライドされていない場合
        if(interpreter != nil && strcmp(interpreter, dynlinker) != 0) {
            // 競合検出
            fprintf(2, "%s: conflict dynlinker: %s and %s\n", argv0, interpreter, dynlinker);
            nerrors++;
            return;
        }
        free(interpreter);
        interpreter = strdup(dynlinker); // interpreter変数を設定
    }
    // ...
}

これらの変更により、Goのビルドシステムは、Cgoを介してCコードをリンクする際に、そのCコードが依存する動的リンカのパスを自動的に検出し、最終的なGoの実行可能ファイルに正しい動的リンカのパスを埋め込むことができるようになりました。これにより、Goプログラムが様々なLinuxディストリビューションやアーキテクチャでより堅牢に動作するようになります。

関連リンク

参考にした情報源リンク