C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

NaCl tips; newlib → glibc

NaClのツールチェインはnewlibとglibcを使える。これまではとりあえずnewlibを使ってた。でも、boost::lexical_castを使おうかと思ったらnewlibではどうもロケール周りの実装でコンパイルエラーがぽぽぽぽーんするのでglibcを使う事に。しかしglibcを使う場合はnewlibを使う場合に比べてちょっと面倒だったのでメモを残す事に。

newlibを使う場合

先に一応簡潔に触れておく。

  • Pepper APIつこうたC++ソースをnewlibのg++でコンパイルして.nexeを生成
  • .nexeをロードする為のマニフェストファイル.nmfを書く(下記参照)
  • .htmlでembedから.nmfをapplication/x-naclとして呼ぶ

.nexe

{
  "program": {
    "x86-64": { "url": "hoge_x86_64.nexe" },
    "x86-32": { "url": "hoge_i686.nexe" }
  }
}

とか基本的にはこれだけ。ちなみに.nmfはJSONと公言されているけど、実際の所はJSON風の文法に厳しい何か。ハッシュオブジェクトのキーのクォートを省略したりシングルクォートを使うとPepperがパースエラーで止まる。ハッシュオブジェクトの末尾の要素の後に,を書いても止まる。

glibcを使う場合

手順を概要だけ書くと同じなのだけど、具体的には一仕事増える。

  • Pepper APIつこうたC++ソースをglibcのg++でコンパイルして.nexeを生成
  • .nexeをロードする為のマニフェストファイル.nmfを若干変態気味に書く(下記参照)
  • 必要な.soを.nmfに記述したパスに用意する
  • .htmlでembedから.nmfをapplication/x-naclとして呼ぶ

.nexe

{
  "program": {
    "x86-64": { "url": "lib64/runnable-ld.so" },
    "x86-32": { "url": "lib32/runnable-ld.so" }
  },
  "files": {
    "bpthread.so.32d9fc17": {
      "x86-64": { "url": "lib64/bpthread.so.32d9fc17" },
      "x86-32": { "url": "lib32/bpthread.so.32d9fc17" }
    },
    "libppapi_cpp.so": {
      "x86-64": { "url": "lib64/libppapi_cpp.so" },
      "x86-32": { "url": "lib32/libppapi_cpp.so" }
    },
    "libppapi_gles2.so":{
      "x86-64": { "url": "lib64/libppapi_gles2.so" },
      "x86-32": { "url": "lib32/libppapi_gles2.so" }
    },
    "libstdc++.so.6":{
      "x86-64": { "url": "lib64/libstdc++.so.6" },
      "x86-32": { "url": "lib32/libstdc++.so.6" }
    },
    "libm.so.32d9fc17":{
      "x86-64": { "url": "lib64/libm.so.32d9fc17" },
      "x86-32": { "url": "lib32/libm.so.32d9fc17" }
    },
    "liibgcc_s.so.1":{
      "x86-64": { "url": "lib64/liibgcc_s.so.1" },
      "x86-32": { "url": "lib32/liibgcc_s.so.1" }
    },
    "libc.so.32d9fc17":{
      "x86-64": { "url": "lib64/libc.so.32d9fc17" },
      "x86-32": { "url": "lib32/libc.so.32d9fc17" }
    },
    "main.nexe": {
      "x86-64": { "url": "hoge_x86_64.nexe" },
      "x86-32": { "url": "hoge_i686.nexe" }
    }
  }
}

ある程度経験豊富なプログラマーなら.nmfを眺めたら何をしているのか察しが付くと思う。newlibでは"program"で直接.nexeプロセスをロードしていたものが、glibcではrunnable-ld.soをロードしている。ldと言えばDSO(.so共有オブジェクトを実行時に動的リンク、とある不自由なOSで云う.dll相当)。そして.nmfに追加された"files"にずらずらと.soがアーキテクチャー毎に記述してあり、"main.nexe"として本来実行したかった.nexeを記述している。

Pepper SDKのglibcのg++で生成した.nexeに必要な.soファイル群は.nexeをビルドした後、

% $NACL_SDK_ROOT/toolchain/linux_x86_glibc/bin/x86_64-nacl-objdump -p hoge_x86_64.nexe | grep NEEDED
  NEEDED               libpthread.so.32d9fc17
  NEEDED               libppapi_cpp.so
  NEEDED               libppapi_gles2.so
  NEEDED               libstdc++.so.6
  NEEDED               libm.so.32d9fc17
  NEEDED               libgcc_s.so.1
  NEEDED               libc.so.32d9fc17

とか

% $NACL_SDK_ROOT/toolchain/linux_x86_glibc/bin/i686-nacl-objdump -p Labyrinthian_i686.nexe | grep NEEDED
  NEEDED               libpthread.so.32d9fc17
  NEEDED               libppapi_cpp.so
  NEEDED               libppapi_gles2.so
  NEEDED               libstdc++.so.6
  NEEDED               libm.so.32d9fc17
  NEEDED               libgcc_s.so.1
  NEEDED               libc.so.32d9fc17

とかして調べられる。こうして調べた.soファイル群を:

  • x86_64 用なら
    • $NACL_SDK_ROOT/toolchain/linux_x86_glibc/x86_64-nacl/lib/libstdc++.so.6 など
  • i686 用なら

からhttpdでアクセス取得可能な場所へ調達して来る。cpなりlnなりして必要ならchmodる。

これでglibcを使ったNaClを動作させる事ができる。

面倒臭いので

おまけ。omakeではなくgnu makeだけど。

Labyrinthian_x86_64.nexe: Labyrinthian.cxx $(THIS_MAKE)
        $(CXX) -o $@ $< -m64 $(CXXFLAGS)

.PHONY: _site_dir
_site_dir:
        @if [ -d _site ]; then rm -rf _site/*; else mkdir _site; fi;

.PHONY: glibc_so_64
glibc_so_64: _site_dir Labyrinthian_x86_64.nexe
        @if [ -d _site/lib64 ]; then rm -rf _site/lib64/*; else mkdir _site/lib64; fi;
        @ln -v $(TC_PATH)/x86_64-nacl/lib/runnable-ld.so _site/lib64/
        @for a in `$(TC_PATH)/bin/x86_64-nacl-objdump -p Labyrinthian_x86_64.nexe | grep NEEDED | tr NEEDED " " | sed "s/^[ ]*//" | tr "\n" " "`; do ln -v $(TC_PATH)/x86_64-nacl/lib/$${a} _site/lib64/$${a}; done;
        @chmod 644 _site/lib64/*

.PHONY: Labyrinthian.nmf
Labyrinthian.nmf: _site_dir Labyrinthian_x86_64.nexe
        @echo '{' >> Labyrinthian.nmf
        @echo '  "program": {' >> Labyrinthian.nmf
        @echo '     "x86-64": { "url": "lib64/runnable-ld.so" },' >> Labyrinthian.nmf
        @echo '     "x86-32": { "url": "lib32/runnable-ld.so" }' >> Labyrinthian.nmf
        @echo '  },' >> Labyrinthian.nmf
        @echo '  "files": {' >> Labyrinthian.nmf
        @for a in `$(TC_PATH)/bin/i686-nacl-objdump -p Labyrinthian_i686.nexe | grep NEEDED | tr NEEDED " " | sed "s/^[ ]*//" | tr "\n" " "`;\
                do\
                echo "    \"$${a}\": {" >> Labyrinthian.nmf;\
                echo "      \"x86-64\": { \"url\": \"lib64/$${a}\"}," >> Labyrinthian.nmf;\
                echo "      \"x86-32\": { \"url\": \"lib32/$${a}\"}" >> Labyrinthian.nmf;\
                echo '    },' >> Labyrinthian.nmf;\
                done;
        @echo '    "main.nexe": {' >> Labyrinthian.nmf
        @echo '      "x86-64": { "url": "Labyrinthian_x86_64.nexe" },' >> Labyrinthian.nmf
        @echo '      "x86-32": { "url": "Labyrinthian_i686.nexe" }' >> Labyrinthian.nmf
        @echo '    }' >> Labyrinthian.nmf
        @echo '  }' >> Labyrinthian.nmf
        @echo "}" >> Labyrinthian.nmf

テスト中のLabyrinthianのMakefileより一部抜粋。

  • .nexeを生成した後、必要な.soファイル群を調べて自動的にlnで拾ってくる。
  • .nexeから.nmfを自動的に生成する。(但しx86_64しか見ないでi686も生成している手抜き注意)

newlibからglibcへの変更ログは、

とかそういう感じ。.nmfを.gitignoreに入れてるのはMakefileで自動生成する様にしたのでソースファイルとしては不要になった為。

※とりあえず仕様でg++のオプションに-I/usr/includeが増えてるけど、これはシステムに放り込んであるincludeをそのまま使える様にした手抜き。厳密には異なる処理系向けのincludeなので手抜きせずにnaclのincludeとの共用に1つパス用を用意してそこにlnでincludeたちを用意してあげるのが安全だと思われます。

おまけのおまけ

さて、そもそも何でglibcにしたかと言うと、Pepper19現在nacl-g++は4.4.3なのです。一応NaCl的にはまだC++03で使ってねという事になっていますが、実際のところはg++の実装に併せてC++11は使える様なんですね(もちろん-std=c++0xとか要る)。ところが、std::to_stringはGCC4.5からっぽい。

Pepper APIを試すに当たり、NaCl側で処理した数値をpp::Instance::PostMessageからJavaScriptにメッセージングしたりとかするのにltoaとか生で使うの嫌でござる、という訳でboost::lexical_castを使おうとした訳です。するとnewlibではロケール周りの実装に原因があるっぽいコンパイルエラーが起きたのでglibcへ、という流れ。

これで引き続きのpp::Instanceの入力やAudio、そしてGLES2のお勉強を安心して続けられる。

おまけのおまけの追記

pp::Instance::PostMessageはstd::stringではなくて数値も投げる事はできるんだけど、JavaScriptで投げられたメッセージを整形して表示とか嫌(スクリプトとか使う意味無いのに使うとか面倒臭い)なのですよ・x・ APIのテストの為にメッセージのコンソールログ出力を特殊化するとかに労力を使うのも勿体無いしね。