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より一部抜粋。
newlibからglibcへの変更ログは、
- glibcを使う様に変更。(newlibから) · 038c7ba · usagi/Labyrinthian.WonderRabbitProject.net · GitHub
- https://github.com/usagi/Labyrinthian.WonderRabbitProject.net/commit/8a12bf0512c99069547c7c787b5c9622235d7735
- .nmfは自動生成にしたのでポイポイ · 4aea1fe · usagi/Labyrinthian.WonderRabbitProject.net · GitHub
- .nmf自動生成の,出力ミスを修正 · 2466998 · usagi/Labyrinthian.WonderRabbitProject.net · GitHub
- .nmf自動生成の.so配置パスと.soの実際の配置パスが異なってたので修正 · aaae640 · usagi/Labyrinthian.WonderRabbitProject.net · GitHub
- runnable-ld.soをlnするのを仕込み忘れてたので追加 · 6f02436 · usagi/Labyrinthian.WonderRabbitProject.net · GitHub
とかそういう感じ。.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のテストの為にメッセージのコンソールログ出力を特殊化するとかに労力を使うのも勿体無いしね。