Open5

WasmLinux: 目指せHello World

okuokuokuoku

とりあえずユーザランドのlibc移植は目処が付いたので、Hello Worldの実行を目指す。

うーん。。pipe(2)して片方をカーネル側でprintしつづけるのが良いかな。。とりあえずユーザランドをprintfデバッグできるようにするのが先決。その後libcのprintf → 引数を渡せるように変更 → マルチプロセス対応 → busyboxのtelnetdと進んでいくのが良いんじゃないだろうか。

okuokuokuoku

なんかAssertする

mplite.c:287: mplite_unlink: Assertion `(handle->aCtrl[i] & MPLITE_CTRL_LOGSIZE) == iLogsize' failed.

メモリ破壊が起きてるな。。

mplite_unlink(handle=0x000000000296c320, i=16519040, iLogsize=6) at mplite.c:287:5

データブレークポイントを張る:

(lldb) watchpoint set variable mpool.aCtrl[16519040]

... カーネル側から書かれてるのはおかしいよな。。Wasm側アドレス 1076035456 は 0x7ffe37c51f80 で確かにwatchpointのアドレスになっている。

(lldb) bt
* thread #2, name = 'runner', stop reason = watchpoint 1
  * frame #0: 0x000000000040305b runner`i64_store(mem=0x00007ffdf0000b80, addr=1076035456, value=0) at lin.c:164:1
    frame #1: 0x0000000001244333 runner`w2c_kernel_number(instance=0x00007ffdf0000b70, var_p0=1076035573, var_p1=1076035560, var_p2=0, var_p3=1076035480) at lin.c:1583867
    frame #2: 0x000000000123fae5 runner`w2c_kernel_vsnprintf(instance=0x00007ffdf0000b70, var_p0=1076035552, var_p1=1076035573, var_p2=305142, var_p3=1076035636) at lin.c:1581617
    frame #3: 0x0000000001234523 runner`w2c_kernel_vprintk_store(instance=0x00007ffdf0000b70, var_p0=0, var_p1=1, var_p2=0, var_p3=305121, var_p4=1076035632) at lin.c:1577055
    frame #4: 0x0000000001235d5d runner`w2c_kernel_vprintk_emit(instance=0x00007ffdf0000b70, var_p0=0, var_p1=0, var_p2=0, var_p3=305121, var_p4=1076035632) at lin.c:1577652
    frame #5: 0x0000000001237eb2 runner`w2c_kernel_vprintk_default(instance=0x00007ffdf0000b70, var_p0=305121, var_p1=1076035632) at lin.c:1578369
    frame #6: 0x00000000011a9d8b runner`w2c_kernel_vprintk(instance=0x00007ffdf0000b70, var_p0=305121, var_p1=1076035632) at lin.c:1522525
    frame #7: 0x000000000122a66e runner`w2c_kernel_0x5Fprintk(instance=0x00007ffdf0000b70, var_p0=305121, var_p1=1076035632) at lin.c:1573232
    frame #8: 0x0000000001195e36 runner`w2c_kernel_free_area_init_node(instance=0x00007ffdf0000b70, var_p0=0) at lin.c:1514591
    frame #9: 0x0000000001194ae1 runner`w2c_kernel_free_area_init(instance=0x00007ffdf0000b70, var_p0=1076035784) at lin.c:1514124
    frame #10: 0x000000000042a4e7 runner`w2c_kernel_bootmem_init(instance=0x00007ffdf0000b70, var_p0=3406) at lin.c:193940:3
    frame #11: 0x0000000001297f31 runner`w2c_kernel_setup_arch(instance=0x00007ffdf0000b70, var_p0=1076035836) at lin.c:1618053
    frame #12: 0x00000000010ff71d runner`w2c_kernel_start_kernel(instance=0x00007ffdf0000b70) at lin.c:1454998
    frame #13: 0x0000000001298dd2 runner`w2c_kernel_lkl_run_kernel(instance=0x00007ffdf0000b70, var_p0=0) at lin.c:1618263

スタックポインタがおかしい。つまりスタックポインタの初期値が狂ってるな。。?

(lldb) p *instance
(w2c_kernel) $13 = {
  w2c_env_instance = NULL
  w2c_0x5F_stack_pointer = 1076035440
  w2c_memory = (data = "", pages = 16429, max_pages = 65536, size = 1076690944, is64 = false)

https://github.com/okuoku/lkl-wasm/commit/6f58e4dcfad7d3b532c621b9925b0c52613b7216

okuokuokuoku

syscallが成功しない

-22 を返却する。これは EINVAL で引数が正常に渡っていないようだ。

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/tools/include/uapi/asm-generic/errno-base.h#L26

[    0.564232] ------------[ cut here ]------------

[    0.564312] WARNING: CPU: 0 PID: 1 at arch/lkl/kernel/threads.c:129 0xdeadcafe

[    0.564455] CPU: 0 PID: 1 Comm: host0 Tainted: G        W          6.1.0+ #

[    0.564514]  Call Trace:

[    0.564572]  #00 [<0x000000000020a290>] 0x20a290

[    0.564725]  #01 [<0x0000000000000000>] 0x0

[    0.564926] ---[ end trace 0000000000000000 ]---

Ret: -22, 0, 0

WARNINGはココ。

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/arch/lkl/kernel/threads.c#L129

こういうときに自由にブレークポイント張れるのがwasm2cでデバッグする意義だよな。。

(lldb) b w2c_kernel_switch_to_host_task
Breakpoint 1: where = runner`w2c_kernel_switch_to_host_task + 15 at lin.c:1554882, address = 0x00000000011fad0f

周りのコードを読むと、どうもTLS関連がおかしいようだ。

とりあえず、実際に pipe2 がエラーを返却したのは確かで、

(lldb)
Process 134410 stopped
* thread #1, name = 'runner', stop reason = step over
    frame #0: 0x00000000005bb48b runner`w2c_kernel_0x5F_do_pipe_flags(instance=0x000000000292c200, var_p0=2292624, var_p1=2292632, var_p2=2292640) at lin.c:342639:10
   342636         var_l3 = var_i0;
   342637         var_i0 = var_p2;
   342638         var_i1 = 4294424447u;
-> 342639         var_i0 &= var_i1;
   342640         if (var_i0) {goto var_B0;}
   342641         var_i0 = var_p1;
   342642         var_i1 = var_p2;

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/fs/pipe.c#L959-L960

このとき flags = 2292640 。ゴミが渡っている。。

okuokuokuoku

syscallディスパッチが正しくない

lklは関数ポインタ経由でsyscallのディスパッチをしているが、ここで関数を可変長引数の関数にキャストしてしまっている:

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/arch/lkl/kernel/syscalls.c#L22

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/arch/lkl/kernel/syscalls.c#L43-L44

これはC言語的には未定義のはずで、これが正しく実施できるアーキテクチャは限られている(常識的なABIは殆どセーフだけど)。実際これはWasm的には正しくないシーケンスになる ...がwasm2cはこれを弾けない。

frame #2: 0x000000000129656d runner`w2c_kernel_lkl_syscall(instance=0x000000000292c200, var_p0=59, var_p1=1059512320) at lin.c:1617613
   1617610        var_i0 = var_l3;
   1617611        var_i1 = var_l2;
   1617612        var_i2 = var_l4;
-> 1617613        var_i0 = CALL_INDIRECT(instance->w2c_T0, u32 (*)(void*, u32, u32), w2c_kernel_t4, var_i2, instance->w2c_T0.data[var_i2].module_instance, var_i0, var_i1);
   1617614        var_l4 = var_i0;
   1617615        w2c_kernel_task_work_run(instance);
   1617616        var_i0 = var_p0;

lkl_syscall に引数カウントを渡して、再キャストして呼ぶしかないな。。

okuokuokuoku

引数の数を渡すようにした

https://github.com/okuoku/lkl-wasm/commit/44aa5f2142c206f938d5652b434a3f8990825da2

diff --git a/arch/lkl/include/asm/syscalls.h b/arch/lkl/include/asm/syscalls.h
index 4116e8a0cc6fc1..07d65ebfbb2e59 100644
--- a/arch/lkl/include/asm/syscalls.h
+++ b/arch/lkl/include/asm/syscalls.h
@@ -3,7 +3,11 @@
 
 int syscalls_init(void);
 void syscalls_cleanup(void);
+#ifndef __wasm__
 long lkl_syscall(long no, long *params);
+#else
+long lkl_syscall(long no, int nargs, long *params);
+#endif
 void wakeup_idle_host_task(void);
 
 #define sys_mmap sys_mmap_pgoff

まぁ仕方ないね。。

If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.

Cはcompatibleでない関数ポインタにconvertして呼んだ場合の挙動はundefinedとしている。(なので厳密な禁止ではない)

For two function types to be compatible, both shall specify compatible return types. Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types.

関数ポインタがcompatibleであるためには、 ... (ellipsis) の使用有無も一致しなければならない。今回のケースでは、

long syscall_XX(long a, long b);

を、

long syscall_XX(long a, ...);

にキャストして呼んでしまっているのでcompatibleでない。ただし、常識的なABIでは6個以下の可変長引数関数に整数のみを積んで呼ぶときのABIは通常の関数呼出しとかわらないのでLKLのような使い方でも大抵のアーキテクチャで動作する。(が、WebAssemblyはそうではない)