IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

FastCGI のプロセスを strace する

メモしておきます。

1. プロセスマネージャに以下のように attach する(-ff は fork したプロセスにも自動で attach してくれる)

$ sudo strace -ff -o fastcgi_strace_log -p <fcgi プロセスマネージャの PID>

2. fcgi サーバーのプロセスを殺す

$ sudo pkill application.fcgi

3. すると、プロセスマネージャが新しい fcgi サーバーのプロセスを立ち上げるので、そのプロセスに strace で attach できる

$ ls
fastcgi_strace_log.<fcgi サーバーの PID> fastcgi_strace_log.<fcgi サーバーの PID> fastcgi_strace_log.<fcgi サーバーの PID> fastcgi_strace_log.<fcgi サーバーの PID> fastcgi_strace_log.<fcgi サーバーの PID> fastcgi_strace_log.<fcgi サーバーの PID>

私的 Win32 API メモ

いろいろ勉強した

忘れてしまいそうなので書き下しておく

雰囲気的なこと

  • ウィンドウ
    • 俗にいうウィンドウではなく、 GUI の部品全般(ボタンとか、ツールバー)も含む
    • 俗にいうウィンドウは、フレームとか言う
  • ハンドル
    • ポインタみたいなもん
  • HWND
    • ウィンドウのハンドル
    • DOM でいうと Node みたいなもん
  • LPXXX
    • XXX のポインタって意味
  • owner と popup
    • 「ポップアップさせたウィンドウ = owner」と「ポップアップしたウィンドウ = popup」
    • DOM でいうと
      • owner は、 window.opener で
      • popup は、 window.frames って感じ
  • HINSTANCE
    • DLL とか EXE とかひとつと対応してるハンドル
    • アプリケーションの HINSTANCE というと実行されている EXE のハンドル
    • DLL や EXE のメモリ上の位置
    • HMODULE とは同じもの

あまり本質的でないこと

  • W で終わる関数は WCHAR
    • WideCharToMultiByte を使えば、 WCHAR -> char
int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, buf, sizeof(buf) - 1, NULL, NULL);

ウィンドウの取得

  • HWND GetWindow(HWND, 定数)
    • 様々な方向に、要素を取得しにいく
    • GetWindow(HWND, GW_CHILD)
      • DOM でいうと node.firstChild
    • GetWindow(HWND, GW_HWNDNEXT)
      • DOM でいうと node.nextSibling
    • GetWindow(HWND, GW_HWNDPREV)
      • DOM でいうと node.previousSibling
    • GetWindow(HWND, GW_HWNDFIRST)
      • DOM でいうと node.parentNode.firstChild
    • GetWindow(HWND, GW_HWNDLAST)
      • DOM でいうと node.parentNode.lastChild
    • GetWindow(HWND, GW_OWNER)
      • DOM でいうと window.opener
    • GetWindow(HWND, GW_ENABLEDPOPUP)
      • DOM でいうと window.frames[0]
  • HWND GetParent(HWND)
    • DOM でいうと node.parentNode
  • AnyPopup
    • ポップアップウィンドウを持ってるか
    • DOM でいうと (window.frame.length != 0)
  • BOOL EnumChildWindows(HWND, WNDENUMPROC, LPARAM)
    • DOM でいうと node.getElementsByTagName('*').forEach(function() { }); みたいなもん
    • 第三引数は何でも OK (ポインタとか入れとけば OK)
    • あっちこっち何回もトラバースするときは、これ使えってことらしい
  • HWND GetDesktopWindow()
    • デスクトップのウィンドハンドルを返す
    • DOM でいうと document.documentElement とか document.body

WHND から取得できる情報

  • Class
  • HINSTANCE
    • GetWindowLong(hwnd, GWL_HINSTANCE)
    • CreateWindow, CreateWindowEx したときに渡したやつ
  • スタイル
    • GetWindowStyle(HWND) & flags, GetWindowExStyle(HWND) & flags
    • LVS_ とか TBSTYLE_ とか CCS_ とか WS_ とか Class Name によっていろいろ
  • タイトル的な何か
    • GetWindowText(hwnd, buf, sizeof(buf) - 1)
  • 矩形
    • GetWindowRect(hWnd,&rect)
  • ウィンドウを作成したプロセス ID とスレッド ID
    • threadid = GetWindowThreadProcessId(HWND, &processid)
  • プロセス ID から HINSTANCE を得る
    • OpenProcess 使う
  • SendMessage
    • なんでもあり、奥義
    • SendMessage で情報を取得する場合で、外部プロセスのウィンドウの場合は VirtualAllocEx で
  • VirtualAllocEx
    • 外部プロセスのメモリを確保
    • HWND → GetWindowThreadProcessId → OpenProcess → VirtualAllocEx の手順
    • ReadProcessMemory で読む
    • VirtualFreeEx で開放

ブラウザで X86 のマシン語を動かす! Google 謹製 Native Client をさっそく試してみる

はじめに

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

third_party/sdl というディレクトリがありますね!ワクワクします。

サンプルを実行してみる

サンプルは、 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.nexe

sel_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_ldrSDL.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>

そして、ブラウザで読み込むと、、、

おおおおおお。 diff が取れました!

自分で 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/
さすがです><

C/C++ のコードを Flash Player で動かす! Alchemy を速攻試してみる。

はじめに

Adobe から C/C++ で書いたコードを FlashAIR で動かす Alchemy というものがリリースされましたね!
Alchemy - Adobe Labs

これはすごい!ということで、少し試してみたいと思います。
その様子をリアルタイムに書いていきます。ちゃんと出来るかな

環境

OS は Mac OS Xgccjava は入っているものとします。
ホームディレクトリ(/Users/amachang)に AlchemyTest というディレクトリを作って作業します。

インストール

まずは、いろいろインストールします。

Flash Player 10 Debugger Version

Adobe Flash Player - Debug Downloads
ここからダウンロードしてきて、普通にインストールします。

Flex SDK

以下から Flex SDK をダウンロードします。
Flex SDK
現段階での最新の Stable のやつをダウンロードしました。具体的には以下のバージョンのものです。
http://opensource.adobe.com/wiki/display/flexsdk/download?build=3.2.0.3794&pkgtype=1
ダウンロードできたら、解凍します。今回は FlexSDK という名前のディレクトリを作ってそこに解凍しました。

$ unzip flex_sdk_3.2.0.3794.zip -d FlexSDK

とりあえず、 bin に PATH を通しておきましょう。

$ export PATH=/Users/amachang/AlchemyTest/FlexSDK/bin:$PATH

試しに、なんか適当な ActionScript のファイルから swf ファイルを作ってみましょう。
以下のように、適当なディレクトリを作って

$ mkdir Src
$ cd Src

そこに、以下のようなファイル Hoge.as を置きます。

package {
    import flash.display.Sprite
    public class Hoge extends Sprite {
        public function Hoge() { } 
    }   
}

で、さっき FlexSDK に解凍したツールを使ってコンパイルします。

$ mxmlc Hoge.as
設定ファイル "/Users/amachang/AlchemyTest/FlexSDK/frameworks/flex-config.xml" をロードしています
/Users/amachang/AlchemyTest/Src/Hoge.swf (555 bytes)

おおお。 swf ファイルがちゃんとできましたね。
というわけで、次は Alchemy Toolkit Package を導入します。
ここまでのディレクトリ構成は以下のような感じです。

$ pwd
/Users/amachang/AlchemyTest
$ tree -L 2
.
|-- FlexSDK
|   |-- AIR SDK Readme.txt
|   |-- SDK license.pdf
|   |-- ant
|   |-- asdoc
|   |-- bin
|   |-- flex-sdk-description.xml
|   |-- frameworks
|   |-- lib
|   |-- license-adobesdk.htm
|   |-- license-adobesdk_ja.htm
|   |-- license-mpl.htm
|   |-- readme.htm
|   |-- readme_ja.htm
|   |-- runtimes
|   |-- samples
|   `-- templates
|-- Src
|   |-- Hoge.as
|   `-- Hoge.swf
`-- flex_sdk_3.2.0.3794.zip

10 directories, 11 files
Alchemy Toolkit Package

次は Alchemy Toolkit Package をダウンロードしてきます。
Alchemy Toolkit Package
ダウンロードしたら、作業ディレクトリに置いて AlchemyToolkit というディレクトリに解凍します。

$ unzip alchemy_sdk_darwin_p1_111708.zip -d AlchemyToolkit

少し、ディレクトリ構成を見てみましょう。

$ tree -L 2 AlchemyToolkit
AlchemyToolkit
`-- alchemy-darwin-v0.4a
    |-- CHANGES
    |-- LICENSE
    |-- README.cygwin
    |-- README.darwin
    |-- README.ubuntu
    |-- achacks
    |-- avm2-libc
    |-- bin
    |-- config
    |-- flashlibs
    `-- samples

7 directories, 5 files

AlchemyToolkit のトップはこんな感じのディレクトリ構成になっています。
まず、 avm2-libc というのがありますね。この中にが libc.so の代わりにリンクされるライブラリが入っているのでしょうか。
また、 bin にツールが入っているのでしょうか。README を読む前に bin の中身を確認してみましょう。

$ ls AlchemyToolkit/alchemy-darwin-v0.4a/bin/
ExplSWF.pl			ShrSWF.pl			llvm-as				llvm-prof
GetABC2.pl			V10SWF.pl			llvm-bcanalyzer			llvm-ranlib
ImplSWF.pl			alc-util			llvm-config			llvm-stub
LICENSE-LLVM.TXT		asc.jar				llvm-db				llvm-upgrade
LICENSE-LLVMC.TXT		gccas				llvm-dis			llvm2cpp
LICENSE-ZLIB.TXT		gccld				llvm-extract			llvmc
PutABC2.pl			gluegen				llvm-gcc4-darwin-install	opt
PutBIN.pl			llc				llvm-ld				swfbridge
README-LLVM			lli				llvm-link			zpipe
README-ZLIB			llvm-ar				llvm-nm				zpipe.pl

おおお LLVM のツールがいっぱい入っています。
というわけで avm2-libc の中をみてみます。

$ ls -la AlchemyToolkit/alchemy-darwin-v0.4a/avm2-libc/
total 0
drwxr-xr-x   5 amachang  staff   170 11 14 03:09 .
drwxr-xr-x   8 amachang  staff   442 11 14 03:09 ..
drwxr-xr-x   2 amachang  staff   102 11 14 03:09 avm2
drwxr-xr-x  10 amachang  staff  2822 11 14 03:09 include
drwxr-xr-x   2 amachang  staff   136 11 18 20:19 lib

そして、この lib の中に

$ ls -la AlchemyToolkit/alchemy-darwin-v0.4a/avm2-libc/lib/
total 7544
drwxr-xr-x  2 amachang  staff      136 11 18 20:19 .
drwxr-xr-x  5 amachang  staff      170 11 14 03:09 ..
-rw-r--r--  1 amachang  staff  1564524 11 14 03:09 avm2-libc.l.bc
-rw-r--r--  1 amachang  staff  2297544 11 14 03:09 avm2-libstdc++.l.bc

LLVMバイトコードが入っていました。

ちょっと寄り道、します。
avm2-libc.l.bc をディスアセンブルしてみましょう!

$ cd AlchemyToolkit/alchemy-darwin-v0.4a/
$ bin/llvm-dis avm2-libc/lib/avm2-libc.l.bc 

おお。成功したみたいです。 avm2-libc.l.ll というファイルが出来ました。
LLVM というものを全然しらないのですが、そのファイルの中身を見てみると冒頭に ActionScript 3.0 のソースコードみたいなものが見えます。そして、アセンブラのようなものが見えます。
きっと LLVMアセンブラなのでしょう。

; ModuleID = 'avm2-libc/lib/avm2-libc.l.bc'
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-s0:0:64-f80:128:128"target triple = "i686-apple-darwin8"
module asm "import flash.utils.*"
module asm "import flash.display.*"
module asm "import flash.text.*"
module asm "import flash.events.*"
module asm "import flash.net.*"
module asm "import flash.system.*"
module asm "" 
module asm "public var gdomainClass:Class;"
module asm "public var gshell:Boolean = false;"
module asm "" 
module asm "public function establishEnv():void"
module asm "{"
module asm "  try"
module asm "  {"
module asm "    var ns:Namespace = new Namespace(\22avmplus\22);"
module asm "  "
module asm "    gdomainClass = ns::[\22Domain\22];"
module asm "    gshell = true;"
module asm "  }"
:
:

初見だと、どこがエントリーポイントかもわかりませんね

寄り道が過ぎたので、インストールに取りかかりましょう。
まず、 AlchemyToolkit/alchemy-darwin-v0.4a に移動します。

$ cd AlchemyToolkit/alchemy-darwin-v0.4a

で、 ./config を実行します。

$ ./config 
Generating alchemy-setup...
Turning execution bit on for Alchemy binaries...

Add "source /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/alchemy-setup" to your login script.
  "alc-home" takes you to the Alchemy install folder.
  "alc-on" puts Alchemy gcc toolchain replacements at the front of your path.
  "alc-off" restores original path.
  "alc-util" shows you various Alchemy-related environment vars
You need Flash 10 or AIR 1.5 and the Flex 3.2 SDK installed for testing.

alchemy-setup というファイルが作られました。「このファイルを login スクリプトに入れろ」と言われますが、とりあえず使うだけなので手動で実行するだけにします。

$ source alchemy-setup

そうすると、 alc-on alc-off が使えるようになります。これは、実際は環境変数 PATH などを切り替えることをします。
alc-on にした状態で gcc と叩くと achacks/gcc が実行されるといった具合ですね。
というわけで、 alc-on を実行します。

$ alc-on

環境変数を見てみましょう。

$ echo $PATH
/Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/achacks:/Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/bin:/Users/amachang/AlchemyTest/FlexSDK/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin:/opt/local/bin

先頭に、 achacks ディレクトリが追加されていますね。 alc-off を実行すれば環境変数は元に戻ります。
次は、 sample をコンパイルして実行してみたいですね。

サンプルをコンパイルして実行する

サンプルは、 Alchemy Toolkit の直下の samples ディレクトリにありました。

$ cd AlchemyToolkit/alchemy-darwin-v0.4a/samples/
$ ls -la
total 0
drwxr-xr-x  6 amachang  staff  204 11 14 03:09 .
drwxr-xr-x  9 amachang  staff  510 11 18 21:39 ..
drwxr-xr-x  2 amachang  staff  170 11 14 03:09 AS3API
drwxr-xr-x  2 amachang  staff  204 11 14 03:09 AS3Lib
drwxr-xr-x  2 amachang  staff  102 11 14 03:09 HelloFlash
drwxr-xr-x  3 amachang  staff  204 11 14 03:09 stringecho
HelloFlash

まずは、 HelloFlash を実行してみましょう。
HelloFlash ディレクトリの中には HelloFlash.c というファイルが入っていました。
内容はこんな感じ

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
	printf("Hello Flash!\n");
}

とりあえず、なにも考えずに gccコンパイルしてみます

$ cd HelloFlash
$ gcc HelloFlash.c 
WARNING: While resolving call to function 'main' arguments were dropped!

45416.achacks.swf, 354510 bytes written

なんか、 WARNING が出ましたが成功したみたいです。
そして、 a.exe というファイルが出来ました。

$ ls -la
total 704
drwxr-xr-x  2 amachang  staff     136 11 18 21:51 .
drwxr-xr-x  6 amachang  staff     204 11 14 03:09 ..
-r--r--r--  1 amachang  staff     104 11 14 03:09 HelloFlash.c
-rwxr-xr-x  1 amachang  staff  354590 11 18 21:51 a.exe

何も考えずに実行してみましょう。ドキドキ

$ ./a.exe 
/Users/amachang/AlchemyTest/FlexSDK/bin/adl _sb_45518/app.xml 2> /tmp/adl.trace & echo $!
Hello Flash!

おおおお。ちゃんと Hello Flash! と表示されました!

ちょっと寄り道
この a.exe っていうのがどういうファイルなのか気になったので調べてみる

$ file a.exe 
a.exe: a /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/bin/swfbridge script text executable

swfbridge というコマンドで実行されるスクリプト?開いてみる。
バイナリだった。。

$ head -3 a.exe 
#!/Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/bin/swfbridge
FWS
(バイナリ)

って感じのファイル。
FWS って swf ファイルのマジックだったっけ?
そんな感じのファイルらしい。 swfbridge ってどんなものなんだろう。 adl を呼び出してるみたいだけど。
app.xml っていうファイルを自動で作って adl を呼び出すツールかな
ちょっと strings してみた

$ strings /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/bin/swfbridge
PATH
Couldn't write setup
_sb_%d
Couldn't create temp dir
error: %s
pid%d
%s/app.xml
<application xmlns="http://ns.adobe.com/air/application/1.5">
<id>com.adobe.%s</id>
<version>1.0</version>
<filename>%s</filename>
<name>%s</name>
<initialWindow>
<x>0</x>
<y>0</y>
<visible>false</visible>
<content>app.swf</content>
</initialWindow>
</application>
%s/app.swf
SWF: %s
:

おおお。 XML はけーん。 a.exe から app.xml と app.swf を作って adl に渡すみたいだなー。

またもや脱線しすぎました。

stringecho

次のサンプルは stringecho です。

$ cd ../stringecho

とりあえず、ディレクトリ構成は以下のようは感じになっています。

$ tree
.
|-- as3
|   `-- EchoTest.as
|-- readme.txt
`-- stringecho.c

1 directory, 3 files

readme.txt がありますね。でも、読んじゃうともったいないので、何も考えずにコンパイルしてみます。

$ gcc stringecho.c 
WARNING: While resolving call to function 'main' arguments were dropped!

46476.achacks.swf, 355002 bytes written

なんか、成功しました。では、実行します。ドキドキ

$ ./a.exe

おおおお

AIR さんに怒られました!これはこれで嬉しいですね。
というわけで、 readme.txt を読みます。
なるほど、以下のように swc オプションを付けることで、 swf から使える *.swc ファイルになるようになるようです。

$ gcc stringecho.c -swc -o stringecho.swc
WARNING: While resolving call to function 'main' arguments were dropped!

47506.achacks.swf, 362570 bytes written
frame rate: 60
frame count: 1
69 : 4
72 : 362500
76 : 33
1 : 0
0 : 0
frame rate: 24
frame count: 1
69 : 4
77 : 506
64 : 31
63 : 16
65 : 4
9 : 3
41 : 26
82 : 471
1 : 0
0 : 0
  adding: catalog.xml (deflated 75%)
  adding: library.swf (deflated 61%)

で、 stringecho.swc ファイルが出来るので、 EchoTest.as のコンパイル時に指定してあげます。

$ mxmlc -library-path+=stringecho.swc --target-player=10.0.0 as3/EchoTest.as

そうすると、 as3/EchoTest.swf が出来ます。
ただ、一見これを実行しても何もおきません。
以下のように、 trace 関数に結果を渡しているので、結果を見るには mm.cfg とかめんどくさい設定をしなければならないからです。

package {
    import flash.display.Sprite;
    import cmodule.stringecho.CLibInit;

    public class EchoTest extends Sprite
    {   
        public function EchoTest()
        {   
            var loader:CLibInit = new CLibInit;
            var lib:Object = loader.init();
    
            trace(lib.echo("foo"));
        }   
    }   
}

てっとり早く結果を見たいので、わざと Error を発生させましょう。

package {
    import flash.display.Sprite;
    import cmodule.stringecho.CLibInit;

    public class EchoTest extends Sprite
    {   
        public function EchoTest()
        {   
            var loader:CLibInit = new CLibInit;
            var lib:Object = loader.init();
    
            // 結果をエラーに乗せる
            throw Error(lib.echo("foo"));
        }   
    }   
}

というわけで、これで出来た EchoTest.swf をブラウザで開きます。

おおおお。ちゃんとError に foo という文字列が運ばれてきましたね!
この swf は以下のように swfbridge でも実行出来るようです。

$ ../../bin/swfbridge as3/EchoTest.swf


ちなみに、 C 言語側のコードはこんな感じです。 main で初期化して C 言語の関数を Flash から扱えるようにしているみたいですね。

//Simple String Echo example
//mike chambers
//mchamber@adobe.com

#include <stdlib.h>
#include <stdio.h>

//Header file for AS3 interop APIs
//this is linked in by the compiler (when using flaccon)
#include "AS3.h"

//Method exposed to ActionScript
//Takes a String and echos it
static AS3_Val echo(void* self, AS3_Val args)
{
	//initialize string to null
	char* val = NULL;
	
	//parse the arguments. Expect 1.
	//pass in val to hold the first argument, which
	//should be a string
	AS3_ArrayValue( args, "StrType", &val );
	
	//if no argument is specified
	if(val == NULL)
	{
		char* nullString = "null";
		//return the string "null"
		return AS3_String(nullString);
	}
	
	//otherwise, return the string that was passed in
	return AS3_String(val);
}

//entry point for code
int main()
{
	//define the methods exposed to ActionScript
	//typed as an ActionScript Function instance
	AS3_Val echoMethod = AS3_Function( NULL, echo );

	// construct an object that holds references to the functions
	AS3_Val result = AS3_Object( "echo: AS3ValType", echoMethod );

	// Release
	AS3_Release( echoMethod );

	// notify that we initialized -- THIS DOES NOT RETURN!
	AS3_LibInit( result );

	// should never get here!
	return 0;
}

自分でも何か作ってみる

最初に作った Src ディレクトリに移動して

$ cd /Users/amachang/AlchemyTest/Src
まずは hello

hello.c を書く

#include "AS3.h"

static AS3_Val hello(void* self, AS3_Val args)
{
    return AS3_String("Hello, world.");
}

int main()
{
    AS3_Val method = AS3_Function( NULL, hello );

    AS3_Val object = AS3_Object( "hello: AS3ValType", method );

    AS3_Release( method );

    AS3_LibInit( object );

    return 0;
}

Hello.as

package {
    import flash.display.Sprite;
    import cmodule.hello.CLibInit;

    public class Hello extends Sprite {
        public function Hello() {
            throw Error(new CLibInit().init().hello());
        }
    }
}

で、コンパイル

$ gcc hello.c -swc -o hello.swc && mxmlc -library-path+=hello.swc --target-player=10.0.0 Hello.as 
WARNING: While resolving call to function 'main' arguments were dropped!

49833.achacks.swf, 358742 bytes written
frame rate: 60
frame count: 1
69 : 4
72 : 358677
76 : 28
1 : 0
0 : 0
frame rate: 24
frame count: 1
69 : 4
77 : 506
64 : 31
63 : 16
65 : 4
9 : 3
41 : 26
82 : 471
1 : 0
0 : 0
  adding: catalog.xml (deflated 75%)
  adding: library.swf (deflated 60%)
設定ファイル "/Users/amachang/AlchemyTest/FlexSDK/frameworks/flex-config.xml" をロードしています
/Users/amachang/AlchemyTest/Src/Hello.swf (142645 bytes)

おおお。では、実行。 Hello, world! というエラーが出れば成功。ドキドキ

おおおおお。成功

クロージャを渡す

AS 側からクロージャを渡して

package {
    import flash.display.Sprite;
    import cmodule.hello.CLibInit;

    public class Hello extends Sprite {
        public function Hello() {
            var message:String = "Hello, ";
            new CLibInit().init().hello(function(message2:String):* {
                message += message2;
            }); 

            throw Error(message);
        }   
    }   
}

C 言語側で実行

#include <stdio.h>
#include <stdlib.h>
#include "AS3.h"

static AS3_Val hello(void* self, AS3_Val args)
{
    AS3_Val callback;
    AS3_ArrayValue(args, "AS3ValType", &callback);

    AS3_Call(callback, AS3_Undefined(), AS3_Array("StrType", " world."));

    return 0;
}

int main()
{
    AS3_Val method = AS3_Function( NULL, hello );

    AS3_Val object = AS3_Object( "hello: AS3ValType", method );

    AS3_Release( method );

    AS3_LibInit( object );

    return 0;
}


おおおお。成功
何かとサクサクできた。

Alchemy の gcc は何をしているか

hoge.c が hoge.swc になる過程を順に見て行きましょう。

1. C/C++LLVM 用のバイナリにコンパイルする
$ llvm-gcc -v -emit-llvm -nostdinc -I/Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/avm2-libc/include -I/usr/local/include --include /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/avm2-libc/avm2/AVM2Env.h hoge.c -c -o hoge.o

まずは、このように hoge.c が hoge.o にコンパイルされます。
この hoge.o は LLVMバイトコードです。

2. avm2-libc.l.bc とリンクする
$ llvm-ld -o=hoge -O5 -internalize-public-api-list=_start,malloc,free,__adddi3,__anddi3,__ashldi3,__ashrdi3,__cmpdi2,__divdi3,__fixdfdi,__fixsfdi,__fixunsdfdi,__fixunssfdi,__floatdidf,__floatdisf,__floatunsdidf,__iordi3,__lshldi3,__lshrdi3,__moddi3,__muldi3,__negdi2,__one_cmpldi2,__qdivrem,__adddi3,__anddi3,__ashldi3,__ashrdi3,__cmpdi2,__divdi3,__qdivrem,__fixdfdi,__fixsfdi,__fixunsdfdi,__fixunssfdi,__floatdidf,__floatdisf,__floatunsdidf,__iordi3,__lshldi3,__lshrdi3,__moddi3,__muldi3,__negdi2,__one_cmpldi2,__subdi3,__ucmpdi2,__udivdi3,__umoddi3,__xordi3,__subdi3,__ucmpdi2,__udivdi3,__umoddi3,__xordi3,__error /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/avm2-libc/lib/avm2-libc.l.bc hoge.o

次に、 llvm-ld でリンクされて hogehoge.bc が出来ます。( hoge はただ単に hoge.bc を実行する sh ファイル)
この avm2-libc.l.bc C 言語側に libc と同じ API を提供しているようですね。

3. llc というツールで ActionScript のコードを作る
$ llc -march=avm2 -avm2-use-memuser -o=hoge.as -avm2-package-name=cmodule.hoge hoge.bc

このツールで LLVM のコードが、なんと ActionScript のコードになります。

package cmodule.hoge {
// Start of file scope inline assembly
import flash.utils.*
import flash.display.*
import flash.text.*
import flash.events.*
import flash.net.*
import flash.system.*

public var gdomainClass:Class;
public var gshell:Boolean = false;

public function establishEnv():void
{
  try
  {
    var ns:Namespace = new Namespace("avmplus");
  
    gdomainClass = ns::["Domain"];
    gshell = true; 
  }
  catch(e:*) {}
  if(!gdomainClass)
  {
    var ns:Namespace = new Namespace("flash.system");
  
    gdomainClass = ns::["ApplicationDomain"];
  }
}

establishEnv();

public var glogLvl:int = Alchemy::LogLevel;

public function log(lvl:int, msg:String):void
{
  if(lvl < glogLvl)
:
:
(27436行)
4. asc.jar で、 as をコンパイルする
$ java -Xms16M -Xmx1024M -jar /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/bin/asc.jar -AS3 -strict -import /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/flashlibs/global.abc -import /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/flashlibs/playerglobal.abc -d -config Alchemy::Shell=false -config Alchemy::NoShell=true -config Alchemy::LogLevel=0 -config Alchemy::Vector=true -config Alchemy::NoVector=false -config Alchemy::SetjmpAbuse=false -swf cmodule.hoge.ConSprite,800,600,60 hoge.as 

そして、 hoge.as から hoge.swf が生成されます。

5. hoge.swf から ActionScriptバイトコードを取り出す
$ GetABC2.pl hoge hoge.swf.abc 
6. もっかい swf を作る
$ PutABC2.pl /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/achacks/swctmpl.swf temp.swf hoge.swf.abc cmodule/hello/CLibInit 
$ V10SWF.pl temp.swf library.swf 
7. catalog.xml を付けて zip で固めて swc にする

ちなみに、 catalog.xml はこんなの

<?xml version="1.0" encoding ="utf-8"?>
<swc xmlns="http://www.adobe.com/flash/swccatalog/9">
  <versions>
    <swc version="1.0" />
    <flex version="2.0" build="143452" />
  </versions>
  <features>
    <feature-script-deps />
    <feature-files />
  </features>
  <libraries>
    <library path="library.swf">
      <script name="cmodule/hello/CLibInit"  mod="1177909560000" >
        <def id="cmodule.hello:CLibInit" /> 
        <dep id="Date" type="e" /> 
        <dep id="Date" type="s" /> 
        <dep id="flash.utils:Dictionary" type="e" /> 
        <dep id="flash.utils:Dictionary" type="s" /> 
:
(略)
:
        <dep id="AS3" type="n" /> 
        <dep id="Object" type="i" /> 
      </script>
    </library>
  </libraries>
  <files>
  </files>
</swc>
$ zip hoge.swc catalog.xml library.swf 

おおおおおおおお。と言う訳で hoge.swc が出来ました。

まとめ

最後に

Alchemy 楽しいです!
みなさんも遊んでみてはいかがでしょうか。

Firefox メモ

alert の位置を知る

  • dom/src/base/nsGlobalWindow.cpp 4035 行目
  • nsGlobalWindow::Alert

JavaScript を書く

alert(0);
var img = document.createElement('img');
img.onload = function() { alert(1) };
alert(2);
img.src = 'http://www.hatena.ne.jp/images/top/h1.gif';
alert(3);
document.body.appendChild(img);
alert(4);

Firefox で開く

0 -> 2 -> 3 -> 1 -> 4

gdb 起動(Firefox のビルドが必要)

$ gdb MinefieldDebug.app/Contents/MacOS/firefox-bin
(gdb) b nsGlobalWindow::Alert
(gdb) r

alert(1) のところだけ見る

var img = document.createElement('img');
img.onload = function() { alert(1) };
img.src = 'http://www.hatena.ne.jp/images/top/h1.gif';
document.body.appendChild(img);

バックトレース

#0  nsGlobalWindow::Alert (this=0x1c97f420, aString=@0x18cc38d0) at /Users/amachang/mozilla/dom/src/base/nsGlobalWindow.cpp:4035
:
:
#16 0x1314fcf1 in nsImageLoadingContent::Event::Run (this=0x17f93040) at /Users/amachang/mozilla/content/base/src/nsImageLoadingContent.cpp:833
:
:

nsImageLoadingContent::Event::Run のコードを見てみる

(gdb) b nsImageLoadingContent::Event::Run
(gdb) c
Breakpoint 2, nsImageLoadingContent::Event::Run (this=0x17e55150) at /Users/amachang/mozilla/content/base/src/nsImageLoadingContent.cpp:823
823	  if (mMessage.EqualsLiteral("load")) {
(gdb) l
818	NS_IMETHODIMP
819	nsImageLoadingContent::Event::Run()
820	{
821	  PRUint32 eventMsg;
822	
823	  if (mMessage.EqualsLiteral("load")) {
824	    eventMsg = NS_LOAD;
825	  } else {
826	    eventMsg = NS_LOAD_ERROR;
827	  }
(gdb) l
828	
829	  nsCOMPtr<nsIContent> ourContent = do_QueryInterface(mContent);
830	
831	  nsEvent event(PR_TRUE, eventMsg);
832	  event.flags |= NS_EVENT_FLAG_CANT_BUBBLE;
833	  nsEventDispatcher::Dispatch(ourContent, mPresContext, &event);
834	
835	  return NS_OK;
836	}
837	
(gdb) 

うーん。送信してるところが見たい

(gdb) reverse-search "load"
823	  if (mMessage.EqualsLiteral("load")) {
(gdb) reverse-search "load"
249	    FireEvent(NS_LITERAL_STRING("load"));

あった!

249 行目で止めてみる

(gdb) b nsImageLoadingContent.cpp:249
(gdb) c
Breakpoint 4, nsImageLoadingContent::OnStopDecode (this=0x1c6dd39c, aRequest=0x1c6dbf80, aStatus=5505024, aStatusArg=0x0) at /Users/amachang/mozilla/content/base/src/nsImageLoadingContent.cpp:249
249	    FireEvent(NS_LITERAL_STRING("load"));
(gdb) 

バックトレース

Breakpoint 4, nsImageLoadingContent::OnStopDecode (this=0x1c6dd39c, aRequest=0x1c6dbf80, aStatus=5505024, aStatusArg=0x0) at /Users/amachang/mozilla/content/base/src/nsImageLoadingContent.cpp:249
249	    FireEvent(NS_LITERAL_STRING("load"));
(gdb) bt
#0  nsImageLoadingContent::OnStopDecode (this=0x1c6dd39c, aRequest=0x1c6dbf80, aStatus=5505024, aStatusArg=0x0) at /Users/amachang/mozilla/content/base/src/nsImageLoadingContent.cpp:249
:
:
#10 0x13227d41 in nsHTMLImageElement::SetSrc (this=0x1c6dd380, aValue=@0xbfff99a4) at /Users/amachang/mozilla/content/html/content/src/nsHTMLImageElement.cpp:219
#11 0x11223f98 in nsIDOMHTMLImageElement_SetSrc (cx=0xb67000, obj=0x1cc65340, id=441507292, vp=0xbfffa5f8) at dom_quickstubs.cpp:6846
:
:
#64 0x000026e3 in main (argc=1, argv=0xbffff8e8) at /Users/amachang/mozilla/browser/app/nsBrowserApp.cpp:156

ながいなー

よーく見ると

nsHTMLImageElement::SetSrc から load イベントが発火されていることが分かる
でも

alert(0);
var img = document.createElement('img');
img.onload = function() { alert(1) };
alert(2);
img.src = 'http://www.hatena.ne.jp/images/top/h1.gif';  // ここで発火されるのに
alert(3); // ここが alert(1) より先に起こるのは何故?
document.body.appendChild(img);
alert(4);

alert(3) で止めてバックトレースを見る

Breakpoint 1, nsGlobalWindow::Alert (this=0x1c97f420, aString=@0x1e75d860) at /Users/amachang/mozilla/dom/src/base/nsGlobalWindow.cpp:4035
4035	  FORWARD_TO_OUTER(Alert, (aString), NS_ERROR_NOT_INITIALIZED);
(gdb) p aString
$7 = (const nsAString_internal &) @0x1e75d860: {
  mData = 0x1e66a898, 
  mLength = 1, 
  mFlags = 5
}
(gdb) p aString->mData   
$8 = (PRUnichar *) 0x1e66a898
(gdb) p (char*)aString->mData 
$9 = 0x1e66a898 "3"
(gdb) bt
#0  nsGlobalWindow::Alert (this=0x1c97f420, aString=@0x1e75d860) at /Users/amachang/mozilla/dom/src/base/nsGlobalWindow.cpp:4035
#1  0x0049e1c1 in NS_InvokeByIndex_P (that=0x1c97f420, methodIndex=65, paramCount=1, params=0xbfff84e4) at /Users/amachang/mozilla/xpcom/reflect/xptcall/src/md/unix/xptcinvoke_unixish_x86.cpp:179
:
:

うーん。

別スレッドなのかな?

全スレッドバックトレース

(gdb) thread apply all bt

でも、 alert(1) も alert(2) も main から繋がってたから。たぶん同じスレッドだと思うんだけどなー

あそっか

イベントは発火されてもすぐには実行されなくて、しかるべきタイミングでイベントのキューを見に行くのか。

そのタイミングってどこよ

もっかい nsImageLoadingContent::Event::Run で止めてみる
Event::Run のバックとレースを見てみると alert(3) の nsGlobalWindow::Alert から呼ばれていることが分かった

#0  nsImageLoadingContent::Event::Run (this=0x1d67fe50) at /Users/amachang/mozilla/content/base/src/nsImageLoadingContent.cpp:823
#1  0x004833c0 in nsThread::ProcessNextEvent (this=0x719030, mayWait=1, result=0xbfffadfc) at /Users/amachang/mozilla/xpcom/threads/nsThread.cpp:510
#2  0x0040d198 in NS_ProcessNextEvent_P (thread=0x719030, mayWait=1) at nsThreadUtils.cpp:227
#3  0x1298c2da in nsXULWindow::ShowModal (this=0x16991bc0) at /Users/amachang/mozilla/xpfe/appshell/src/nsXULWindow.cpp:396
:
:
#10 0x133b6904 in nsGlobalWindow::Alert (this=0x1c53aa10, aString=@0x1d69f080) at /Users/amachang/mozilla/dom/src/base/nsGlobalWindow.cpp:4064

でも、なんで XULWindow::showModal が NS_ProcessNextEvent_P を呼び出すかが分からない

NS_ProcessNextEvent_P (マクロ名 NS_ProcessNextEvent)を呼び出してるところを grep してみるとたくさんあることに気がつく。
これらのタイミングでイベントは処理されるのか。

  • alert されたとき
  • window.open したとき
  • HTML のパースが終わったとき

などなど
つまり、

var img = document.createElement('img');
img.onload = function() { alert(1) };
img.src = 'http://www.hatena.ne.jp/images/top/h1.gif'; // この時点で画像のロードが終わっていて
alert(3); // この alert に誘発されて onload イベントハンドラが起動した
document.body.appendChild(img);

ってことか

じゃあ、 Firefox の画像って非同期にロードされてる訳じゃないのかな?

試しに、以下のような CGI を作ってみる

#! /usr/bin/perl

print "Content-Type: image/gif\n\n";

# 10 秒停止
sleep(10);

open IMG, "hogehogehogehoge.gif";
binmode IMG;
binmode STDIO;
while (my $bin = <IMG>) {
    print STDOUT $bin;
}
close IMG;

で、以下のような JavaScript からこの image.cgi を起動

var start = new Date;
var img = document.createElement('img');
img.onload = function() { alert(1) };
img.src = 'image.cgi';
document.body.appendChild(img);
alert(4);

お。ちゃんと非同期になってるなー

とりあえず

今日はこの辺で、結局なんで Firefox の img.src の設定が遅いかは分からなかった

1000 万行のソースコード

Linuxカーネルのコード行数、1000万行を上回る | スラド
1 日 1000 行読んだとしても 30 年かかるのかあ。
桁が違いすぎる。
その間もソースコードは増え続ける。

やがて、誰も保守することができなくなったソースコードの山。
人類はソースコードを解析するという目的で、ソースコードを理解するプログラムを作った。
そして、コンピュータは自立進化を始める。


これがコンピュータ対人類の戦争の始まりだったのだ

なーんて