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

[インデックス 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としてパッケージ化されたアプリケーションでは、stdoutstderrの両方がデフォルトで/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つの方法でエラーメッセージを出力します。

  1. fprintf(stderr, ...): 従来のstderrへの出力も維持されます。これは、adb shell経由でテストバイナリを実行する場合など、stderrが完全に/dev/nullにリダイレクトされない可能性のあるシナリオを考慮しているためです。ただし、APKからの実行ではこの出力は失われます。
  2. __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_createpthread_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のインクルードが不要になったため削除されています。

関連リンク

参考にした情報源リンク