ものがたり(旧)

atsushieno.hatenablog.com に続く

XamarinがMonoの権利をほぼ全て獲得

暇なのでふらふらと渡米してボストンに遊びに来ています。いや元々4月くらいから行く予定だったんですけど。

XamarinがNovell/Attachmateからmonoと関連製品に関するライセンスをサポート契約付きで譲り受けました。これはすごいニュース。
http://tirania.org/blog/archive/2011/Jul-18.html
http://blog.xamarin.com/2011/07/18/first-press-release/

今回の話を簡潔にまとめると、NovellはAttachmateに買収されて、AttachmateはMonoチームを(わたしを含め)クビにしてリソースをほぼゼロにしたのだけど、Monoの権利は引き続き保持していたわけです。Hudsonの権利を持ち続けるOracleみたいな状態だったわけですね。それが今回、SUSEのライセンス購入者にも引き続きMonoのサポートを提供するという条件で、Monoのビジネスをほぼこれまで通りに継続できるかたちで譲り受けることに成功した、というわけです。*1

今回の交渉を成功させたのはCEOのNat。おそろしい手腕の持ち主です。AttachmateでMonoをメンテナンスすることになっていたのはSUSEで(まあ明らかに不可能だったでしょう*2)、彼らの一部は "very emotional" だったそうですが*3、今回のような誰も損をしない条件を出すことで、上手くまとめあげたわけですね。特に、MonoTouchやMono for Androidの有償サポートは損害賠償訴訟にも繋がりうるもので、そのリスクを冒してまでライセンスを持っている理由は、Attachmateには無かったことでしょう。

一方的にレイオフされても全くへこたれたり怨恨に流されることなく、ほんの2ヶ月で会社を立ち上げて、オフィスをセットアップして、雇用を確保して、Attachmateと対等のパートナーとしてビジネスを成功させているXamarinのポテンシャルには、アウトサイダーとして感嘆させられるばかりです。

今週末にはMonospace Conferenceも開かれるのですが、(レイオフで差し支えて全員ではありませんが)多くのチームメンバーが参加することになるでしょう。

あ、ちなみにわたしは8月からXamarinにjoinしてAndroid製品の開発に参加する予定です。

*1:ほぼ、というのは、これまでの顧客サポート用のバグ情報などは引き継げない、とか、細かい部分の話です。

*2:一般的なLinux系のコンポーネントと全然違うし、C#読み書き出来ないだろうし

*3:好ましくないことですが、あちら側には、KDE陣営にあって、GNOMEを設立した彼らの失敗を望む者もいたということでしょう

BridJ on Androidでネイティブコードを活用する

これは最近いじっていたBridJというライブラリについてまとめたものです。本当はもうちょっと体裁を整えてどっかに記事として投げられないかなあと思っていたんですが、そこに時間を割くのももったいないかなと思ってやめました。なわけで文体が違和感ありありですがそこは気にしない^2

      • -

AndroidとJNI

Javaでは、ネイティブコードを呼び出す方法として、JNI (Java Native Interface)が提供されています。JNIは、JavaC/C++で書かれたネイティブコードを繋ぐものです。具体的には、Javaでnativeキーワードを使用して定義したメソッドを入り口から、対応するCあるいはC++のコードを(プラットフォーム固有の)共有ライブラリとしてロードして実行できます。

JNIで使用する共有ライブラリとしては、Windowsであれば.dll、Mac OS Xであれば.dylib、Linuxであれば.soの各種ファイルが利用できます。JNI用のライブラリは、javahというツールにjavaのソースを渡すことで、Cのヘッダファイルを生成し、その関数を実装することで作成できます。

$ cat name/atsushieno/Test.java
package name.atsushieno;

public class Test
{
static native long dlopen (String libFileName, int mode);
static native int dlclose (long handle);
}

$ javac -d target name/atsushieno/Test.java

$ javah -classpath target name.atsushieno.Test

$ cat name_atsushieno_Test.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class name_atsushieno_Test */

#ifndef _Included_name_atsushieno_Test
#define _Included_name_atsushieno_Test
#ifdef __cplusplus
extern "C" {
#endif
/* (snip) */
JNIEXPORT jlong JNICALL Java_name_atsushieno_Test_dlopen
(JNIEnv *, jclass, jstring, jint);

/* (snip) */
JNIEXPORT jint JNICALL Java_name_atsushieno_Test_dlclose
(JNIEnv *, jclass, jlong);

#ifdef __cplusplus
}
#endif
#endif

JNIはAndroid仮想マシンであるdalvikでも利用できます。AndroidLinuxベースのプラットフォームであり、Android NDKというgccベースの開発環境を使用することで、共有ライブラリ (.so) を生成できます。Android NDKを利用して作成したライブラリは、プラットフォームに依存するので、各プラットフォーム向けにライブラリをビルドすることが望ましいです。公式にAndroid NDKに含まれるプラットフォームには、(大まかに言えば)armとx86が存在します。非公式にはMIPS用もあるようです。

以上のようにざっと説明すると簡単にも聞こえますが、実際にはそれほど簡単ではありません。javahで生成されるヘッダに含まれる関数は、ネイティブライブラリで実装されているコードに直接対応するものではないので、自分で実装しなければなりません(そうすることで、Sun/Oracleは、動的にライブラリを呼び出す際の煩雑な問題の幾ばくかをユーザーの責務としています)。さらに、Androidの場合、Android NDKを利用するプロジェクトの多くはndk-buildというビルドスクリプトを呼び出します。これはAndroid.mkという独自のMakefileを使用するもので、ユーザーはその書き方・使い方を覚えなければなりません(ndk-buildを使わない場合でも、NDKのツールチェインを正しくセットアップしてコンパイルするのは少々面倒ですが)。

しかし、Androidのネイティブコードには、大きな可能性があります。ひとつはパフォーマンスの大幅な改善です。Android 2.2以降はdalvikにJIT (Just-in-Time) コンパイラが搭載されるようになって、ネイティブに近い速度が出せるようになったコードもありますが、まだ追いつかない部分はあります。例えばネイティブで直接メモリ管理するコードには対抗できないでしょう。また、既存のネイティブコードが再利用できることは大きなメリットです。

というわけで、わたしはAndroidでもネイティブコードを積極的に活用できることが望ましいと考えています。そのために必要なステップとして、JNI用のCコードを書かずに利用できる(それによってネイティブコードを簡単に使える)、実用的な動的ライブラリ呼び出し環境が必要だと考えました。

AndroidとJNIについては、以下も参考になるでしょう(古い記事はNDKの使い方が古いこともありますが):

JNA、様々な類似プロジェクト、そしてBridJ

さて、Cコードを書かずにネイティブコードを呼び出す試みは、実のところSun Microsystemsでも行われていました。JNA (Java Native Access)です。これは、.NET FrameworkにおけるP/Invokeの機能にならって、Cコードを書かずに動的なネイティブライブラリの呼び出しを可能にしようというものです。JNAはlibffiを利用してC++とのinteroperabilityを実現しているようです。これでも利便性は向上しています。

JNAについてはこちらの記事が導入として参考になるでしょう: JNIより簡単にJavaとC/C++をつなぐ「JNA」とは

ただ、JNAはJava 1.4の時代に設計されたもので、Java 1.5のプリミティブ型やジェネリクスなど、有用な機能を活用できていません。Javaにはポインタ型が存在しないので、ポインタに相当するIntByReferenceやByteByReferenceといった型を使用してポインタを表現しますが、ジェネリクスが無いので、これらは全てByReferenceから派生する別々の型になります。ライセンスもLGPLで、組み込み用途によっては利用できないかもしれません。

JNAがいろいろと不便だったこともあって、JNAとは別に、さまざまなネイティブコード呼び出しのフレームワークが開発されてきました(JNIDirectJ/InvokeJavaCPPHawtJNIJavolutionなど)。それぞれに独自の利点がありますが、その中でわたしが特に興味をもったのがBridJというプロジェクトです。これは、JNAを起点としつつ、さらにC++の利用可能性を高め、Java 1.5のジェネリクスアノテーションを活用し、ライセンスもMIT/X11ベース、現在も活発に開発されています。わたしはこれに魅力を感じ、開発に協力することにしました。

ちなみにわたし自らも当初、Androidでも利用できるようなJNAとAPI互換の独自実装を作成しようとしていました。そのためには、特にJavaメソッドのさまざまな引数群をC関数の適切な引数型に変換した上で、Cの関数に引数として渡す機能が必要です。これを実現する標準的なCの方法は存在しないため、わたしは、次善の策として、これをプラットフォームやコンパイラフレームワーク別に実現するdyncallというライブラリを活用することを思いつき、libdlのdlopen関数と組み合わせて、任意の関数を実行する機能のプロトタイプを実装しました(dyncallはARMアーキテクチャgccのC呼び出しに対応しています)。BridJは、このdyncallを調べていた時に発見したもので、つまりBridJもわたしと同様のアプローチで関数呼び出しを実装しています。

JNAerator

BridJはNativeLibs4Javaというプロジェクトの一部であり、NativeLibs4Javaにはもうひとつ、JNAeratorという興味深いサブプロジェクトがあります。これは、Cのヘッダを解析して、前述のJNAに対応するJavaのライブラリを自動生成してしまおうというもので、BridJにも対応しています。JNAeratorを使えば、JNIのCコードを書くどころか、JNAで必要となるjava側のコードすら手書きする必要がなくなるのです。これはとても便利です。

JNAeratorは id:syuu1228:20100316:1268737011 - miniupnpc for JavaとJNAeratorのお話 - でも紹介されています。

(.NETのP/Invokeには、P/Invoke Signature Generatorというツールがあるようですが、実用的かどうかはわたしは評価していないのでコメントできません。わたしが.NET用でJNAeratorに最も近いと思ったのは、Gtk#におけるgapiと呼ばれる一連の自動生成ツールです。)

JNAeratorはJava Web Startアプリケーションとして実行できます。

わたしは自分のAndroidアプリケーションで、libcのdirent.hに含まれる関数を使いたいと考え、dirent.hをJNAeratorに渡して、生成されたjavaソースをプロジェクトに追加して呼び出してみました。後述する問題を修正するだけで、簡単にbionic libcの機能を呼び出すことができました。ユーザーは、生成されたライブラリの関数を呼び出すだけです。

特にBridJと組み合わせた場合、JNAeratorは、未知の型を単純にメンバーなしのinterfaceとして定義してしまい、それをポインタで利用する関数ではPointer型を経由してそのインターフェースを参照します。Cヘッダは何層にもインクルードしていることが多いですが、多くの場合、同じヘッダファイル内で必要とされる型で未定義のものは構造体へのポインタとして参照されているでしょう(たとえば、Cの標準ファイルI/Oを使用する時は、FILE型の詳細に触れることなくFILE*を使用するのが一般的です)。ポインタはPointer<FooBar>として自動生成され、それら未知の型を全てJNAeratorに渡さなくても、必要なものだけ渡すことで、型の内部構造には踏み込まずに、Cの関数を呼び出すことができるのです。

もちろん、必要であれば別途CヘッダをJNAeratorに渡して型を生成することも出来ます。後からダミーのinterfaceを実際の型に置き換える作業も、大規模なインポートを行うのでなければ、それほど困難ではないでしょう。

Dalvikのコード生成制約(解決済)

さて、BridJは便利ですが、Android上で実行できるようになるには大きな制約がありました。それはクラスコードの自動生成の制約です。

BridJでは、Cの関数ポインタに対応する関数コールバッククラスを生成して、それをユーザーが派生させて(その型の関数に対応する)メソッドをオーバーライドすることで、ユーザーコードを関数ポインタとして呼び出せるようにしています。この関数コールバッククラスの生成部分では、java.lang.ClassLoaderクラスのdefineClass()というメソッドを利用しているのですが、このメソッドがAndroidAPIでは実装されていません(UnsupportedOperationExceptionが投げられます)。

dalvik VMレジスタマシンであり、dexという独自のバイトコードのみがサポートされています。スタックマシンであるJVMが使用するJavaバイトコードは、あくまでアプリケーションのビルド時にdexに変換された上でapkに含まれるようになっています。そして、dexを実行時にロードすることはできても、Javaのクラスを動的にロードする機能は(前述の通り)実装されていません。このため、Androidでは動的なクラスの生成が出来ないとされています。

この問題を解決するため、例えばClojureには独自のdex変換サポートが含まれています。これは、dxというAndroid SDKに含まれるツールのソースを再利用することで実現されています。dxにはCfTranslatorというクラスがあり、これがjavaバイトコードをdexに変換するようです。BridJでも、とりあえずこのdxの機能を利用してdalvik用のコードを生成する機能を実装することにしました。

ちなみに、BridJでは、バイトコード生成にはASMというJavaのプロジェクトを利用しています。これは現在のところJVMバイトコードの生成しかサポートしていませんが、これがもしdexバイトコードを生成できるようになれば、BridJからもだいぶシームレスにコード生成が行えるようになるでしょう(し、Closureのような動的言語におけるニーズを満たすこともできるかもしれません)。ただ、設計思想の相違を乗り越えなければならないので、実現は難しいかもしれません。

BridJの制約(2011年6月現在)

BridJはまだバージョン1.0リリースにも到達していない、比較的若いプロジェクトであり、まだ少々制約があります。大きな問題としては、構造体の値渡しによる関数呼び出しがサポートできていません。多くのCライブラリでは構造体へのポインタを利用しているでしょうが、構造体引数が利用できないのでは困る場面も少なくないでしょう。ここはJNAでは実現していることであり、BridJでも今後なんらかのかたちで実装出来ないだろうかと思っています。

また、Android-x86に対応したビルドはまだ出来ていません。これは誰も試していないというのが実態なので、もしかしたら簡単にできるかもしれませんし、BridJが依存しているdyncallを修正しなければならないかもしれません。

BridJの制約にぶつかってどうしようもない場合は、それでもJNIを併用することが可能ですし、C/C++でポインタベースのライブラリ関数を作成して、それをBridJで呼び出すようにするのも良いでしょう。

Android NDKでネイティブライブラリをビルド

さて、ここからは具体的にBridJを利用するまでの手順を説明していこうと思います。大まかに分けると、以下のような流れになります。

まず、ネイティブライブラリを利用するには、Android NDKを使用してネイティブライブラリをビルドする必要があります。ただし、Android本体に含まれるネイティブライブラリについては、その必要はありません。Android NDKでは、Androidのバージョンごとにネイティブライブラリとして利用可能なものが決められています。AOSP (オープンソースAndroid)に含まれていても実機によって含まれていないものがあり得るので、Android NDKに含まれるものだけを利用するのが正統とされていることに注意してください。

Android NDKには、いわゆるGNU compiler collection (GCC)のツールが含まれています。Android NDKが特殊なのは、libcの実装としてbionicという独自の標準Cライブラリが使用されていることです(ライセンスの関係でリベラルなものがフルスクラッチで実装されています)。

これらのツールの標準的な使い方については、NDKのドキュメントを読むべきですが、(あくまで参考として)以下のようなconfigureオプションによるmakeと手動libcリンクによって、いわゆるautotoolsによるビルドシステムでもビルドできることもあります。


# ./configure --host=arm-eabi CC=arm-eabi-gcc CPPFLAGS="-I$NDK_ROOT/platforms/android-9/arch-arm/usr/include/" CFLAGS="-nostdlib" LDFLAGS="-Wl,-rpath-link=$NDK_ROOT/platforms/android-9/arch-arm/usr/lib/ -L$NDK_ROOT/platforms/android-9/arch-arm/usr/lib/" LIBS="-lc "
# make
# arm-eabi-gcc -nostdlib -shared -s -o YOUR_LIBRARY.so --whole-archive -Wl,-whole-archive YOUR_PROJECT_SOURCES/.libs/YOUR_LIBRARY.a -Wl,-no-whole-archive --no-whole-archive -L $NDK_ROOT/platforms/android-9/arch-arm/usr/lib -lc

Android NDK r5cを前提としています / autotoolsとNDKについてはこちらが参考になります: Building Open Source libraries with Android NDK

ただし、Unix環境向けに書かれた大抵のライブラリには依存ソフトウェアがあり、また暗黙的にglibcに依存していてbionicではビルド出来ないものも存在します。

(JNIの標準的な方法によらずに)ビルドしたネイティブライブラリは、apkパッケージの中で、lib/armeabi などのディレクトリに.soを含める必要があります(Javaプロジェクトであれば、プロジェクトのトップディレクトリに上記のディレクトリを作成して.soをコピーします)

JNAeratorStudioの使い方

BridJを利用する最も簡単な方法は、JNAeratorを利用してネイティブライブラリのバインディングを自動生成してしまうことです。ここでは、コードの自動生成を通じて、生成されたライブラリの構造に軽く言及しつつ、使い方に主眼を置いて説明することにします。

まず JNAeratorStudio を起動しましょう。Java Web Startが動作する環境がセットアップされていれば、JNAeratorのプロジェクトページにあるリンクから起動できます。

何回かJava Web Startから問い合わせられるアクセス要求を承諾して、JNAeratorStudioが立ち上がったら、以下の作業を行います。

  • Runtime のリストボックスで "BridJ (...)" を選択する。
  • Library Name のテキストボックスに、呼び出すCのライブラリ名を入力する。これはそのままパッケージ名にも使用されますが、名前に . (dot)を含めると生成コードがそのまま使えないので、区切りのない名前にしておいた方が良いでしょう。
  • 利用したいライブラリのCヘッダを貼りつける。(残念ながら、現状では1つのテキストエリアにしか対応していません。)
  • "Ready to JNAerate" と書かれた画面最下部にあるボタンを押す(凹んでいるので分かりにくいですが)。

以下は Unixのdirent.hを渡す場合の例です。

ファイルの生成には(特に初回は)時間がかかるので、しばらく待ちます。

変換が完了すると、複数のjavaソースファイルがjarにまとめられてローカルの一時ファイルとして保存されます。"Show JAR" ボタンを押して内容を表示して解凍するなり、JNAerated classes のリストボックスでファイルを選択して内容を確認するなりします。

生成されたライブラリには、構造体などの型に対応するクラスと、グローバルな(例えばCの)関数に対応するXxxxLibraryというクラスがあります。

それぞれに @Library というアノテーションが付いていますが、これは対応する定義が含まれているとされるネイティブライブラリの名前となります。ですので、パッケージ名とCのライブラリ名が異なる場合は、これらを全て書き換えなければなりません。わたしの場合、dirent.h に対応するバインディングを作成するために dirent という名前を設定しましたが、これはlibc.soに含まれるので @Library ("c") に書き換えました。

構造体に対応するクラスは、StructObjectクラスから派生することになり、構造体のメンバーフィールドには @Field というアノテーションが付けられ、これにはメンバーのC構造体における位置が渡されています(位置であり、メモリ上のサイズに基づくオフセットではありません)。ここで、もしJNAeratorがメンバーの型定義を解決できないフィールドがあった場合には、そのメンバーフィールドの生成がスキップされてしまいます。しかしそれらが足りないと、その型の非ポインタ値を利用するコードが正しく動作しない(メンバーの読み取りや書き込みに失敗する)ので、JNAeratorに必要な型情報を補充するか、生成されたコードに手を加える必要があります。そうしないと、正しく読み取り・書き込みが行われず、実行時に例外が発生することになります。

グローバル関数を全て定義した XxxxLibrary クラスには、「ランタイム」を定義する @Runtime というアノテーションが付いて、さらにC関数の呼び出しに対応するJava関数を定義しています。ここでは @Runtime にはBridJの CPPRuntime.class が渡されています。関数は定義上はstatic nativeメソッドとなっており、その呼び出しが実行時に処理されます。実際にはこのクラスのstaticコンストラクターでBridJ.register()というメソッドが呼び出されており、この中でネイティブライブラリがロードされ、System.loadLibrary()の呼び出しで定義された*BridJの*ネイティブライブラリが呼び出されて、その内部処理において実際に呼び出されるライブラリのC関数が適宜マッピングされる、という仕組みになっています。

BridJの使い方(の基本)

さて、今度はJNAeratorで生成されたコードをJavaのプロジェクトに組み込んで、それを呼び出します。といっても、XxxxLibrary クラスで定義されたstatic nativeメソッドを呼び出すだけです。

実際にはBridJの呼び出しにはJNAeratorで生成されたコードである必要は無く、@Libraryと@RuntimeをもちBridJ.register()を呼び出したクラスがnativeとして定義されたC関数を呼び出すだけで足りるはずですが、JNAeratorで生成したものを使う方が簡単でしょう。

メソッドの引数は、Cの基本型については概ね直感的に渡せます。Stringの渡し方はchar*, wchar_t*など、複数の種類があるので、適宜使い分ける必要があります。Stringをchar*にするには、Pointer.pointerToCString(String)というstaticメソッドを利用します。ポインタ変数を渡すには、Pointer.pointerTo(Object) というstaticメソッドを利用します。

(構造体の値渡しを含む関数は、2011年6月の時点でサポートされていないので、注意してください。BridJが正しくシンボル解決せず、ネイティブ関数とのマッピングの過程でエラーが発生し、呼び出しにも失敗します。)

逆に、メソッドの戻り値がPointerである場合など、Pointerを個別のJava型に変換する場合は、PointerクラスのgetXxx()インスタンスメソッドを利用します。getNativeObject(Class)が汎用的に利用できます。

Pointer型はジェネリッククラスであり、JNAのようにプリミティブ型に特化したXxxxByReference クラスよりもずっと直感的に使用できます。

追記: 重要: eclipse ADTのプロジェクトでBridJを参照する場合は(大抵の人がそうするかと思いますが)、bridj-0.5-android.jar をandroidプロジェクトで参照するだけではエラーになってしまいます。これは既知のeclipse ADTの問題です。eclipseから参照する場合は、いったんbridj-0.5-android.jarからlib/armeabi/libbridj.soを除外したjarを作成して、libbridj.soは自分のプロジェクトで直接 lib/armeabi 以下に配置するようにすれば、eclipseからでも問題なく利用できます。

利用例

最後になりますが、わたしがディレクトリツリーの効率的なルックアップのために dirent.h を利用した例を紹介します。これは、java.io.FileクラスのlistFiles()が、ディレクトリとファイルの全エントリについてFileインスタンスを生成する非効率を嫌って作成したもので、java.io.Fileより高速なディレクトリとファイルのルックアップを実現するものです。

以下は自動生成でないコードです:

以下は、JNAeratorによる自動生成に @Library を手書きで修正したコードです:

追記: 以下のコードでunistd.hに含まれるaccess()も呼び出していたので、これに対応する自動生成コードにもリンクしておきます(ほとんどの関数は無関係なので、コメントアウトしてあります) - unistdLibrary.java

以下は、以上のコードの利用例として、.oggファイルを含むディレクトリを列挙するものです。Android向けに、ルックアップしても時間の無駄になる特定のディレクトリは除外してあります。

   void getOggDirectories(String path, List<String> list) {
       if (unistdLibrary.access(Pointer.pointerToCString(path),
               unistdLibrary.X_OK | unistdLibrary.R_OK) != 0)
           return;
       if (path.equals("/proc") || path.equals("/sys") || path.equals("/data"))
           return;
       DirectoryIterator di = new DirectoryIterator(path);
       boolean hasOgg = false;
       do {
           DirectoryEntry de = di.next();
           if (de == null)
               break;
           if (de.getEntryType() == DirectoryIterator.ENTRY_TYPE_DIR) {
               String dn = de.getName();
               if (!dn.equals(".") && !dn.equals(".."))
                   getOggDirectories((path.equals("/") ? "" : path).concat(
                           File.separator).concat(dn), list);
           }
           if (!hasOgg && de.getName().toLowerCase().endsWith(".ogg")) {
               hasOgg = true;
               list.add(path);
           }
       } while (true);
   }

最後にひとこと

というわけで、今回はBridJについて簡単に紹介してみました。BridJで皆さんがネイティブライブラリ依存アプリケーションの開発を簡単にできるようになると、わたしも嬉しく思います。

使ってみて問題などがありましたら教えてもらえれば、開発者にフィードバックします。

(今さら)プログラミング.NET Framework 第3版

事後になって書くのも何ですが、C#ユーザー会のイベントで(おまけで)しゃべってきたのでした。話のネタは、プログラミング.NET Framework第3版です。

プログラミング.NET FRAMEWORK 第3版 (Microsoft Press)

プログラミング.NET FRAMEWORK 第3版 (Microsoft Press)

この本自体は今年の2月には発行されていたので、ココに書くのも今さら感があるわけですが…

第2版までは吉松さんの翻訳だったわけですが、今回は何やら多忙だったそうで、実績のある藤原さんに回ってきた。それで、わたしのところに(元々は)監訳のようなかたちで話をいただいたわけです。んが、なるはやで出版できるようにしたいということで、結局翻訳にも着手することに…w そんなわけで、22-24章はこっそりわたしの翻訳です。後は25-29章は訳文チェックをやらしい感じで担当しました*1。実のところ、自分の訳文の方がはるかに怪しいところです。自分で自分のチェックはしていないですし。

翻訳の話ばかり書いてもしょうがないので、少し内容の話を書きます。でも実のところ第2版もリファレンス的にしか読んでいなかったし、読書会直前まで自分たちが訳した第3版すら全部読んでいなかったのですが…

今回は第3版ということで、第2版以降にリリースされた.NET 4.0の変更に触れつつ、いろいろ加筆されたもののようです。特にスレッディング周りが新しく追加されていて、元々Richterはこの辺に一家言ある人物なので、細かく書かれている上に自作のPower Threading Libraryの紹介が(いらないwくらい)じっくり書かれています。C#5のasync/awaitが出る前で、Rxが出る前でもあったので、原文はともかく翻訳の方は時期的には悪かった気もするwわけですが、逆に言えばasync/awaitの仕組みのようなものを知るにはコレで十分なのかもしれません。実際、(わたしが自分で確認したことではありませんが)C#5のasync/awaitはPTLに近いという声もありました。

型システムについては、無難な(?)内容がひと通りまとめられています。基礎的な部分の他にリフレクションについても章を別立てしてあります。個人的にはSystem.Reflectionの他にSystem.Reflection.Emitの話が書いてあってほしかったと思いますが…(そうするとdynamic methodとかLinqのexpression treeの話にも繋がると思いますし)。ちなみに無難でないレベルの話については、.NET 2.0以前ですがEssential .NETの方がいろいろ詳しいと思います。

Essential .NET ― 共通言語ランタイムの本質

Essential .NET ― 共通言語ランタイムの本質

あとMS荒井さんのRoot of .NET Frameworkも(これについては id:atsushieno:20090101 を参照)。

今回はserializationの話も追加されていますが、実のところremotingの話も一緒に書いてくれれば良かったのにと思います。SoapFormatterやBinaryFormatterを経由した通信は過去のものかもしれませんが、DelegateをBeginInvoke()/EndInvoke()した時に使われる内部的な仕組みは古くありませんし、AOPまわりの話に繋げることも出来るでしょう(そうするとMEFあたりも話題に含まれるようになったり…)。ちなみにXML serializationやWCFのserializationには言及されていません*2

…とまあ、仮に.NET Frameworkのバージョンがそのままであっても、この本のお題でさらに追記されうるネタは多々あるんじゃないかなあ、と思っています。既に900ページ超の内容になっているけど。

この本で面白いのは、RichterがCLRC#のいろんな部分を好んでいないのが読み取れるところで、「イベント駆動型非同期プログラミングモデルはイケてない」のレベルから「プロパティなんていらない」くらいのことまで平気で書かれています。個人的にウケたのはintより(System.)Int32って書く方が好みですっていう部分かな。

あと、いつの間にかけっこうな価格になってしまったこともあって、割と買うのを躊躇っている声が多いと思いましたw 会社の経費などで買える人はその路線で入手した方がいいかもしれません。

*1:どんな風にやらしいかというと、(全く無関係な文章ですが)こんな感じでネチネチと読み込みます

*2:わたしの理解する限り、この本はあくまでmscorlibが関わるものを中心に書かれています

monodevelop, monotouch, mono for androidのサイトがダウン

昨日からどのページも落ちています。

これらのドメインと内部のサーバは、(Novell社があった頃の)Monoチームが所有しておらず、修正するのは困難です。クビが決まっていて業務に就く必要もないけど労働法の保護のために身分上まだ解雇されていない欧州のメンバーが、そんな義務もないのに気をきかせてAttachmateの人間と連絡をとろうとしているようですが、未だに落ちているという…

あり得ないですね。

Xamarin

私事ではありますが、と本来ならつくところなのですが、ワタクシ昨日を最終出社日としてNovellを退職しますた。理由は、知っている人はご存知の通り、コレです:
http://developers.slashdot.org/story/11/05/03/2226259/Attachmate-Fires-Mono-Developers

これは極めて唐突に通達されたものでした。最初にカナダとU.S.で事が行われて、その時点でMono, Moonlight, MonoTouch, Mono for Androidの全方面でリード開発者がレイオフされたことになります。日本にもその波が及んできました。ってわたししかいなかったわけですが。

彼らとしては途方にくれるしかなかった…ところですが、何もしないわけがない。というわけで、Miguelが2週間ばかり資金集めに奔走し、Monoチームから新しい会社Xamarinが立ち上がりました。(Ximianを想起させる名前ですね。)
http://tirania.org/blog/archive/2011/May-16.html

わたしを含め、Mono開発者のほとんどは、明らかにこのXamarinを中心に動いていくことになります。

Xamarinはまだ立ち上がったばかりなので、たとえばわたしが直ちに入社して仕事できる状態ではないので、わたしはとりあえずは無職生活を満喫することとします。

Xamarinの情報は、一応外部の人間として(?)、引き続きここでもお知らせしていきます。

追記: 早速ニュースになり始めたみたい

追記2: そんなわけで、しばらくはmonoの開発にフルタイムで拘束されないことになるので、monoまわりで社内勉強会やりたいとかなんか調査してほしいとかそういう話がありましたら、お気軽にご相談くださいませ。(atsushi@ximian.comはもう使えません。twitterやatsushienoATveritas-vos-liberabit.comが使えます。)

Falplayer: infinite ogg vorbis player (alpha)

今北産業

ゴールデンウィーク中にMono for AndroidOgg Vorbisプレイヤーを作っていたのですが、何とか実用的なところまで出来上がったので、Android Marketで公開しました: https://market.android.com/details?id=name.atsushieno.falplayer

ソースコードgithubに置いてあります: https://github.com/atsushieno/falplayer-android

既存のメディアプレイヤーで、このFalplayerと同じ機能を実現している一般的なものは無いと思いますが、そのことも含めて、以降でこのアプリについて説明します。

はじまり

大震災の後、いまいちコード書きに集中できなかったこともあって、3月の途中から英雄伝説/空の軌跡SCで(今さら)遊んでいました。しかもオンライン販売されていなかったせいで、店頭まで買いに行ったらFC/SC/3rdのセットの方が安い…というわけでついうっかりセットで買ってしまったせいで、3rd.までクリアする羽目に…

[rakuten:murauchi-denki:27096651:detail]

で、Windows版には.oggファイルとしてBGMファイルが生で入っています。20世紀からの伝統として、FalcomのBGMはたいへんクオリティが高いので、これを聴きたがる人はけっこういまして、たとえばこんなWindowsアプリがあったりします: http://www.forest.impress.co.jp/docs/review/20100210_348148.html

これらの.oggファイル、ゲーム中ではコメントとして埋め込まれたLOOPSTART/LOOPLENGTHという値を使用して、うまいことループできるように作られていて(そこそこ技術が必要)、ループしない場合は適当に1曲として聴けるようにもなっています。

今回は、後述するようないくつかの理由から、Mono for Androidのサンプルを作るという意味合いも込めて、このoggファイルを永久ループ再生できるようなプレイヤーを作ろうということになりました。

アプリケーションの特徴

メディアプレイヤー、というと、既にPowerAMPやWinamp、Meridianなどポピュラーなプレイヤーが数多く存在しているので、今さら感があるように聞こえますが、今回必要なのは、Ogg Vorbis中のコメントを抜き出して永久ループさせてくれるものです。

実際にこれらのプレイヤーのソースコードが眺められるわけではないので、あくまで一般論になりますが、Androidのメディアプレイヤーは、APIとしては、android.media.MediaPlayerを使用します。これが既に一時停止や停止、シーク機能などを提供しているわけですね。

このクラスでは、oggファイルの永久ループはサポートされていません。

.oggを使って永久ループを実現しているゲームは他にもあって、たとえばContrastaAEなんかもそうなんですが、一般的な機能なんじゃないかと思われるかもしれませんが、実のところループを記述する方法は一意ではありません。ContrastaAEにはタグが無いようです。

そんなわけでMediaPlayerはあきらめます。音楽を再生するもうひとつの方法として、android.media.AudioTrackクラスがあります。今回はこれを利用しています。このクラスは、着信音など音声ファイルの再生とストリーミング再生を両方サポートするもので、合成した任意の生PCMを再生する等の場合には、これが一般的な解になるのではないかと思います。

このクラスに、デコードした.oggの生PCMを全部放り込めれば楽なのですが、AudioTrackで指定できるバッファの大きさには、明示されていない限度があるので、展開して数十MBにもなる.oggファイルの内容をバッファ再生することはできません。Ogg Vorbisの内容をデコードして、生PCMをストリーミング再生する必要があります。

また、AudioTrackにはループバック再生の機能が含まれているのですが、あくまでストリーミング再生でなくバッファ再生のためのものなので(ストリーミングで消えていったバッファにシークして戻れるはずはありませんね)、今回は使えません。このループバック機能は、着信音など、短いものをループ再生するために存在しているわけです。

Mono for Android

今回はP/Invoke以外は特に変わったことをしているつもりはありません。イヤホンを抜けばBroadcastReceiverがIntentを受け取るようにしているくらいで、AlertDialogの作り方も凡庸なものです。

ひとつ言及しておく必要があるとしたら、SharedPreferenceは実質的に使えませんでした。これは僕はMono for Androidのバグだと認識しているのですが、アプリケーションをビルドして転送するたびに、過去のアプリを消してしまうので、SharedPreferenceのようなものは消えてしまって使えない(はずである)のです。。

Ogg Vorbisのデコード

AndroidJava APIには、Ogg Vorbisをデコードするためのクラスが存在しないので、何らかの方法で代替する必要があります。

今回、Ogg Vorbisのデコードには、音声を再生できるAndroidバイスであればほぼ必ず含まれているであろう tremolo の共有ライブラリ (libvorbisidec.so) を、P/Invokeで呼び出して利用しています。tremoloは、モバイル環境向けに最適化されたlibvorbis/libvorbisfile、のようなものです。

以前に id:atsushieno:20090325:p1 で紹介した通り、MonoでOgg Vorbisデコーダを実装した(というかJorbisを移植した)ハッカーがいました。僕はそれをmoonlightで利用できるようにした(だけの)ものを作りましたが、実は今回このアプリを作る前に、同じコンセプトでogg vorbisプレイヤーが作れないかと画策していた時に、csvorbisをデコーダとして試してみたことがあります。残念ながらその時は到底まともに再生できる速度が得られませんでした。

(とはいえ、実のところ、今回のアプリでも、AudioTrackのバッファサイズの調整が無いと、まともな速度で再生できなかったので、デコーダの速度というよりAudioTrackの使い方がまずかったのかもしれません。この辺は要再検証。)

そんなわけで、今回はパフォーマンスの観点で問題が一番小さいと思われるtremoloを何とかして使うことにしました。tremoloのバインディングについては、後で少し詳しく書きます。

実のところ、JNIでmonoランタイムの呼び出し→マネージドコードでストリームデコード指示→tremoloにreadをP/Invokeで指示→tremoloからコールバック関数としてdelegateが呼び出される という流れは、それなりにパフォーマンスが悪いんじゃないかという気がしますが、とりあえずはそれなりに再生しているようです…

tremoloバインディング

tremoloのバインディングを作るのは、当初VS2010 (Mono for Android)とAndroidエミュレータ上で行っていて、デバッグで死ぬ思いをしたわけですがw、途中で気付いてLinux上で通常のmono環境で何とか作れました。

今回のアプリでは、ov_open_callbacks()を使って、マネージドコード上で開かれたStreamを再生しています。これを実現するために、ov_callbacksというlibvorbisfileの構造体に対応するC#の構造体を定義する必要があったわけですが、コレがハマりました。

マネージドコードのメソッドを関数ポインタにマッピングさせるには、delegateを使えば良いわけですが、このov_callbacksの中に含まれるポインタの示す先が、構造体に含まれるdelegateフィールドだと、たとえ GCHandle.Alloc (..., GCHandleType.Pinned) でポインタを固定していても、やがて消えて無くなってしまいます(そもそも.NETだとdelegateがpinnedに出来なかったりとか…)。仕方ないのでdelegateは別途保持しておいて、ov_callbacksに対応する構造体では Marshal.GetFunctionPointerForDelegate () でアドレスを保持しています。もしかしたらまだ直さないといけない部分があるかも。

ちなみに、当初はtremoloのヘッダに合わせて作成していて、emulator上では動いたのですが、手元のHTC Desireで試してみたら、どうやら一部の ov_int64_t の部分が32ビットになっていたりなどして、正しい値が返ってきませんでした。HTCが独自に手を加えているのかもしれません。

改善の余地

残念ながら、このアプリケーションは軽くありません。AudioTrackにマネージドコードで生成したバッファを渡している以上仕方ないのですが、そこにP/Invokeのオーバーヘッドが入り込んでいる部分は、可能なら削りたいところです(これはMono for Androidの代わりにJavaクラスで実装してJNIにしたところで大して変わらないというか悪化するかもしれないでしょう)。

現在はov_open_callbacks()を使用して、コールバックの中でSystem.IO.Streamからバッファを読み出すように実装しているのですが、これはov_open()でFILE *を直接渡すようにすれば、ひとつ無駄なP/Invokeが減らせそうな気はします。

ひとつの大胆な変更案としては、libvorbisidec の代わりに、libmediaplayer あるいは libmediaplayerservice に相当するネイティブライブラリに、LOOPSTART/LOOPLENGTHをサポートする機能を追加した上で、自前でビルドして、そのP/Invokeラッパーをやはり自前で作ってしまう、という方法があるのではないかと考えています。libmediaplayer / libmediaplayerservice はAndroid専用なのでLinuxgdbデバッグ出来ないのが難点ですが…。また、これは通常のAndroid NDKではビルド出来ないので、その辺にいろいろ手を加える仕組みが必要になります。が、これについてはまた別の機会に…

ちなみにリリースするようなapkをパッケージしたのもAndroid Marketとか登録したのも初めてなので、未だによく分かってないかもしれません。

どうでもいいこと

アイコンはflickrから適当に見つけてきたハーモニカの写真を適当にPaint.NETで加工したものです。その意図は空の軌跡/Ysシリーズをやったことある人なら分かってもらえるはず(!?)

…ふう、これでやっとクリアした気分になったw

WebGLの(実質的に)仕様上の脆弱性について(日本語訳)

WebGLの(実質的に)仕様上の脆弱性が見つかったとされて、話題になっています。元はContext Information Security社のブログの記事なのですが、
http://www.contextis.co.uk/resources/blog/webgl/

日本語でもかいつまんで紹介されています。
http://japan.cnet.com/news/service/35002505/

とはいえ、これじゃ何だか意味が分かりませんし、原文はなかなか簡単には読めないと思うので、ちょっくら日本語訳してみました。全体的にやっつけながら、後半は特に寝ぼけながら訳しているので、何かおかしいところがありましたらコメント等で教えて下さいませ。翻訳許諾は明日辺りお願いしてみようと思います。(ダメって言われたら消す)→もらいました。調査の次のラウンドが終わったらまた結果を教えてくれるみたい。

追記: Khronos仕事早いですね。対応する動きを見せています。
http://www.itmedia.co.jp/enterprise/articles/1105/11/news026.html


WebGLの管理団体Khronos Groupはこの問題について、DoSなどの攻撃についてはOpenGL規格の拡張機能「GL_ARB_robustness」で対応済みだと説明した。この拡張機能は一部のGPUベンダーに採用されているが、他社にも採用が広がることを期待するとしている。WebブラウザではWebGLコンテンツを有効にする前にGL_ARB_robustness拡張の存在をチェックすることができ、近いうちにWebGLでもこれが実装モードになる見通しだとした。

 クロスドメイン問題については、「Cross Origin Resource Sharing」(CORS)を選択できるようにするなど、悪用を防ぐ仕組みについてWebGL作業部会で検討すると表明した。

      • -

要約

WebGLは、3Dグラフィックスをインターネット上の任意のページで表現できるようにする、ブラウザ向けの新しいWeb標準です。これは最近になって、Firefox 4とGoogle Chromeでデフォルトで有効とされており、Safariの最新のビルドでも有効に出来ます。Context社は、セキュリティ面に影響する新しいエリアの研究に関心をもっており、特にわたしたちのカスタマーに重大な影響を与える場合に備えて注意しています。わたしたちは以下の件を発見しました:

1. WebGLの仕様および実装で、いくつかの重大なセキュリティ問題が検出されたこと。
2. これらの問題によって、攻撃者が悪意のコードをWebブラウザ経由でGPUおよびグラフィックスドライバに攻撃を仕掛けられるようになるということ。これらWebGL経由でのGPUに対する攻撃によって、マシン全体が利用不能になり得ます。
3. さらに、WebGLには、ユーザーのデータ、プライバシー、セキュリティを危険に晒す問題があります。
4. これらの問題は、WebGLの仕様にかかる問題であり、そのプラットフォーム設計を修正するためには、多大なアーキテクチャの変更が必要になるでしょう。根本的に、WebGLでは現在、コンピューター上でもっとも保護されて(カーネルモードで)実行いるはずの、グラフィックスドライバおよびグラフィックスハードウェアに、インターネット上から全ての(チューリング完全な)プログラムを実行できるようになっています。
5. WebGLをデフォルトで有効にしているブラウザは、ユーザーをこれらの問題による危険に晒しています。


  • (1) ユーザーが、悪意のあるWebGLスクリプトのあるサイトを訪れる
  • (2) WebGLコンポーネントが、指定された3Dジオメトリとコードをグラフィックスカードにアップロードする
  • (3) そのジオメトリあるいはコードが、バグのある、あるいは未パッチのグラフィックスドライバの脆弱性問題を突く
  • (4) そのグラフィックスハードウェアが、システムをフリーズやクラッシュに繋がるような攻撃を受ける可能性がある

WebGL

Webの歴史を通して、Webコンテンツのインタラクティビティと表現力を高める試みがなされてきました。スクリプティングへの進出、広汎なプラグイン機能やActiveXからvideoやcanvasタグのようなHTML5機能まで、ブラウザはどんどん多くの複雑な機能をデフォルトで提供するようになってきました。

モダンブラウザの革新のそれぞれのステージにおいて、新しい機能によって重大な攻撃に晒されることがないか、それまでのセキュリティの教訓が、何度も評価され続けてきました。例えば、スクリプティングが導入されるまでは、悪意のあるページが他のサイトのコンテンツへのアクセスを得る簡単なメカニズムはありませんでした。同一の発信元というポリシーを実装する必要は無い、と言えたわけです。その過去になされたブラウザのセキュリティについての判断は、新しい革新については、特にクロスドメインのコンテンツアクセスには、当てはまらない場合があるわけです。

データの盗取は重大な問題ですが、ブラウザとホストオペレーティング環境のintegrityも、新技術の導入において忘れられてはならない問題です。時には、利益がより大きな呪いをもたらす場合があります。例えば、バイナリブラウザプラグインサポートを考えてみましょう(例: ActiveXあるいはNetscape Plugin Application Programming Interface = NPAPI)。これらをサポートすることで、サードパーティは、ブラウザの機能を拡張しwebページで呼び出し可能なインターフェースを提供することが、とても簡単になります。これはそして、ブラウザ上での攻撃の機会がより多くのコードに向けられることになります(それらの一部はほぼ明らかにまずい書き方になっているでしょう)。そうなると、ブラウザベンダーは、問題のあるコードが自身のコードですらないかもしれないので、安全なプラットフォームを提供するのが難しくなり、IEのKillbitやFirefoxプラグインチェッカーのように、ブロックしたりユーザーに必須のアップデートを通知したりするような、やっつけ(band-aids)対策を施すことになります。結局のところ、唯一の安全な方法は、そのような幅広いネイティブコンテンツをそもそも持たせない、というものですが、現在のところの一般的なコンセンサスとしては、これらのもたらす利益はセキュリティリスクを上回る、というところです。

これが、今回の投稿の主題であるWebGLに繋がってきます。もしWebGLなんて聞いたことがないとしても、近いうちに耳にすることになるでしょう。WebGLの目的は、OpenGLから派生した3DのAPIを、ブラウザで任意のWebページからJavaScriptを通じてアクセスできるようにする、というものです。最近リリースされたMozilla Firefox 4ブラウザ、GoogleChromeブラウザのバージョン9以降、そしてSafari (WebKit) 5以降では、これをデフォルトで有効にしています。

このこと自体は異論を呼ぶようなことではありませんが、この実装のされ方と、現在のPCおよびグラフィックス プロセッサのアーキテクチャのあり方によって、このアプローチのセキュリティに問題が生じてきます。Context社では、WebGLの仕様に由来する、可能性のあるセキュリティ上の懸念に関する初期調査を行い、果たしてWebGLが現在サポートされているプラットフォームで利用可能とされるべきものか、その利益がリスクを上回るものであるか、という疑問に至りました。

3Dグラフィックス パイプラインの簡単な概要

まず以下の、3Dグラフィックスが大半のモダンなPCスタイルのアーキテクチャでどのように実装されているかを示す、非常に簡略化された図から話を始めましょう。

図1 - グラフィックス パイプラインの簡単な図

最も低いレベルには、グラフィックス プロセッサ (GPU) ハードウェア自身があり、これは必ずしも特定のAPIを実装しているわけではありません(ほぼ確実に、ここは製造者が開発しているプロプラエタリなインターフェースです)が、少なくともプログラミングAPIレベルで期待されている機能はサポートしているはずです。モダンな3Dハードウェアはほぼ全て、各自のプログラマブル ユニット(通常シェーダーと呼ばれます)を持っており、それらはユーザーモードのプロセスによって個別にプログラムできます。このシェーダーのコードのネイティブ フォーマットは、一般的にはハードウェアベンダーに特化していますが、クロスプラットフォームのコードを開発できるよう、共通の言語が存在しています。

ハードウェアの上にあるのは、通常はオペレーティングシステムカーネルモードで動作する、ドライバです。ドライバの仕事は、低レベルのハードウェア機能を扱い、標準化されたインターフェース(例: WDDM)を、オペレーティングシステムGPUにアクセス出来る他のコンポーネントを通じて、提供する、というものです。

次はスケジューリングです。これは様々な場所、たとえばOSのカーネルドライバ自身で、あるいは完全なユーザーモードで、実装されうるものです。スケジューラーの責務は、GPUへのアクセスを、同じマシン上で動作している各プログラムに共有・分配するというものです。伝統的な環境では、どの時点でもひとつのアプリケーション(たとえばウィンドウマネージャ)のみが、GPUへの直接のアクセスを必要としたため、スケジューラーは不要だったでしょう。3Dのシナリオにおいて、シェーダーとテクスチャおよびジオメトリをアップロードするメモリへの直接アクセスに関する要件とは、それらが適切に管理される必要がある、ということです。

上記のスタックの最後の部分はインターフェースライブラリです。その主な経路は、ユーザーのプロセスがグラフィックスライブラリにアクセスする、というものです。これは抽象化の最後のレベルであり、どんなハードウェア固有の機能も排除されています。共通のインターフェースライブラリとしては、Direct3D(これにはカーネル機能も幾分か含まれています)や、クロスプラットフォームOpenGLです。これらは3Dジオメトリを生成し表示するためのAPIや、シェーダープログラムをよりGPU向けの表現に変換し、あるいはテクスチャ情報をビデオカードメモリ上に確保しアップロードするためのコンパイラを、提供します。

WebGLの問題

以上の簡単な説明に基づいて、現在のWebGLの仕様のあり方、設計、実装について、何が問題なのかを説明できるようになりました。伝統的なブラウザコンテンツは、通常、ハードウェアに対する直接的なアクセスは、いかなるかたちでも行いません。もしあなたがビットマップを描画したら、それはビットマップを描画する責務を負ったブラウザ中のコードによって処理されるでしょう。これはさらに、描画を自ら行うOSのコンポーネントに責務を委譲することになるでしょう。ポピュラーなブラウザの全てにおける2Dグラフィックスアクセラレーションの導入によって、この境界は消失しつつありますが、それでもなおGPUの機能がWebページに直接晒されているわけではありません。最も違いの際立つ点としては、それらのコンテンツは簡単に検証することができ、コンテンツによって測定可能なレンダリング時間をもち、一般的にプログラマブルな機能(少なくともグラフィックハードウェアに送られるようなもの)はほとんど含まない、といったことが挙げられます。

一方、WebGLは、その機能要件から必然的に、グラフィックス ハードウェアへのアクセスを提供します。シェーダーのコードは、GPUのネイティブ言語で書かれるわけではありませんが、コンパイルされ、グラフィックス ハードウェア上にアップロードされて実行されます。中規模以上に複雑なジオメトリのレンダリング時間は、実際にその内容をレンダリングしてみないと正確な値を生成するのは困難であるため、生データからは事前に予測することが困難であるという、古典的な鶏と卵の問題です。また、データによっては検証が困難であり、WebGLの実装の管理外では、セキュリティ上の制限を強制するのも難しいでしょう。

これは、現在のハードウェアおよびグラフィックス パイプライン実装が、プリエンプティブであったりセキュリティ境界を維持するようには設計されていない、という事実さえなければ、それほど問題ではなかったかもしれません。いったんスケジューラーによってGPU上のディスプレイリストの配置が決まると、少なくともシステム全体で明らかに生じ得るビジュアルの崩壊や不安定なしで、これを止めるのは困難です。コンテンツを念入りに造成すれば、ユーザーインターフェースを描画するOSの能力に重大な影響を与える、あるいはそれ以上にまずいことを起こすことも可能でしょう。コンテンツの全てを検証しセキュリティ境界を維持するという困難に無理に立ち向かえば、システムおよびユーザーデータのintegrityに、悪影響が出る可能性があります。

現在に至るまで、グラフィックス ハードウェアの製造者は、その製品での信頼できない用途について、心配する必要がありませんでした。確かに、integrityとサービス拒否(DoS)の問題は、ネイティブプログラムでもあり得ましたが、開発者は一般的には、自分たちのプログラムが問題を引き起こさない方向に関心を向けるものです。悪意ある行為者は、他者を説得して悪いコードをインストールさせる必要があるでしょうが、そこでグラフィックス ハードウェアが狙われるというのは、あまりユーザーが心配すべきところではないでしょう。グラフィックスドライバは通常、セキュリティに焦点を当てて書かれているわけではなく、パフォーマンスが最重要であるものです。セキュリティには人月と工数が大幅にかかるものであり、製造者が現状のWebGLをサポートするために製品にコストをかけるインセンティブは、極めて小さいでしょう。

仮にセキュリティ問題が特定できたとしても、膨大なGPU製造者がどのようにパッチを提供するかは、自明ではありません。ATINVIDIAのみでSecurity Focusを検索しても、公開された脆弱性がいくつか見つかるのみです(2008年まで遡ってもです)。関連するセキュリティ公示をGoogleで検索しても、特に情報が上がってきません。ドライバおよびハードウェアインタラクションの複雑性を思えば、そのソフトウェアの中に、直ちに解決すべき脆弱性問題のあるバグが無かったとは考えにくいです。

もちろん、典型的なOEM製品の制限によって、特にラップトップ環境などでは、パッチ配布でも状況が改善されない可能性もあります。そのような場合は通常、GPU製造者が提供するリファレンスドライバをラップトップにインストールすることが妨げられ、セキュリティアップデートを適用することがより困難です。

WebGLの開発において、これをサポートするブラウザベンダーは全て、特定のドライバが不安定だったり完全にクラッシュしたりする問題に遭遇してきたようです。これを防ぐための回避策としては、ドライバのブラックリストがあるようです(Chromeの場合、Windows XPではWebGLを全く実行しないようです)(https://wiki.mozilla.org/Blocklisting/Blocked_Graphics_Driversを参照)。これは長期的に通用するアプローチではないでしょう。

サービス拒否 (DoS)

サービス拒否(DoS)の危険は、WebGLが直面する最も知られたセキュリティ問題のひとつです。これは、既に現在の仕様の文書にすら明記されているから、というだけではありません(https://www.khronos.org/registry/webgl/specs/1.0/#4.4を参照)。基本的には、WebGL APIからグラフィックス ハードウェアへのほぼ直接のアクセスによって、ハードウェアに多大なレンダリング時間を消費させるようなシェーダープログラムあるいは複雑な3Dジオメトリの集合を作成できてしまいます。影響のあるコンポーネントがブラウザのプロセスのみである場合には、クライアントのサービス拒否(DoS)攻撃に対処するのは簡単です(その方法は既に数多く存在します)が、この場合は、この攻撃ではユーザーのコンピューターへのアクセスを完全に遮断することができ、より深刻な事態に陥らせることもあり得ます。

ある条件下において、Context社ではオペレーティングシステムのクラッシュ(即ちBSOD, ブルースクリーン)を観測しました。それらのクラッシュは、ドライバのコードが潜在的脆弱性を突かれる状況を引き起こすかたちで暴走した (faulted) 無害なものでした(脆弱性という観点で)。これを引き起こした、実際にエクスプロイトになり得るコードの脆弱性について、これ以上の情報は現時点では開示しません。

Windows 7およびVistaでは、この点では多少良い状況にあります。もしGPUが約2秒以上ロックアップした場合、OSはこれをリセットします。これによって、リセットされるまでの間に3Dグラフィックスを使用していたアプリケーションは全て停止させられます。しかし、これらのOSでは、カーネルがバグチェックを強制する(BSOD)前に、これが短時間のうちに何回発生するかを最大値で制限する仕組みを持っています(http://msdn.microsoft.com/en-us/windows/hardware/gg487368.aspxを参照)。

もちろん、既に知られている通り、これを緩和するための努力は為されており、たとえばANGLEプロジェクト(http://code.google.com/p/angleproject/)には簡単なインラインループの使用を排除するシェーダーの検証機能が含まれており、これはFirefox 4とChromeで使用されています。この検証機能では、特にループを含まないがなお実行に多大な時間のかかる巨大なジオメトリとシェーダーを生成する場合など、サービス拒否(DoS)に繋がる全てのケースをブロックすることは出来ません。

ここまでで、proof of conceptをひとつくらい提示するというのも合理的そうです。しかし、KhronosですらWebGL SDKの中でひとつ作成しているので、Context社ではひとつも作成する必要がありませんでした。https://cvs.khronos.org/svn/repos/registry/trunk/public/webgl/sdk/tests/extra/lots-of-polys-example.htmlを見て下さい。このページは、OSXデスクトップを完全にロックし、XPマシンをほぼ確実にクラッシュさせ、Windows 7上でGPUをリセットさせるものと見なすことが出来ます。

クロスドメイン画像盗取

文書オブジェクトモデル(DOM)仕様およびJavaScriptのブラウザ処理における、もっとも根本的なセキュリティ境界のひとつが、ドメイン境界です。これは、たとえば www.evil.com で公開されているコンテンツが www.mybanking.com にある認証済みあるいは信頼されたリソースにアクセスするのを妨げるためのものです。コンテンツが境界を越えてアクセスされるのを認めるかどうかは、アクセスされるリソースの種類によります。これは時々「埋め込み権限」あるいは「読み取り権限」と言われるものです。たとえば、あなたのドメインの外にある画像を埋め込むことは、完全に認められています。これは、その下回りのAPIでは、(画像の大きさやロードの成否を除いて)実際のコンテンツを読み取るメカニズムが存在しないためです。一方で、XMLHttpRequestオブジェクトがあなたのドメイン外からコンテンツを取得すること(そしてその生データへのアクセスを獲得すること)は、一般には認められていません。

HTML5で標準化された'Canvas'要素が導入されるまでは、クロスドメインで画像の生データを盗取する方法はあまり存在しませんでした。この状況を闘うために、'origin-clean'フラグが導入されました。このフラグは内部的にtrueに設定され、もしクロスドメインの画像あるいはコンテンツがcanvas上に使用されるとfalseになります(http://www.w3.org/TR/html5/the-canvas-element.html#security-with-canvas-elementsを参照)。いったん'origin-clean'フラグがfalseになると、画像コンテンツを抽出する'toDataURL'のようなAPIの呼び出しは不可能になります。

WebGLAPIはこの'Canvas'要素の上にあり、やはりこのフラグのコンセプトを拡張して、クロスドメインのテクスチャの利用について指針が設計されています(https://www.khronos.org/registry/webgl/specs/1.0/#4.2を参照)。ひとつの問題を除くと、説明は以上です。既にサービス拒否(DoS)の部分で議論した通り、シェーダーのコードおよびジオメトリ描画を長時間にわたって引き起こすことができます。シェーダーが直接アクセス出来るリソースのひとつに、テクスチャのピクセルデータがあります。これはシェーダーのコードに到達すると、そのオリジンの概念を失うものです。従って、仮にわたしたちが直接読むことが出来ないとしても、ピクセル値を抽出するタイミング攻撃を開発することが可能です。これは、1つのピクセルの色や輝度を対象に、どれだけ長時間シェーダーを実行するかを変えて、JavaScript上でその描画プロセスがどれだけ時間をとるかを測定することで、実現できます。これはセキュリティの分野では、主として暗号システムを破るためのものとしてではありますが、標準的な攻撃手法です。WebGLとの関係では、これが問題になり得ることは、既に公開のメーリングリストで指摘されてきたことです(http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2011-March/030882.htmlを参照)。

もちろん、攻撃者はこの用途のために画像全体のピクセルデータを展開する必要はありません。たとえば、クロスドメイン画像を他の既知の画像と比較するために、単純なtrue/falseを返すために使用できます。例として、固定URL上のプロフィール画像を返すwebサイトにおいて、ブラウザに保存されたセッションクッキーによってコンテンツが決定されるとします。攻撃者はこのクロスドメイン画像を既知のプロフィール画像のリストと突き合わせて、いつ特定の人物が悪意のサイトを使用したか知ることが出来るかもしれません。

このように、わたしたちのWebGLの調査の一環として、この攻撃が実際的であることをデモンストレーションするために、proof-of-conceptのコードが開発されました(小さいものですが)。このproof of conceptにアクセスするにはこちらからどうぞ。これはWindos XP、Windows 7、Mac OSXにおいて、Firefox 4とChrome 11でテストされました。Firefoxが最も確実に動作します。Context社ではページからキャプチャしたデータを保持せず、全てはクライアント上で行われます。WebGLを処理出来ないマシンあるいはブラウザを使っている方向けに、短いビデオもあります。

図2 - 画像キャプチャの段階を示す流れ図

これについては、わたしたちは、WebGLの仕様において、クロスドメイン画像アクセスのあり方(nature)を変更することによってしか修正し得ないと考えます。それはクロスドメイン画像を全てブロックするか、CORS (http://www.w3.org/TR/cors/) のようなものを使用して、特定の画像コンテンツのみが特定のドメインにアクセス出来るように許可することで、解決できます。

結論

以上の限られた調査から、Context社は、WebGLはまだ一般向けに準備が整ったものであるとは言うことが出来ず、ユーザーおよび企業のITマネージャーには、WebブラウザWebGLを無効にすることを推奨します。

高性能な3DコンテンツがWeb上で利用可能になることについては、一定の需要があるでしょうが、WebGLは、十分に安全にサポートするために必要なインフラクラクチャを、十分考慮していないかたちで規定されています。セキュリティ問題を緩和する方法の開発として、検証レイヤーの導入とドライバ ブラックリストは有効であると分かっています。しかしながら、これはWebGLを安全にする責務を、ハードウェア製造者に押しつけています。おそらく最適解は、これらを考慮に入れた上で、3Dグラフィックスの仕様をゼロから作り上げることでしょう。