[インデックス 19668] ファイルの概要
このコミットは、Goランタイムのcgo
(C言語との相互運用)部分において、エラー出力のメカニズムを改善するものです。特にLinuxおよびAndroid環境において、従来のfprintf(stderr, ...)
によるエラー出力を、新しく導入されたfatalf(...)
関数に置き換えることを目的としています。これにより、Androidアプリケーションでstderr
が/dev/null
にリダイレクトされる問題に対処し、致命的なエラーが__android_log_print
を通じてログキャットに出力されるようにします。
コミット
commit 72faffbc704f08273b258d8ff868c61b2f1bef7c
Author: David Crawshaw <david.crawshaw@zentus.com>
Date: Thu Jul 3 21:04:48 2014 -0400
runtime/cgo: replace fprintf(stderr, ...) with fatalf(...) for linux/android
Both stdout and stderr are sent to /dev/null in android
apps. Introducing fatalf allows android to implement its
own copy that sends fatal errors to __android_log_print.
LGTM=minux, dave
R=minux, dave
CC=golang-codereviews
https://golang.org/cl/108400045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/72faffbc704f08273b258d8ff868c61b2f1bef7c
元コミット内容
このコミットは、Goランタイムのcgo
サブシステムにおいて、LinuxおよびAndroidプラットフォームでのエラー報告方法を変更します。具体的には、直接fprintf(stderr, ...)
を使用していた箇所を、新しく定義されたfatalf(...)
関数に置き換えます。この変更の主な動機は、Androidアプリケーション環境では標準出力(stdout)と標準エラー出力(stderr)の両方が/dev/null
にリダイレクトされるため、fprintf(stderr, ...)
によるエラーメッセージがユーザーや開発者には見えないという問題に対処することです。fatalf
関数を導入することで、Android固有の実装では致命的なエラーメッセージをAndroidのログシステム(logcat)に送信できるようになり、デバッグとエラー追跡が容易になります。
変更の背景
GoプログラムがCコードと連携するためにcgo
を使用する場合、C側のコードでエラーが発生した際に、そのエラーを適切に報告する必要があります。従来のGoランタイムのcgo
コードでは、C標準ライブラリのfprintf
関数を使ってエラーメッセージを標準エラー出力(stderr
)に書き出していました。
しかし、Android環境でGoアプリケーションが実行される場合、特にAPKとしてパッケージ化されたアプリケーションでは、stdout
とstderr
の両方がデフォルトで/dev/null
にリダイレクトされます。これは、Androidのアプリケーションモデルが、ログ出力に専用のログシステム(logcat)を使用することを前提としているためです。このため、fprintf(stderr, ...)
で出力されたエラーメッセージは、実際にはどこにも表示されず、開発者がアプリケーションのクラッシュや異常終了の原因を特定することが非常に困難でした。
この問題を解決するため、このコミットではfatalf
という新しい関数を導入しました。この関数は、プラットフォームに応じて異なる実装を持ちます。Androidでは、fprintf(stderr, ...)
に加えて、AndroidのネイティブログAPIである__android_log_print
(または__android_log_vprint
)を使用してメッセージをlogcatにも出力するようにします。これにより、Goランタイムのcgo
部分で発生した致命的なエラーが、Androidの標準的なログメカニズムを通じて可視化されるようになります。Linuxのような他のプラットフォームでは、fatalf
は引き続きfprintf(stderr, ...)
を使用しますが、将来的な拡張性や一貫性のために抽象化レイヤーを提供します。
前提知識の解説
cgo
cgo
はGo言語のツールの一つで、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのメカニズムを提供します。これにより、既存のCライブラリをGoプロジェクトで再利用したり、Goでは実装が難しい低レベルの操作を行ったりすることが可能になります。cgo
を使用すると、Goのソースコード内にCのコードを直接記述したり、Cのヘッダーファイルをインポートしたりできます。
fprintf(stderr, ...)
fprintf
はC標準ライブラリの関数で、指定されたファイルストリームにフォーマットされた文字列を書き込みます。stderr
は標準エラー出力ストリームを指すグローバルなファイルポインタです。通常、エラーメッセージや診断メッセージはstderr
に出力されます。多くのUnix系システムでは、stderr
はデフォルトでコンソールに表示されますが、リダイレクトによって他のファイルや/dev/null
に送ることもできます。
Androidのログシステム (logcat)
Androidは、アプリケーションやシステムからのログメッセージを一元的に収集・表示するためのlogcat
というログシステムを持っています。開発者はadb logcat
コマンドを使用してこれらのログをリアルタイムで確認できます。Androidアプリケーションは、android/log.h
で定義されている__android_log_print
や__android_log_vprint
といった関数を使用して、logcatにメッセージを書き込みます。これらの関数は、ログレベル(FATAL, ERROR, WARN, INFO, DEBUG, VERBOSEなど)とタグを指定できるため、ログのフィルタリングや重要度に応じた管理が容易になります。
pthread_key_create
pthread_key_create
はPOSIXスレッド(pthreads)ライブラリの関数で、スレッド固有データ(Thread-Local Storage, TLS)のためのキーを作成します。TLSは、各スレッドが自分自身のデータコピーを持つことを可能にするメカニズムです。例えば、Goランタイムでは、各Goルーチン(Goのスレッドのようなもの)が独自のTLSを持つことで、グローバル変数へのアクセス競合を避けることができます。pthread_key_create
が失敗すると、通常はメモリ不足などの深刻な問題を示します。
abort()
abort()
はC標準ライブラリの関数で、現在のプログラムの異常終了を引き起こします。通常、回復不可能なエラーが発生した場合に呼び出され、プログラムは即座に終了し、多くの場合コアダンプファイルを生成します。これは、プログラムがこれ以上安全に実行を継続できないことを示すために使用されます。
va_list, va_start, va_end
これらはC言語の可変引数リストを扱うためのマクロです。
va_list
: 可変引数リストを保持するための型です。va_start(ap, format)
:ap
を初期化し、format
の次の引数から可変引数リストへのアクセスを開始します。va_end(ap)
:ap
をクリーンアップし、可変引数リストへのアクセスを終了します。 これらは、printf
のような可変引数を取る関数を自分で実装する際に使用されます。
技術的詳細
このコミットの核心は、fatalf
という新しい関数の導入とそのプラットフォームごとの実装です。
fatalf
関数の役割
fatalf
関数は、Goランタイムのcgo
部分で発生した致命的なエラーを報告し、プログラムを終了させるための統一されたインターフェースを提供します。これにより、エラー報告のロジックが抽象化され、プラットフォーム固有の要件(例: Androidのlogcat)に柔軟に対応できるようになります。
Android向けfatalf
の実装 (src/pkg/runtime/cgo/gcc_android.c
)
Android環境では、fatalf
は以下の2つの方法でエラーメッセージを出力します。
fprintf(stderr, ...)
: 従来のstderr
への出力も維持されます。これは、adb shell
経由でテストバイナリを実行する場合など、stderr
が完全に/dev/null
にリダイレクトされない可能性のあるシナリオを考慮しているためです。ただし、APKからの実行ではこの出力は失われます。__android_log_vprint(ANDROID_LOG_FATAL, "runtime/cgo", format, ap)
: AndroidのネイティブログAPIを使用して、メッセージをlogcatに書き込みます。ANDROID_LOG_FATAL
レベルでログを記録することで、このメッセージが致命的なエラーであることを示し、logcatで容易に識別できるようにします。タグは"runtime/cgo"
が使用され、Goランタイムのcgo部分からのログであることを示します。
両方の出力を行った後、abort()
を呼び出してプログラムを即座に終了させます。
Linux向けfatalf
の実装 (src/pkg/runtime/cgo/gcc_fatalf.c
)
Android以外のLinux環境(!android,linux
ビルドタグで制御)では、fatalf
はよりシンプルな実装になります。ここでは、fprintf(stderr, ...)
を使用してエラーメッセージを標準エラー出力に書き込み、その後abort()
を呼び出してプログラムを終了させます。この実装は、従来のfprintf(stderr, ...)
とabort()
の組み合わせをfatalf
という単一の関数にカプセル化したものです。
既存コードの変更
Goランタイムのcgo
関連ファイル(gcc_android_arm.c
, gcc_linux_386.c
, gcc_linux_amd64.c
, gcc_linux_arm.c
など)では、pthread_key_create
やpthread_create
の失敗時など、致命的なエラーが発生した場合に直接fprintf(stderr, ...)
とabort()
を呼び出していた箇所が、新しく定義されたfatalf
関数への呼び出しに置き換えられています。これにより、コードの重複が減り、エラー報告ロジックが一元化されます。
libcgo.h
の更新
libcgo.h
ヘッダーファイルには、fatalf
関数のプロトタイプが追加され、この関数がcgo
の内部で使用できることが宣言されています。コメントには「Prints error then calls abort. For linux and android.」と記載されており、この関数の目的と対象プラットフォームが明確にされています。
この変更により、Goランタイムの堅牢性が向上し、特にAndroid環境でのデバッグ体験が改善されます。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが変更されています。
src/pkg/runtime/cgo/gcc_android.c
: 新規ファイル。Android向けのfatalf
関数の実装が含まれます。src/pkg/runtime/cgo/gcc_android_arm.c
: 既存のfprintf(stderr, ...)
と__android_log_print
の呼び出しがfatalf
に置き換えられています。src/pkg/runtime/cgo/gcc_fatalf.c
: 新規ファイル。Android以外のLinux向けのfatalf
関数の実装が含まれます。src/pkg/runtime/cgo/gcc_linux_386.c
:fprintf(stderr, ...)
の呼び出しがfatalf
に置き換えられています。src/pkg/runtime/cgo/gcc_linux_amd64.c
:fprintf(stderr, ...)
の呼び出しがfatalf
に置き換えられています。src/pkg/runtime/cgo/gcc_linux_arm.c
:fprintf(stderr, ...)
の呼び出しがfatalf
に置き換えられています。また、x_cgo_inittls
の宣言位置が変更されています。src/pkg/runtime/cgo/libcgo.h
:fatalf
関数のプロトタイプが追加されています。
コアとなるコードの解説
src/pkg/runtime/cgo/gcc_android.c
(新規ファイル)
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include <stdarg.h>
#include <android/log.h> // Android固有のログヘッダー
#include "libcgo.h"
void
fatalf(const char* format, ...)
{
va_list ap;
// Write to both stderr and logcat.
//
// When running from an .apk, /dev/stderr and /dev/stdout
// redirect to /dev/null. And when running a test binary
// via adb shell, it's easy to miss logcat.
fprintf(stderr, "runtime/cgo: "); // stderrにも出力
va_start(ap, format);
vfprintf(stderr, format, ap); // 可変引数をstderrに出力
va_end(ap);
fprintf(stderr, "\n");
va_start(ap, format);
__android_log_vprint(ANDROID_LOG_FATAL, "runtime/cgo", format, ap); // logcatに出力
va_end(ap);
abort(); // プログラムを異常終了
}
このファイルは、Androidプラットフォーム向けのfatalf
関数の実装を提供します。fprintf(stderr, ...)
で標準エラー出力にメッセージを書き込むだけでなく、__android_log_vprint
を使用してAndroidのlogcatにも同じメッセージをANDROID_LOG_FATAL
レベルで出力します。これにより、Androidアプリケーションがstderr
を/dev/null
にリダイレクトしている場合でも、エラーメッセージがlogcatを通じて開発者に届くようになります。最後にabort()
を呼び出してプログラムを終了させます。
src/pkg/runtime/cgo/gcc_fatalf.c
(新規ファイル)
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !android,linux // Android以外のLinux環境でビルドされることを示すビルドタグ
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include "libcgo.h"
void
fatalf(const char* format, ...)
{
va_list ap;
fprintf(stderr, "runtime/cgo: "); // stderrに出力
va_start(ap, format);
vfprintf(stderr, format, ap); // 可変引数をstderrに出力
va_end(ap);
fprintf(stderr, "\n");
abort(); // プログラムを異常終了
}
このファイルは、Android以外のLinuxプラットフォーム向けのfatalf
関数の実装です。Android版とは異なり、__android_log_vprint
の呼び出しはなく、純粋にfprintf(stderr, ...)
でエラーメッセージを標準エラー出力に書き込み、その後abort()
を呼び出してプログラムを終了させます。これは、従来のGoランタイムのエラー報告動作をfatalf
関数としてカプセル化したものです。
既存ファイルの変更例 (src/pkg/runtime/cgo/gcc_android_arm.c
の抜粋)
--- a/src/pkg/runtime/cgo/gcc_android_arm.c
+++ b/src/pkg/runtime/cgo/gcc_android_arm.c
@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-#include <android/log.h> // この行が削除されている
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
@@ -28,9 +27,7 @@ inittls(void **tlsg, void **tlsbase)
err = pthread_key_create(&k, nil);
if(err != 0) {
-\t\tfprintf(stderr, "runtime/cgo: pthread_key_create failed: %d\\n", err);
-\t\t__android_log_print(ANDROID_LOG_FATAL, "runtime/cgo", "pthread_key_create failed: %d", err);
-\t\tabort();
+\t\tfatalf("pthread_key_create failed: %d", err); // fatalfへの置き換え
}
pthread_setspecific(k, (void*)magic1);
for (i=0; i<PTHREAD_KEYS_MAX; i++) {
@@ -40,9 +37,7 @@ inittls(void **tlsg, void **tlsbase)
return;
}
}
-\t\tfprintf(stderr, "runtime/cgo: could not find pthread key\\n");
-\t\t__android_log_print(ANDROID_LOG_FATAL, "runtime/cgo", "could not find pthread key");
-\t\tabort();
+\t\tfatalf("could not find pthread key"); // fatalfへの置き換え
}
void (*x_cgo_inittls)(void **tlsg, void **tlsbase) = inittls;
この例では、pthread_key_create
の失敗時やpthread
キーが見つからない場合に、以前は直接fprintf(stderr, ...)
と__android_log_print
、そしてabort()
を呼び出していた箇所が、新しく導入されたfatalf
関数への単一の呼び出しに置き換えられています。これにより、コードが簡潔になり、エラー報告ロジックの変更が容易になります。また、android/log.h
のインクルードが不要になったため削除されています。
関連リンク
- Goの公式リポジトリでのコミット: https://github.com/golang/go/commit/72faffbc704f08273b258d8ff868c61b2f1bef7c
- Go Code Review: https://golang.org/cl/108400045
参考にした情報源リンク
- Go Programming Language Documentation (cgo): https://go.dev/blog/c-go-is-not-c (cgoの一般的な情報)
- Android Developers Documentation (Logcat): https://developer.android.com/tools/logging (Androidのログシステムに関する情報)
- POSIX Threads (pthreads) documentation (pthread_key_create): https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_key_create.html (pthread_key_createに関する情報)
- C Standard Library (fprintf, abort): https://en.cppreference.com/w/c/io/fprintf (fprintfに関する情報)
- C Standard Library (va_list, va_start, va_end): https://en.cppreference.com/w/c/language/variadic (可変引数に関する情報)