WebGL-Native: Linux移植
prev: https://zenn.dev/okuoku/scraps/b4b12919554ab0
next: https://zenn.dev/okuoku/scraps/4f74b4e887ee14
とりあえず32bitでも動きそうなことはわかったので、次はLinuxに移植する。
直接ビルドに対応する
以前一旦LLVMを経由してビルドするような機能を追加 してるけど、gccで直接ビルドするならそういう配慮は不要なので直接ビルドできるようにした。
CMakeには仮想的なライブラリとしてオブジェクト(.o)を直接表現する OBJECT
ライブラリがあるのでそれを使用している。
FAILED: libappdll_app.so
: && /usr/bin/cc -fPIC -O2 -g -DNDEBUG -shared -Wl,-soname,libappdll_app.so -o libappdll_app.so CMakeFiles/app_wasm.dir/app_wasm.c.o CMakeFiles/appdll_app.dir/app_stub.c.o CMakeFiles/appdll_app.dir/home/oku/repos/cwgl/wasmstub/rt.c.o && :
/usr/bin/ld: CMakeFiles/app_wasm.dir/app_wasm.c.o: relocation R_X86_64_PC32 against symbol `wasm_rt_call_stack_depth' can not be used when making a shared object。 -fPIC を付けて再コンパイルしてください。
/usr/bin/ld: 最終リンクに失敗しました: bad value
collect2: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.
... 必要なツールチェーンオプションが渡ってこないので直接 .c をビルドする形に変更した。
NULL
が無い
/home/oku/repos/cwgl/apps/../wasmstub/stub.inc.c: In function ‘stub_library_get_import’:
/home/oku/repos/cwgl/apps/../wasmstub/stub.inc.c:357:25: error: ‘NULL’ undeclared (first use in this function)
357 | const char* name0 = NULL;
| ^~~~
/home/oku/repos/cwgl/apps/../wasmstub/stub.inc.c:357:25: note: ‘NULL’ is defined in header ‘<stddef.h>’; did you forget to ‘#include <stddef.h>’?
... そうなのか。。
ヘッダをincludeしたときに定義されるシンボルは割とC標準ライブラリ(libc)ごとに個性がある。なのでLinuxやCygwinで正常にビルドできてもBSDを壊したりするというかwabtで自分のパッチもやってしまっている。
(まぁこれはOpenBSDのlibcが悪いと思うけど...)
malloc
や free
という関数がある
[1/2] Building C object CMakeFiles/appdll_app.dir/app_wasm.c.o
app_wasm.c:3051:12: warning: conflicting types for built-in function ‘malloc’; expected ‘void *(long unsigned int)’ [-Wbuiltin-declaration-mismatch]
3051 | static u32 malloc(u32);
| ^~~~~~
app_wasm.c:5:1: note: ‘malloc’ is declared in header ‘<stdlib.h>’
4 | #include "app_wasm.h"
+++ |+#include <stdlib.h>
5 | #define UNLIKELY(x) __builtin_expect(!!(x), 0)
app_wasm.c:3052:13: warning: conflicting types for built-in function ‘free’; expected ‘void(void *)’ [-Wbuiltin-declaration-mismatch]
3052 | static void free(u32);
| ^~~~
app_wasm.c:3052:13: note: ‘free’ is declared in header ‘<stdlib.h>’
[2/2] Linking C shared library libappdll_app.so
そりゃあるでしょうね。。とりあえず fno-builtin
を付ける。
実際には、これはUbuntuに入っている wasm2c
が古いだけで、最新のものではシンボルに w2c_
を前置することでWASM内のシンボル名がビルドに影響しないようになっている。
エクスポートするシンボルの指定
/home/oku/repos/cwgl/wasmstub/rt.c: In function ‘__declspec’:
/home/oku/repos/cwgl/wasmstub/rt.c:282:51: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘{’ token
282 | the_module_root(const uint64_t* in, uint64_t* out){
| ^
/home/oku/repos/cwgl/wasmstub/rt.c:280:1: warning: type of ‘dllexport’ defaults to ‘int’ [-Wimplicit-int]
280 | __declspec(dllexport)
| ^~~~~~~~~~
/home/oku/repos/cwgl/wasmstub/rt.c:352: error: expected ‘{’ at end of input
352 |
|
まぁ __attribute__ ((visibility ("default")))
で良い。
dllexportやvisibilityは共有ライブラリが外部にエクスポートするシンボルを指定する。最近はデフォルトをhidden(エクスポートしない)に指定するのが普通なのでdefaultってどっちだよ感は有るが。。
GNUではディレクトリのファイルサイズを取得すると例外になる
filesystem error: cannot get file size: Is a directory [app2/appfs/idbfs]
... なんでVisual Studioは大丈夫なんだろう。。とりあえず取らないようにする。
const
性要求が厳しい
[1/5] Building CXX object CMakeFiles/cwgl.dir/yfrm/src-cxx17/yfrm-fs-cxx17.cpp.o
FAILED: CMakeFiles/cwgl.dir/yfrm/src-cxx17/yfrm-fs-cxx17.cpp.o
/usr/bin/c++ -DCWGL_DLL -DCWGL_SHARED_BUILD -DYFRM_DLL -DYFRM_SHARED_BUILD -Dcwgl_EXPORTS -I/home/oku/repos/cwgl/include -I/home/oku/repos/cwgl/angle/include -I/usr/include/SDL2 -O2 -g -DNDEBUG -fPIC -std=gnu++17 -MD -MT CMakeFiles/cwgl.dir/yfrm/src-cxx17/yfrm-fs-cxx17.cpp.o -MF CMakeFiles/cwgl.dir/yfrm/src-cxx17/yfrm-fs-cxx17.cpp.o.d -o CMakeFiles/cwgl.dir/yfrm/src-cxx17/yfrm-fs-cxx17.cpp.o -c /home/oku/repos/cwgl/yfrm/src-cxx17/yfrm-fs-cxx17.cpp
/home/oku/repos/cwgl/yfrm/src-cxx17/yfrm-fs-cxx17.cpp: In function ‘int yfrm_file_pathinfo(const char*, uint64_t*, uint64_t*, uint64_t*, uint64_t*)’:
/home/oku/repos/cwgl/yfrm/src-cxx17/yfrm-fs-cxx17.cpp:102:36: error: cannot bind non-const lvalue reference of type ‘std::filesystem::__cxx11::path&’ to an rvalue of type ‘std::filesystem::__cxx11::path’
102 | return file_info_gen(fs::u8path(path), flags, size, time_create, time_mod);
| ~~~~~~~~~~^~~~~~
/home/oku/repos/cwgl/yfrm/src-cxx17/yfrm-fs-cxx17.cpp:65:38: note: initializing argument 1 of ‘int file_info_gen(std::filesystem::__cxx11::path&, uint64_t*, uint64_t*, uint64_t*, uint64_t*)’
65 | file_info_gen(std::filesystem::path& path,
| ~~~~~~~~~~~~~~~~~~~~~~~^~~~
[2/5] Building C object ncccstubs/CMakeFiles/yfrm_stubs.dir/colroot.c.o
ninja: build stopped: subcommand failed.
これもなんでVisualStudioで通るのか謎だな。。
WASM_RT_MEMCHECK_SIGNAL_HANDLER
の指定忘れ
そういえば境界チェックを無効化するために指定が要るんだった。。
この設定を効かせるために、wabtはアップデートせざるを得なかった。(Ubuntu標準のものは古すぎる)
絵が出ない
... ここまでで起動するようになったが、 "Development Build" の文字しか描画されない。
これデバッグすんのか。。VirtualBoxで動かしているけど3Dアクセラレーションは切っているので、Mesaのソフトウェアレンダラで動作しているはず。
テクスチャや頂点は正常にアップロードされているのをRenderDocで確認できたので、例の如くラスタライザの設定が不味そう。
API Validationでエラーが見つかった
RenderDocにもAPI validation機能があったのを思い出したので掛けてみたら速攻で問題が見つかった。
Depth-Stencil attachmentにしないとダメなのか。。この制約はGLES3で追加されたらしい。
/* The OpenGL ES3 spec, in chapter 9.4. FRAMEBUFFER COMPLETENESS, says:
*
* "Depth and stencil attachments, if present, are the same image."
*
* This restriction is not present in the OpenGL ES2 spec.
*/
if (_mesa_is_gles3(ctx) &&
has_stencil_attachment && has_depth_attachment &&
!_mesa_has_depthstencil_combined(fb)) {
fb->_Status = GL_FRAMEBUFFER_UNSUPPORTED;
fbo_incomplete(ctx, "Depth and stencil attachments must be the same image", -1);
return;
}
これはWebGLも一緒なので、バックエンドがdepth-stencilバッファを作成できる( https://www.khronos.org/registry/OpenGL/extensions/OES/OES_packed_depth_stencil.txt )ならそっちを使うようにするか、そもそもGLES2コンテキストになるように頑張るかのどっちかが必要かな。。
ただ、MESAの MESA_GLES_VERSION_OVERRIDE
を設定するとRenderDocが正常にキャプチャできなくなってしまった。
... というか今気付いたけど、これそもそもRaspberry PiのVC4だとどうやっても動かないような。。(Stencil8 + Depth16を書く方法が無い気がする)
コードサイズがでかすぎる
もちろんRaspberry Pi上でもコンパイルできないので、Ubuntu上にクロスコンパイラを導入して wasm2c
部分だけをビルドすることにした。
sudo apt-get install g++-arm-linux-gnueabihf
→ EDIT: そもそもUbuntuはARMv6をターゲットできない (のでこのコンパイラも使えない)
wasm2c
の出力コードについては C 以外の依存関係は一切ないので追加のライブラリは不要でビルドできるが、出力が大きすぎてビルドに失敗してしまった。。
/tmp/cceyILfN.s:40419112: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:40419125: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:40419138: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:40419151: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:40419164: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:40419177: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:40419191: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:41433611: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:41433613: Error: Thumb2 branch out of range
そして -mlong_calls
付けても効かない。。(PICのPLT経由ジャンプは常に bl
なので ← バグ..?)
静的リンクするしかないかな。RaspberryPi OSは幸いPIEは使っていないようだ。
pi@raspberrypi:~/build/cwgl/duk-nccc $ hardening-check yuniduk
yuniduk:
Position Independent Executable: no, normal executable!
Stack protected: no, not found!
Fortify Source functions: no, only unprotected functions found!
Read-only relocations: yes
Immediate binding: no, not found!
Raspberry Pi 1 はそもそもThumb-2に対応していない
なんか正しくビルドしたつもりのツールチェーンでも
sorry, unimplemented: Thumb-1 hard-float VFP ABI
みたいにエラーになったり、ビルドした .so をロードしたら SIGILL
になったりと変だとは思ってたけど、 Raspberry Pi 1はARMv6なのでThumb-2に対応していない 。(実はThumb-2に対応したarmv6t2も無くは無いけどRPiは違う)
00000a90 <register_tm_clones>:
a90: 480a ldr r0, [pc, #40] ; (abc <register_tm_clones+0x2c>)
a92: 490b ldr r1, [pc, #44] ; (ac0 <register_tm_clones+0x30>)
a94: 4478 add r0, pc
a96: 4479 add r1, pc
a98: 1a0b subs r3, r1, r0
a9a: 0fd9 lsrs r1, r3, #31
a9c: 4a09 ldr r2, [pc, #36] ; (ac4 <register_tm_clones+0x34>)
a9e: eb01 01a3 add.w r1, r1, r3, asr #2 ★★ ここで SIGILLになる
aa2: 1049 asrs r1, r1, #1
aa4: 447a add r2, pc
aa6: b082 sub sp, #8
aa8: d005 beq.n ab6 <register_tm_clones+0x26>
aaa: 4b07 ldr r3, [pc, #28] ; (ac8 <register_tm_clones+0x38>)
aac: 58d3 ldr r3, [r2, r3]
aae: 9301 str r3, [sp, #4]
ab0: b10b cbz r3, ab6 <register_tm_clones+0x26>
ab2: b002 add sp, #8
ab4: 4718 bx r3
ab6: b002 add sp, #8
ab8: 4770 bx lr
aba: bf00 nop
abc: 016b65c8 .word 0x016b65c8
ac0: 016b65c6 .word 0x016b65c6
ac4: 016b6558 .word 0x016b6558
ac8: 00000054 .word 0x00000054
... ツールチェーンをリビルドして仕切りなおし。そもそも Thumb-2 が無いならThumbに拘る理由もないし。。 (素のThumbではそもそも整数演算しかできない)
8分掛けても起動しない
というわけで動くようにはなったけど、Unityのエンジン側の起動から8分経ってもシーンのロードが終わらない。。
$ ulimit -c unlimited
(再現)
$ kill -SIGQUIT 対象PID
でコアダンプを取ってバックトレースを見てみる。 gdb -c core
でコアダンプにアタッチして file <ELF名>
と sharedlibrary
でシンボルをロード。(メモリ不足でgdbと共存できない)
(gdb) sharedlibrary
(gdb) bt
#0 0x00023240 in duk__js_execute_bytecode_inner (
entry_thread=<optimized out>, entry_act=<optimized out>)
at /home/pi/repos/cwgl/duktape260/duk_js_executor.c:3290
#1 duk_js_execute_bytecode (exec_thr=0x0, exec_thr@entry=0x1132918)
at /home/pi/repos/cwgl/duktape260/duk_js_executor.c:2960
#2 0x00019954 in duk__handle_call_raw (thr=thr@entry=0x1132918, idx_func=5,
call_flags=call_flags@entry=0)
at /home/pi/repos/cwgl/duktape260/duk_js_call.c:2246
#3 0x0001a60c in duk_handle_call_unprotected (thr=thr@entry=0x1132918,
idx_func=<optimized out>, call_flags=call_flags@entry=0)
at /home/pi/repos/cwgl/duktape260/duk_js_call.c:2422
#4 0x000175b4 in duk_call (thr=thr@entry=0x1132918, nargs=nargs@entry=2)
at /home/pi/repos/cwgl/duktape260/duk_api_call.c:137
#5 0x000156b0 in nccc_cb_dispatcher (in=<optimized out>, out=0xbedced88)
at /home/pi/repos/cwgl/duk-nccc/duk-nccc.c:337
#6 0xb5b39134 in instub_Z_envZ____syscall146Z_iii (arg0=<optimized out>,
arg1=<optimized out>)
at /home/oku/repos/cwgl/apps/../wasmstub/stub.inc.c:254
#7 0xb59643f4 in w2c____stdio_write (w2c_p0=w2c_p0@entry=1047420,
w2c_p1=2347104, w2c_p1@entry=1183953, w2c_p2=w2c_p2@entry=12)
at /home/oku/repos/cwgl/apps/../wasmstub/stub.inc.c:196093
#8 0xb5964618 in w2c____stdout_write (w2c_p0=w2c_p0@entry=1047420,
w2c_p1=w2c_p1@entry=1183953, w2c_p2=w2c_p2@entry=12) at app2_wasm.c:7108113
#9 0xb59663d8 in w2c____fwritex (w2c_p0=w2c_p0@entry=1183953, w2c_p1=12,
w2c_p1@entry=376, w2c_p2=w2c_p2@entry=1047420) at app2_wasm.c:7078348
#10 0xb59664cc in w2c__out (w2c_p0=w2c_p0@entry=1047420,
w2c_p1=w2c_p1@entry=1183953, w2c_p2=w2c_p2@entry=376)
at app2_wasm.c:7075304
#11 0xb59667d4 in w2c__printf_core (w2c_p0=w2c_p0@entry=1047420, w2c_p1=376,
w2c_p2=<optimized out>, w2c_p3=<optimized out>, w2c_p4=<optimized out>,
w2c_p4@entry=2346944) at app2_wasm.c:7120699
#12 0xb5968a78 in w2c__vfprintf (w2c_p0=1047420, w2c_p1=w2c_p1@entry=1183936,
w2c_p2=0, w2c_p2@entry=2346768) at app2_wasm.c:7127712
#13 0xb5971e74 in w2c__printf (w2c_p0=1183936, w2c_p1=2346688)
at app2_wasm.c:7240836
#14 0xb416269c in w2c___ZN11ContextGLES6CreateEi (w2c_p0=2)
at app2_wasm.c:1265361
#15 0xb416294c in w2c___ZN13GfxDeviceGLES4InitE16GfxDeviceLevelGL (
w2c_p0=860188, w2c_p1=1) at app2_wasm.c:1264672
#16 0xb44a0a24 in w2c___Z19CreateGLESGfxDevice17GfxDeviceRenderer (
w2c_p0=19694464) at app2_wasm.c:2415453
#17 0xb5b83ab0 in w2c___Z19InitializeGfxDevicev () at app2_wasm.c:2153961
#18 w2c___Z24InitializeEngineGraphicsb (w2c_p0=0, w2c_p0=0)
at app2_wasm.c:16061
#19 w2c___Z24PlayerInitEngineGraphicsb.constprop.0 (w2c_p0=0, w2c_p0=0)
at app2_wasm.c:11265
#20 0xb44598fc in w2c___Z15InitWebGLPlayeriPPc (w2c_p1=18032984,
w2c_p0=<optimized out>) at app2_wasm.c:2030441
#21 w2c__main (w2c_p0=1, w2c_p1=2346064) at app2_wasm.c:9571
#22 0xb5b43e28 in nccc_Z__mainZ_iii (in=<optimized out>, out=0xbedcf1f0)
at /home/oku/repos/cwgl/apps/../wasmstub/stub.inc.c:255
#23 0x00015324 in nccc_call_trampoline (ctx=0x1132918)
at /home/pi/repos/cwgl/duk-nccc/duk-nccc.c:232
#24 0x00019a80 in duk__handle_call_raw (thr=thr@entry=0x1132918,
idx_func=idx_func@entry=1, call_flags=call_flags@entry=9)
at /home/pi/repos/cwgl/duktape260/duk_js_call.c:2268
#25 0x0001a60c in duk_handle_call_unprotected (thr=thr@entry=0x1132918,
idx_func=idx_func@entry=1, call_flags=call_flags@entry=9)
at /home/pi/repos/cwgl/duktape260/duk_js_call.c:2422
#26 0x00023c84 in duk__executor_handle_call (call_flags=9,
nargs=<optimized out>, idx=1, thr=<optimized out>)
at /home/pi/repos/cwgl/duktape260/duk_js_executor.c:2685
#27 duk__js_execute_bytecode_inner (entry_thread=<optimized out>,
entry_act=<optimized out>)
at /home/pi/repos/cwgl/duktape260/duk_js_executor.c:4776
#28 duk_js_execute_bytecode (exec_thr=0xe3, exec_thr@entry=0x1132918)
at /home/pi/repos/cwgl/duktape260/duk_js_executor.c:2960
#29 0x00019954 in duk__handle_call_raw (thr=thr@entry=0x1132918, idx_func=1,
call_flags=call_flags@entry=0)
at /home/pi/repos/cwgl/duktape260/duk_js_call.c:2246
#30 0x0001a60c in duk_handle_call_unprotected (thr=thr@entry=0x1132918,
idx_func=<optimized out>, call_flags=call_flags@entry=0)
at /home/pi/repos/cwgl/duktape260/duk_js_call.c:2422
--Type <RET> for more, q to quit, c to continue without paging--c
#31 0x000175b4 in duk_call (thr=thr@entry=0x1132918, nargs=nargs@entry=0) at /home/pi/repos/cwgl/duktape260/duk_api_call.c:137
#32 0x00014844 in dukload (filename=0x4eb00 "/home/pi/repos/cwgl/duk-nccc/bootstrap.js", flags=8, ctx=0x1132918) at /home/pi/repos/cwgl/duk-nccc/yuniduk.c:72
#33 main (argc=<optimized out>, argv=<optimized out>) at /home/pi/repos/cwgl/duk-nccc/yuniduk.c:91
... OpenGL Context の生成まで進んでたの。。?確かUnityは起動時にVendor名とかをダンプするから、それに変なバイトが混じったりしたのかな。
CreateShaderに失敗する
どうも glCreateShader
が 音もなく 失敗してしまっているようだ。OpenGL的なエラー等をセットすることもなく、サイレントにゼロを返却してくる。
Raspberry PiのOpenGLスタックはソースコードが公開されているので、一旦それを適当にビルドしてstatic linkしてgdbで見てみることにした。
Thread 1 "yuniduk" hit Breakpoint 1, glCreateShader (type=35633)
at /home/pi/repos/userland/interface/khronos/glxx/glxx_client.c:900
900 CLIENT_THREAD_STATE_T *thread = CLIENT_GET_THREAD_STATE();
(gdb) next
901 if (IS_OPENGLES_20(thread)) {
(gdb) p *thread
$1 = {error = 12288, bound_api = 12448, opengl = {context = 0x0, draw = 0x0,
read = 0x0}, openvg = {context = 0x0, draw = 0x0, read = 0x0},
high_priority = false,
merge_buffer = "\b@\000\000N\005", '\000' <repeats 31 times>, "\371\377x\231+\000\000\000\371\377\350\256+\000\000\000\371\377\230D\032\000\000\000\371\377\250\313&\000\000\000\371\377\210\222\033\000\000\000\371\377h3\036\000\000\000\371\377\200\205\036\000\000\000\371\377\320\020&\000\000\000\371\377\350\304\033\000\000\000\371\377\070\306\033\000\000\000\371\377\210\307\033\000\000\000\371\377\340\306&\000\000\000\371\377\060\310&\000\000\000\371\377\200\311&\000\000\000\371\377`\372\017\000\000\000\371\377\260\373\017\000\000\000\371\377\220\375\017\000\000\000\371\377\030\231)\000\000\000\371\377h\232)\000\000\000\371\377\270\233)\000\000\000\371\377H"..., merge_pos = 36, merge_end = 36,
glgeterror_hack = 0, async_error_notification = false}
(gdb) next
908 return 0;
thread
の opengl.context
が 0
なのが不味いようだ。これを普段セットしているところを探すのが良いかな。
... ここにstep inしようとしてローカルでビルドしたコードで統一したら正常に動作してしまった。。つまり、RaspberryPi OS標準配布のBroadcomスタックに何か問題があるということか。。ちょっと保留。