はじめに
Google から、非常に面白そうなソフトウェアがリリースされました!
その名も Native Client なんとブラウザ上で X86 のバイナリを動かしてしまうそうです。
これはすごい!
さっそく試してみたいと思います。その過程を逐次更新していきます。
自分が試したときの環境
自分が試す環境は、以下の通りです。
準備
では、さっそく準備をしましょう。
http://nativeclient.googlecode.com/svn/trunk/nacl/googleclient/native_client/documentation/getting_started.html を参考にやってみます。
環境一式をダウンロード
まずは、以下から nacl_mac_0.1_9308700.tgz をダウンロードしてくきます。
http://code.google.com/p/nativeclient/downloads/list
$ wget http://nativeclient.googlecode.com/files/nacl_mac_0.1_9308700.tgz --2008-12-09 15:10:07-- http://nativeclient.googlecode.com/files/nacl_mac_0.1_9308700.tgz nativeclient.googlecode.com をDNSに問いあわせています... 74.125.47.82 nativeclient.googlecode.com|74.125.47.82|:80 に接続しています... 接続しました。 HTTP による接続要求を送信しました、応答を待っています... 200 OK 長さ: 83237741 (79M) [application/x-gzip] `nacl_mac_0.1_9308700.tgz' に保存中 100%[==================================================================================================================>] 83,237,741 2.61M/s 時間 36s 2008-12-09 15:10:44 (2.21 MB/s) - `nacl_mac_0.1_9308700.tgz' へ保存完了 [83237741/83237741]
解凍
tgz だったので、 tar で解凍します。
$ tar xvfz nacl_mac_0.1_9308700.tgz
中を見てみる
tar すると nacl というディレクトリにいろいろと解凍されました。
解凍されたディレクトリを tree してみました。
ng$ cd .. $ tree -dL 3 nacl nacl `-- googleclient |-- native_client | |-- common | |-- documentation | |-- gtest | |-- include | |-- intermodule_comm | |-- ncv | |-- nonnacl_util | |-- npapi_plugin | |-- scons-out | |-- service_runtime | |-- site_scons | |-- site_scons_general | |-- tests | |-- third_party | |-- tools | `-- tools_bin `-- third_party |-- binutils |-- gcc |-- gnu_binutils |-- gtest |-- libxt |-- newlib |-- npapi |-- scons `-- sdl 28 directories
サンプルを実行してみる
サンプルは、 nacl/googleclient/native_client/tests/ の中に入っているみたいです。
tree -dL 1 nacl/googleclient/native_client/tests/ nacl/googleclient/native_client/tests/ |-- Frameworks |-- app_lib |-- cloudfs |-- createthreads |-- earth |-- eviltests |-- fib |-- file |-- hello_world |-- imc_shm_mmap |-- life |-- mandel |-- mandel_nav |-- mm_init |-- mmap |-- noop |-- npapi_bridge |-- npapi_hw |-- npapi_pi |-- nrd_xfer |-- null |-- plug_univ |-- quake |-- srpc |-- srpc_hw |-- syscalls |-- tone |-- voronoi `-- xaos 29 directories
なんか、いろいろあります。
では、 life というサンプルを実行してみましょう!
$ cd nacl/googleclient/native_client/tests/ $ python run.py
おおお。なんか、ライフゲームが立ち上がりました!
ディレクトリの中身を見てみましょう。
$ ls -la total 64 drwxr-xr-x 2 amachang staff 272 12 8 12:45 . drwxr-xr-x 31 amachang staff 1054 12 8 12:45 .. -r--r--r-- 1 amachang staff 1882 12 8 12:45 Makefile -r--r--r-- 1 amachang staff 166 12 8 12:45 README.txt -r--r--r-- 1 amachang staff 1874 12 8 12:45 SConscript.nacl -r-xr-xr-x 1 amachang staff 9795 12 8 12:45 life.cc -r--r--r-- 2 amachang staff 1134 12 8 12:45 life.html -r-xr-xr-x 1 amachang staff 1786 12 8 12:45 run.py
ビルド用の Makefile と SConscript と、ソースコード life.cc と、 html に貼付けるための life.html、単品実行用の run.py って感じですね。
ちょっと脱線:ソースコードを覗き見してみる
ソースコードをちらっと見てみましょう。
include を見ると、以下のように nacl/nacl_* というファイルを include しています。
standalone で実行する時は、違うヘッダを読み込むようですね。#if !defined(STANDALONE) #include <nacl/nacl_av.h> #include <nacl/nacl_srpc.h> #else #include "native_client/common/standalone.h" #endif描画は、以下のように nacl_video_update に uint32_t のバッファを渡してやるようです。
struct Surface { int width, height, pitch; uint32_t *pixels; Surface(int w, int h) { width = w; height = h; pitch = w; pixels = new uint32_t[width * height]; } ~Surface() { delete[] pixels; } }; : : // Copies sw rendered life image to screen void Life::Draw() { int r; r = nacl_video_update(surf_->pixels); if (-1 == r) { printf("nacl_video_update() returned %d\n", errno); } }詳しいことはこの時点ではよく分かりません。
でも、このアプリケーションは 341 行という短いコードで動いているようです。
ちょっと脱線: run.py は何をやっているの?
追いかけてみたら、 run.py は以下のコマンドを実行しているみたいです。
$ nacl/googleclient/native_client/scons-out/dbg-mac/staging/sel_ldr -d -f nacl/googleclient/native_client/scons-out/nacl/staging/life.nexesel_ldr というプログラムは、スタンドアローン版の flash player のようなもので nexe というが swf のようなものなのでしょう。
- -f オプションはファイル指定
- -d オプションはデバッグ
という意味だそうです。
life.nexe というのはどういうファイルなのか?
ちょっと気になるので、 file してみました
$ file scons-out/nacl/staging/life.nexe scons-out/nacl/staging/life.nexe: ELF 32-bit LSB executable, Intel 80386, version 1, statically linked, not strippedどうやら、 *.nexe は ELF 32-bit LSB バイナリ(リナックスの実行ファイルの形式)だということが分かりました。
プラグインをビルドする
SCons でビルド出来るようです。
$ cd googleclient/native_client $ scons --help scons: Reading SConscript files ... EnvironmentError: No module named component_setup: File "/Users/amachang/nc/nacl/googleclient/native_client/SConstruct", line 52: COMPONENT_LIBRARY_PUBLISH = True, File "/opt/local/lib/scons-0.98.4/SCons/Environment.py", line 933: apply_tools(self, tools, toolpath) File "/opt/local/lib/scons-0.98.4/SCons/Environment.py", line 106: env.Tool(tool) File "/opt/local/lib/scons-0.98.4/SCons/Environment.py", line 1582: tool = apply(SCons.Tool.Tool, (tool, toolpath), kw) File "/opt/local/lib/scons-0.98.4/SCons/Tool/__init__.py", line 89: module = self._tool_module() File "/opt/local/lib/scons-0.98.4/SCons/Tool/__init__.py", line 140: raise SCons.Errors.EnvironmentError, e
怒られてしまいました><
どうやら、 Native Client が用意した scons を使えということらしいですね。
$ ./scons --help scons: Reading SConscript files ... ====================================================================== Building nexe binaries using sdk at [/Users/amachang/nc/nacl/googleclient/native_client/tools_bin/mac/sdk/nacl-sdk] ====================================================================== ====================================================================== SDL build enabled, this is somewhat experimental Using version in /Users/amachang/nc/nacl/googleclient/native_client/../third_party/sdl/osx/v1_2_13 ====================================================================== *** Solution file generation skipped (not supported on this platform). scons: done reading SConscript files. Additional options for SCons: --mode=MODE Specify build mode (see below). --host-platform=PLATFORM Force SCons to use PLATFORM as the host platform, instead of the actual platform on which SCons is run. Useful for examining the dependency tree which would be created, but not useful for actually running the build because it'll attempt to use the wrong tools for your actual platform. --site-path=DIRLIST Comma-separated list of additional site directory paths; each is processed as if passed to --site-dir. --verbose Print verbose output while building, including the full command lines for all commands. --brief Print brief output while building (the default). This and --verbose are opposites. Use --silent to turn off all output. --retest Rerun specified tests, ignoring cached results. ====================================================================== Help for NaCl ====================================================================== Common tasks: ------------- * cleaning: scons -c * build mandel: scons MODE=all mandel.nexe * some unittests: scons run_unit_tests * a smoke test: scons -k pp=1 smoke_test * 2nd smoke test: scons -k pp=1 MODE=nacl smoke_test * documentation: scons MODE=doc * firefox plugin: scons MODE=opt-linux npGoogleNaClPlugin * sel_ldr: scons MODE=opt-linux sel_ldr * firefox install: scons firefox_install Options: -------- pp=1 use command line pretty printing (more concise output) sdl=<mode> where <mode>: 'none': don't use SDL (default) 'local': use locally installed SDL 'hermetic': use the hermetic SDL copy naclsdk_mode=<mode> where <mode>: 'local': use locally installed sdk kit 'download': use the download copy (default) 'custom:<path>': use kit at <path> Automagically generated help: ----------------------------- Use --mode=type to specify the type of build to perform. The following types may be specified: dbg-mac MacOS debug build opt-mac MacOS optimized build nacl NaCl module build doc Documentation build The following build groups may also be specified via --mode. Build groups build one or more of the other build types. The available build groups are: all dbg-mac,opt-mac,nacl,doc default dbg-mac Multiple modes may be specified, separated by commas: --mode=mode1,mode2. If no mode is specified, the default group will be built. This is equivalent to specifying --mode=default. The following libraries can be built: gio naclthread nonnacl_util_c google_nacl_imc ncvalidate nrd_xfer google_nacl_imc_c ncvtest sel google_nacl_npruntime nonnacl_srpc gtest nonnacl_util all_libraries (do all of the above) The following large tests can be run: run_service_runtime_tests simple_tests run_large_tests (do all of the above) The following tests can be run: run_service_runtime_tests simple_tests run_all_tests (do all of the above) The following bundles can be built: /Users/amachang/nc/nacl/googleclient/native_client/scons-out/dbg-mac/staging/npGoogleNaClPlugin.bundle all_bundles (do all of the above) The following programs can be built: client ncval sel_universal nacl_cpuid npGoogleNaClPlugin server nacl_ldt_unittest npapi_test service_runtime_tests ncdecode_table nrd_xfer_test ncdis sel_ldr all_programs (do all of the above) Use scons -H for help about command-line options.
おおお。このツールでなんでも作れるみたいですね。
さっそく Firefox のプラグインをインストールします。
$ ./scons --prebuilt firefox_install scons: Reading SConscript files ... ====================================================================== Building nexe binaries using sdk at [/Users/amachang/nc/nacl/googleclient/native_client/tools_bin/mac/sdk/nacl-sdk] ====================================================================== *** Solution file generation skipped (not supported on this platform). scons: done reading SConscript files. scons: Building targets ... /System/Library/Frameworks/Python.framework/Versions/2.5/Resources/Python.app/Contents/MacOS/Python ./tools/firefoxinstall.py MODE=0 PLATFORM_BASE="/Users/amachang/nc/nacl/googleclient/native_client/scons-out/" MODE=0 PLATFORM_BASE=/Users/amachang/nc/nacl/googleclient/native_client/scons-out/ This script will install: /Users/amachang/nc/nacl/googleclient/native_client/scons-out/opt-mac/staging/npGoogleNaClPlugin.bundle in /Users/amachang/Library/Internet Plug-Ins/npGoogleNaClPlugin.bundle /Users/amachang/nc/nacl/googleclient/native_client/scons-out/opt-mac/staging/sel_ldr in /Users/amachang/Library/Internet Plug-Ins/npGoogleNaClPlugin.bundle/Contents/Resources and /Users/amachang/nc/nacl/googleclient/native_client/scons-out/opt-mac/Frameworks/SDL.framework in /Users/amachang/Library/Frameworks/SDL.framework Okey to continue? [y/n]
sel_ldr や SDL.framework や npGoogleNaClPlugin.bundle がインストールされる見たいですね。
良ければ、 y と答えます。
Okey to continue? [y/n] y Okay, you asked for it. copying directory /Users/amachang/nc/nacl/googleclient/native_client/scons-out/opt-mac/staging/npGoogleNaClPlugin.bundle to /Users/amachang/Library/Internet Plug-Ins/npGoogleNaClPlugin.bundle ... copying directory /Users/amachang/nc/nacl/googleclient/native_client/scons-out/opt-mac/Frameworks/SDL.framework to /Users/amachang/Library/Frameworks/SDL.framework ... copying /Users/amachang/nc/nacl/googleclient/native_client/scons-out/opt-mac/staging/sel_ldr to /Users/amachang/Library/Internet Plug-Ins/npGoogleNaClPlugin.bundle/Contents/Resources ... ********************************************************************* * You have successfully installed the NaCl Firefox plugin. * As a self-test, please confirm you can run * /Users/amachang/Library/Internet Plug-Ins/npGoogleNaClPlugin.bundle/Contents/Resources/sel_ldr * from a shell/command prompt. With no args you should see * No nacl file specified * on Linux or Mac and no output on Windows. ********************************************************************* * To test this installation also try the test links on the page * scons-out/nacl/staging/index.html ********************************************************************* scons: done building targets.
おおお。成功したみたいです。
ブラウザでサンプルを見てみる。
さっそく、 Firefox を起動して、 scons-out/nacl/staging/earth.html を見てみましょう。
おおおおおお。めっちゃ軽いです!
自分で動くものを作ってみる
では、さっそく何か作ってみましょう。
足し算するだけの nexe を作ってみる
まずは、以下のような add.c というファイルを作ります。
#include <nacl/nacl_srpc.h> int Add(NaClAppArg **in_args, NaClAppArg **out_args) { out_args[0]->u.ival = in_args[0]->u.ival + in_args[1]->u.ival; return RPC_OK; } NACL_SRPC_METHOD("add:ii:i", Add);
次に、以下のような add.html を作ります。
<!DOCTYPE html> <html> <head> <title>test</title> </head> <body> <embed id="nacl" type="application/x-nacl-srpc" width="0" height="0" src="add.nexe" /> <a href="javascript:void(0);" onclick="alert(document.getElementById('nacl').add(1, 2))">1 + 2 = ?</a> </body> </html>
で、以下のようにコンパイルします。
長い人は PATH を通してしまいましょう。
$ nacl/googleclient/native_client/tools_bin/mac/sdk/nacl-sdk/bin/nacl-gcc -lsrpc -lgoogle_nacl_imc -lpthread -static add.c -o add.nexe
文字列の diff を取る nexe を作ってみる
http://labs.unoh.net/2008/11/diff_with_c.html で紹介されている Google Code Archive - Long-term storage for Google Code Project Hosting. という diff ライブラリを使います。
まず、このライブラリの dtl.hpp を同じディレクトリに置きます。
次に、以下のような C++ のコードを書き diff.cpp とします。
#include "dtl.hpp" #include <iostream> #include <vector> #include <sstream> #include <nacl/nacl_srpc.h> std::string diff(std::string as, std::string bs) { using namespace std; using namespace dtl; // 文字列を行にバラす istringstream ais(as); istringstream bis(bs); string buf; vector<string> av, bv; while(getline(ais, buf)) av.push_back(buf); while(getline(bis, buf)) bv.push_back(buf); // Diff を取る Diff<string, vector<string> > d(av, bv); d.compose(); Ses<string> ses = d.getSes(); vector< pair<string, elemInfo> > v = ses.getSequence(); // 結果を ostringstream に HTML 形式で出力する ostringstream os; vector< pair<string, elemInfo> >::iterator it; for (it = v.begin(); it != v.end(); ++it) { switch (it->second.type) { case SES_ADD : os << "<li class=\"add\">" << it->first << "</li>\n"; break; case SES_DELETE : os << "<li class=\"delete\">" << it->first << "</li>\n"; break; case SES_COMMON : os << "<li class=\"common\">" << it->first << "</li>\n"; break; default : break; } } return os.str(); } // SRPC で呼び出される関数 int Diff(NaClAppArg **in_args, NaClAppArg **out_args) { // diff 関数を呼び出す out_args[0]->u.sval = strdup(diff(in_args[0]->u.sval, in_args[1]->u.sval).c_str()); return RPC_OK; } // diff:ss:s は、文字列を2つ受け取って文字列を返すという意味 NACL_SRPC_METHOD("diff:ss:s", Diff);
そして、さきほどと同じようにコンパイルします。
$ nacl/googleclient/native_client/tools_bin/mac/sdk/nacl-sdk/bin/nacl-g++ -lsrpc -lgoogle_nacl_imc -lpthread -static diff.cpp -o diff.nexe
今回は C++ なので nacl-g++ を使っているところに注意してください。
diff.nexe が出来たので、以下のような diff.html を作ります。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Diff Sample</title> <style type="text/css"> html { width: 100%; margin: 0; padding 0; } body { width: 96%; margin: 2%; padding 0; } body > div { float: left; width: 50%; } body > div > textarea { width: 90%; height: 10em; } ul { list-style-type: none; margin: 0; padding: 0 } li { white-space: pre } li.add { color: #080; background: #8f8; } li.delete { color: #800; background: #f88; } </style> <script type="text/javascript"> var result, nacl, text1, text2; window.onload = function() { result = document.getElementById('result'); nacl = document.getElementById('nacl'); text1 = document.getElementById('text1'); text2 = document.getElementById('text2'); }; </script> </head> <body> <h1>Diff Sample</h1> <div> <h2>Text 1</h2> <textarea id="text1"> function fib(n){ if(n == 1 || n == 2){ return 1; }else{ return fib(n - 1) + fib(n - 2); } } </textarea> </div> <div> <h2>Text 2</h2> <textarea id="text2"> function fibonatti(n){ if(n == 1 || n == 2){ return 1; }else{ return fibonatti(n - 1) + fibonatti(n - 2); } } </textarea> </div> <p> <a href="javascript:void(0);" onclick="result.innerHTML = nacl.diff(text1.value, text2.value);">diff!</a> </p> <ul id="result"></ul> <embed id="nacl" type="application/x-nacl-srpc" width="0" height="0" src="diff.nexe" /> </body> </html>
自分で Native Client の実行環境をビルドしてみる
いったん clean して
$ ./scons --mode=dbg-mac,nacl - c
デバッグビルドします。
$ ./scons --mode=dbg-mac,nacl
普通に成功しました。
で、 firefox にデバッグ版をインストールします。
$ ./scons firefox_install DBG=1
でも、どうやったらデバッグできるんだろう orz
ドキュメントには、以下のようにインクルードして、普通のローカルアプリケーションとしてビルドすれば、 gdb でも kdbg でもなんでも出来るよね!というようなことが書いてありました。
#if !defined(STANDALONE) #include <nacl/nacl_srpc.h> #else #include "native_client/common/standalone.h" #endif
どうやって nexe は実行されるか
ちょっと調べてみます。
sel_ldr が別プロセスとして立ち上がってる
$ ps auwx | grep sel_ldr amachang 17617 0.0 0.3 1139488 6256 ?? S 8:50PM 0:00.12 /Users/amachang/Library/Internet Plug-Ins/npGoogleNaClPlugin.bundle/Contents/Resources/sel_ldr -f /Users/amachang/nc/sample/diff/diff.nexe -i 5:42 -P 5 -X 5
ページをロードするごとに、一つ sel_ldr プロセスが立ち上がるみたいです。
これと Firefox がどうにか通信しているんですね。
どうやって、 nexe にジャンプするか
service_runtime/nacl_switch.S というアセンブラがあって、以下のようにジャンプしています。
.text .globl IDENTIFIER(NaClSwitch) IDENTIFIER(NaClSwitch): popl %eax /* throw away the return addr */ /* do not leak info to app */ xorl %ecx, %ecx /* xorl will leave eflags in a known state, so no info leaks */ popl %edx /* new eip */ popl %ebp popl %edi popl %esi popl %ebx popl %gs popl %fs popl %es ljmp *(%esp)
飛んだ先はどうなっているか
diff.cpp には main がありませんでした。どこに飛ぶのでしょうか。
libsrpc.a に main が weak シンボルで定義されていました。
int __attribute__ ((weak)) main(int argc, char* argv[]) { /* * Print the methods that are available. __NaClPrintRpcMethods(); */ srpc_init(); /* * Message processing loop goes here. For now, just do a sel_universal. */ __CommandLoop(); return 0; }
では __CommandLoop では何をやっているのでしょうか。
__CommandLoop では何をやっているのか
__CommandLoop は以下のようになっていました。
NaClSrpcError __CommandLoop() { : : socket_desc = __srpc_get_fd(); if (socket_desc == -1) { /* * No socket connection, use stdin/stdout. */ rpc_desc = __BuildInterfaceDescription(&num_rpc); // process commands from stdin and dispatch for (;;) { : : } } else { NaClSrpcChannel channel; : : for (;;) { : : } : : } return RPC_OK; }
コメントを見る限りは、ソケット通信しているかどうかでループを分けています。
SRPC メッセージのフォーマットは?
tools/libsrpc/rpc_serialize.c のコメントのところに書いてあります。
SRPC の解析処理 → 関数実行の流れ
Firefox からデータが送られてくると tools/libsrpc/rpc_serialize.c の以下の箇所で rpc_number が抽出され
: retval = ImcRead(&client_protocol, sizeof(client_protocol), 1, channel); : retval = ImcRead(&rpc_number, sizeof(rpc_number), 1, channel); :
以下の箇所で、実際にアプリケーションに書いたあれらの関数(さっきの例の Add や Diff)が呼び出されます。
app_error = (channel->rpc_descr[rpc_number].handler)(args, rets);
また、ページ遷移すると Firefox から NACL_SRPC_SHUTDOWN_METHOD というメッセージが送られてきて
以下の SRPC のメソッドが呼ばれ、 nexe は終了します。
static int srpc_shutdown_request(NaClAppArg **in_args, NaClAppArg **out_arg) { if (srpc_privileged) { _exit(0); } return RPC_OK; } NACL_SRPC_METHOD("shutdown::", srpc_shutdown_request);
このメッセージ(NACL_SRPC_SHUTDOWN_METHOD)は、 JavaScript からも element.shutdown() というように呼び出すことで送出することが出来ます。
まとめ
そろそろまとめますよっと。とりあえず試してみた Native Client ですが、ちょっと難しかったです><
でも、ネイティブのコードが動くというのはやっぱり凄いですね。
Alchemy のようなアプローチでは(C → LLVM → AS → ABC という感じ)、やはり速度には限界がありますし。
ただ、セキュリティをどうやって確保しているのかが気になりますね。もちろん、ウェブページに埋め込むようなものなのできちんとしたサンドボックスが必要ですよね。時間があるときに、そこら辺も調べてみたいですね。
もし、セキュリティ上問題がないのなら、これ以上最強なものはないのではないでしょうか。
追記:id:moira さんがそこら辺のことに言及されています
http://blog.deadbeaf.org/2008/12/09/google-native-client/
さすがです><