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

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

コミット

commit 12b990ba7d9969987e79d5d1e5e71e50d2cc2c06
Author: David Crawshaw <david.crawshaw@zentus.com>
Date:   Thu Jul 3 16:14:34 2014 -0400

    cmd/go, cmd/ld, runtime, os/user: TLS emulation for android
    
    Based on cl/69170045 by Elias Naur.
    
    There are currently several schemes for acquiring a TLS
    slot to save the g register. None of them appear to work
    for android. The closest are linux and darwin.
    
    Linux uses a linker TLS relocation. This is not supported
    by the android linker.
    
    Darwin uses a fixed offset, and calls pthread_key_create
    until it gets the slot it wants. As the runtime loads
    late in the android process lifecycle, after an
    arbitrary number of other libraries, we cannot rely on
    any particular slot being available.
    
    So we call pthread_key_create, take the first slot we are
    given, and put it in runtime.tlsg, which we turn into a
    regular variable in cmd/ld.
    
    Makes android/arm cgo binaries work.
    
    LGTM=minux
    R=elias.naur, minux, dave, josharian
    CC=golang-codereviews
    https://golang.org/cl/106380043

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

https://github.com/golang/go/commit/12b990ba7d9969987e79d5d1e5e71e50d2cc2c06

元コミット内容

cmd/go, cmd/ld, runtime, os/user: TLS emulation for android

Based on cl/69170045 by Elias Naur.

There are currently several schemes for acquiring a TLS
slot to save the g register. None of them appear to work
for android. The closest are linux and darwin.

Linux uses a linker TLS relocation. This is not supported
by the android linker.

Darwin uses a fixed offset, and calls pthread_key_create
until it gets the slot it wants. As the runtime loads
late in the android process lifecycle, after an
arbitrary number of other libraries, we cannot rely on
any particular slot being available.

So we call pthread_key_create, take the first slot we are
given, and put it in runtime.tlsg, which we turn into a
regular variable in cmd/ld.

Makes android/arm cgo binaries work.

LGTM=minux
R=elias.naur, minux, dave, josharian
CC=golang-codereviews
https://golang.org/cl/106380043

変更の背景

このコミットは、GoランタイムがAndroidプラットフォーム、特にARMアーキテクチャ上でCGO(C言語との相互運用)バイナリを適切に動作させるための重要な変更を導入しています。主な課題は、Goランタイムがゴルーチン(goroutine)のコンテキストを管理するために使用するgレジスタ(現在のゴルーチンのg構造体へのポインタ)をスレッドローカルストレージ(TLS: Thread Local Storage)に保存・復元する方法が、Androidの環境では既存のスキームでは機能しなかった点にあります。

具体的には、以下の問題がありました。

  • LinuxのTLSスキーム: LinuxではリンカによるTLS再配置(relocation)を利用しますが、Androidのリンカはこの機能をサポートしていません。
  • Darwin(macOS/iOS)のTLSスキーム: Darwinではpthread_key_createを使用してTLSスロットを確保し、固定オフセットでアクセスします。しかし、AndroidではGoランタイムがプロセスのライフサイクルの比較的遅い段階でロードされるため、他の多数のライブラリが既にロードされている可能性があり、特定のTLSスロットが常に利用可能であるとは限りません。これにより、固定オフセットに依存するアプローチは信頼性が低いと判断されました。

これらの制約により、Android上でCGOを介してGoとCのコードが相互に呼び出しを行う際に、gレジスタの保存と復元が正しく行えず、CGOバイナリが動作しないという問題が発生していました。このコミットは、この問題を解決し、Android/ARM環境でのCGOバイナリの互換性を確立することを目的としています。

前提知識の解説

スレッドローカルストレージ (TLS: Thread Local Storage)

TLSは、各スレッドがそれぞれ独立したデータを保持するためのメカニズムです。通常、グローバル変数や静的変数はプロセス内のすべてのスレッドで共有されますが、TLSに格納された変数は、各スレッドがその変数自身のコピーを持ち、他のスレッドのコピーに影響を与えることなくアクセスできます。Goランタイムでは、現在のゴルーチンを示すgレジスタのようなスレッド固有の情報を保存するためにTLSが利用されます。CGO呼び出しのように、GoランタイムがCコードに制御を渡す際、CコードがGoのレジスタを上書きする可能性があるため、Goの重要なレジスタ(特にgレジスタ)をTLSに保存し、Cコードからの復帰後に復元する必要があります。

gレジスタ

Goランタイムにおいて、gレジスタ(ARMアーキテクチャでは通常R10)は、現在実行中のゴルーチン(runtime.g構造体)へのポインタを保持する特別なレジスタです。Goのスケジューラは、このgレジスタを使って現在のゴルーチンのスタック、状態、その他のコンテキスト情報を迅速に参照します。CGOを介してC関数を呼び出す際、CコードはGoランタイムのレジスタ使用規約を知らないため、gレジスタを含むGoのレジスタを上書きしてしまう可能性があります。そのため、C関数を呼び出す前にgレジスタの値をTLSに保存し、C関数から戻った後にTLSから元の値を復元することが不可欠です。

CGO (C Foreign Function Interface)

CGOは、GoプログラムからC言語のコードを呼び出したり、CプログラムからGoの関数を呼び出したりするためのGoの機能です。これにより、既存のCライブラリを利用したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。CGOを使用する際には、GoとCの異なる実行モデル(特にスタック管理やレジスタ使用規約)の間でコンテキストを適切に切り替える必要があります。この切り替えには、前述のgレジスタの保存と復元が伴います。

pthread_key_create

pthread_key_createはPOSIXスレッド(pthreads)ライブラリの一部であり、スレッド固有データ(TSD: Thread-Specific Data)のキーを作成するために使用されます。この関数は、新しいTSDキーを生成し、そのキーを介して各スレッドが独自のデータを関連付けられるようにします。キーが作成されると、pthread_setspecificでスレッド固有の値を設定し、pthread_getspecificでその値を取得できます。このコミットでは、Android環境でgレジスタを保存するためのTLSスロットを動的に確保するためにこの関数が利用されています。

Androidのlibcpthread

AndroidのC標準ライブラリであるBionic libcは、標準的なLinux libc(glibcなど)とは異なる実装を持っています。Bionicはpthreadの一部を実装していますが、完全なlibpthreadを提供しているわけではありません。この違いが、GoランタイムがLinuxやDarwinで採用しているTLSスキームをAndroidに直接適用できない理由の一つとなっています。特に、リンカのTLS再配置のサポートや、pthread_key_createによって割り当てられるTLSスロットの挙動に違いがあります。

技術的詳細

このコミットの核心は、Android環境におけるgレジスタのTLS保存メカニズムを、既存のプラットフォーム固有の制約を回避するように再設計した点にあります。

  1. runtime.tlsgの扱い変更:

    • 従来のLinuxやDarwinでは、runtime.tlsgはリンカによって特別に扱われるTLSシンボルでした。しかし、Androidのリンカがこれをサポートしないため、このコミットではcmd/ld(Goリンカ)がAndroid向けにruntime.tlsgを通常の変数として扱うように変更されました。
    • 具体的には、src/cmd/ld/data.cでは、Androidの場合にR_TLS(TLS再配置)をR_ADDR(通常のアドレス再配置)に変換するロジックが追加されました。
    • src/cmd/ld/elf.cでは、ELFセクションのフラグ設定において、Androidの場合にはSHF_TLSフラグ(TLSセクションを示す)を設定しないように変更されました。
    • src/cmd/ld/symtab.cでは、シンボルテーブルのエントリ作成時に、Androidの場合にSTT_TLS(TLSシンボル)ではなくSTT_OBJECT(通常のデータオブジェクト)としてruntime.tlsgを扱うように変更されました。
  2. 動的なTLSスロットの取得 (inittls関数):

    • Androidでは固定オフセットに依存できないため、pthread_key_createを使用して動的にTLSスロットを確保する新しいアプローチが導入されました。
    • src/pkg/runtime/cgo/gcc_android_arm.cに新しく追加されたinittls関数がこの役割を担います。
    • inittlspthread_key_createを呼び出して新しいキーを取得します。
    • 次に、取得したキーに一時的にmagic1という特定の値を設定します(pthread_setspecific(k, (void*)magic1);)。
    • その後、PTHREAD_KEYS_MAX(通常128)までの範囲で、スレッドローカルストレージのベースポインタからオフセットを走査し、magic1が設定されているスロットを見つけ出します。
    • 見つかったスロットのインデックスにsizeof(void *)を乗算することで、runtime.tlsgが使用すべきTLSオフセットを計算します。
    • このオフセットは、runtime.tlsgが通常の変数として扱われるため、そのアドレスに加算されてgレジスタの値を保存・復元するために使用されます。
    • 最後に、一時的に設定したmagic1をクリアします(pthread_setspecific(k, 0);)。
  3. CGO初期化フローの変更:

    • src/pkg/runtime/cgo/gcc_linux_arm.c(Androidも含む)のx_cgo_init関数が変更され、inittls関数を呼び出すようになりました。これにより、Goランタイムの初期化時にAndroid固有のTLS設定が行われます。
    • src/pkg/runtime/asm_arm.sでは、_rt0_go(Goプログラムのエントリポイント)におけるCGO初期化の呼び出しが変更され、TLSベースポインタやruntime.tlsgのアドレスを引数として渡すようになりました。
    • src/pkg/runtime/tls_arm.sが新しく追加され、Android向けに調整されたruntime·save_gruntime·load_g関数が定義されました。これらの関数は、MRC 15, 0, R0, C13, C0, 3命令(ARMのCP15レジスタからTLSベースポインタを取得)を使用してTLSベースポインタを取得し、その後にruntime.tlsgのアドレス(Androidでは通常の変数として扱われる)を加算して、gレジスタの値を保存・復元します。
  4. ビルドプロセスの調整:

    • src/cmd/go/build.goでは、Android/ARM向けにビルドする際に-pie(Position Independent Executable)フラグをリンカに渡すように変更されました。これは、Androidの実行環境で正確なシンボル解決を行うために必要です。
    • src/cmd/ld/lib.cでは、Androidの場合に強制的に外部リンキング(LinkExternal)を使用するように設定されました。
    • src/pkg/os/user/lookup_stubs.gosrc/pkg/os/user/lookup_unix.goでは、ビルドタグが調整され、Androidプラットフォームでのユーザー情報ルックアップの挙動が適切に制御されるようになりました。

これらの変更により、GoランタイムはAndroidのTLSの特殊性を吸収し、CGOを介したGoとCの相互運用が安定して行えるようになりました。

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

このコミットでは、Goのビルドツール、リンカ、ランタイム、および標準ライブラリの複数のファイルにわたる広範な変更が含まれています。

  • src/cmd/go/build.go:

    • Android/ARMビルド時に-pie(Position Independent Executable)リンカフラグを適用する条件にgoos == "android"が追加されました。
  • src/cmd/ld/data.c:

    • relocsym関数内で、Androidの場合にR_TLS(TLS再配置)をR_ADDR(通常のアドレス再配置)に変換するロジックが追加されました。これにより、リンカはruntime.tlsgを通常の変数として扱います。
  • src/cmd/ld/elf.c:

    • elfshbits関数内で、.tbssセクション(TLS BSSセクション)のELFフラグを設定する際に、Androidの場合にはSHF_TLSフラグ(TLSセクションを示す)を設定しないように変更されました。
  • src/cmd/ld/lib.c:

    • loadlib関数内で、Androidの場合に強制的に外部リンキング(LinkExternal)を使用するように設定されました。
  • src/cmd/ld/pobj.c:

    • 軽微なコードフォーマットの変更。
  • src/cmd/ld/symtab.c:

    • asmelfsym関数内で、Androidの場合にruntime.tlsgシンボルをSTT_TLS(TLSシンボル)ではなくSTT_OBJECT(通常のデータオブジェクト)としてシンボルテーブルに登録するように変更されました。
  • src/pkg/os/user/lookup_stubs.go:

    • ビルドタグにandroidが追加されました。
  • src/pkg/os/user/lookup_unix.go:

    • ビルドタグからandroidが除外され、!android,linuxという条件が追加されました。
  • src/pkg/runtime/asm_arm.s:

    • runtime·save_gruntime·load_gの古い実装が削除されました。
    • _rt0_go内のCGO初期化呼び出しが変更され、TLSベースポインタやruntime.tlsgのアドレスを引数として渡すようになりました。
  • src/pkg/runtime/cgo/cgo.go:

    • Android向けに-llogリンカフラグが追加され、Linux向けの-lpthreadフラグが!android,linuxという条件付きになりました。
  • src/pkg/runtime/cgo/gcc_android_arm.c:

    • 新規ファイルinittls関数が定義され、pthread_key_createを使用して動的にTLSスロットのオフセットを決定するロジックが含まれています。
  • src/pkg/runtime/cgo/gcc_linux_arm.c:

    • x_cgo_init関数のシグネチャが変更され、tlsgtlsbaseのポインタを受け取るようになりました。
    • x_cgo_inittlsが定義されている場合にそれを呼び出すロジックが追加されました。
  • src/pkg/runtime/tls_arm.s:

    • 新規ファイル。Android向けに調整されたruntime·save_gruntime·load_g関数が定義されました。これらの関数は、GOOS_androidマクロに基づいてruntime.tlsgの参照方法を切り替えます。

コアとなるコードの解説

このコミットの最も重要な変更は、Android環境でのgレジスタのTLS管理方法を根本的に変更した点です。

src/pkg/runtime/cgo/gcc_android_arm.cinittls 関数

この新規ファイルに導入されたinittls関数は、AndroidにおけるTLSエミュレーションの心臓部です。

static void
inittls(void **tlsg, void **tlsbase)
{
    pthread_key_t k;
    int i, err;

    err = pthread_key_create(&k, nil);
    if(err != 0) {
        fprintf(stderr, "runtime/cgo: pthread_key_create failed: %d\\n", err);
        __android_log_print(ANDROID_LOG_FATAL, "runtime/cgo", "pthread_key_create failed: %d", err);
        abort();
    }
    pthread_setspecific(k, (void*)magic1); // 一時的にマジック値を設定
    for (i=0; i<PTHREAD_KEYS_MAX; i++) {
        if (*(tlsbase+i) == (void*)magic1) { // マジック値を見つける
            *tlsg = (void*)(i*sizeof(void *)); // オフセットを計算
            pthread_setspecific(k, 0); // マジック値をクリア
            return;
        }
    }
    fprintf(stderr, "runtime/cgo: could not find pthread key\\n");
    __android_log_print(ANDROID_LOG_FATAL, "runtime/cgo", "could not find pthread key");
    abort();
}

この関数は、pthread_key_createを呼び出して新しいスレッド固有データキーを作成します。その後、そのキーに一時的な「マジック値」(magic1)を設定します。そして、tlsbase(スレッドローカルストレージのベースアドレス)からPTHREAD_KEYS_MAXの範囲でメモリを走査し、このマジック値が格納されている場所を探します。マジック値が見つかった場合、その位置からruntime.tlsgが使用すべきTLSオフセットを計算します。この動的なオフセット計算により、Android環境で固定オフセットに依存することなく、gレジスタを保存するためのTLSスロットを確実に特定できるようになります。

リンカの変更 (cmd/ld)

src/cmd/ld/data.csrc/cmd/ld/elf.csrc/cmd/ld/symtab.cにおける変更は、GoリンカがAndroid向けにruntime.tlsgを通常の変数として扱うように強制するものです。これにより、AndroidのリンカがTLS再配置をサポートしないという制約を回避し、Goランタイムが独自のTLSエミュレーションスキームを実装できるようになります。

アセンブリコードの変更 (src/pkg/runtime/asm_arm.s, src/pkg/runtime/tls_arm.s)

src/pkg/runtime/tls_arm.sに新しく追加されたruntime·save_gruntime·load_g関数は、Android環境でのgレジスタの保存と復元を担います。これらの関数は、ARMのCP15レジスタ(MRC 15, 0, R0, C13, C0, 3)からTLSベースポインタを取得し、その後にruntime.tlsgのアドレス(Androidでは通常の変数として扱われる)を加算して、gレジスタの値をTLSに書き込んだり、TLSから読み込んだりします。これにより、CGO呼び出しの前後でgレジスタのコンテキストが正しく維持されます。

これらの変更の組み合わせにより、GoランタイムはAndroidの特殊なTLS環境に適応し、CGOを介したGoとCの相互運用が安定して行えるようになり、Android/ARMプラットフォームでのGoバイナリの機能性が向上しました。

関連リンク

参考にした情報源リンク