Open7

WasmLinux: pid1が走行しない問題

okuokuokuoku

なぜか start_kernel (→ arch_call_rest_init) → rest_inituser_mode_threadkernel_clonecopy_process で失敗しているようだ。。

okuokuokuoku

エラーコードは多分 EINVAL

lldbでねっとりとステップ実行してみたところ、エラーコードは 4294967274 となっていた。

(lldb)
Process 53301 stopped
* thread #2, name = 'runner', stop reason = step over
    frame #0: 0x0000000000f2b050 runner`w2c_lin_copy_process(instance=0x00007ffdf0000b70, var_p0=0, var_p1=0, var_p2=4294967295, var_p3=268795776) at lin.c:1252794
   1252791        instance->w2c_0x5F_stack_pointer = var_i0;
   1252792        var_i0 = var_l5;
   1252793        FUNC_EPILOGUE;
-> 1252794        return var_i0;
   1252795      }
   1252796
   1252797      u32 w2c_lin_dup_task_struct(w2c_lin* instance, u32 var_p0, u32 var_p1) {
(lldb) p var_i0
(u32) $0 = 4294967274

これは -22 だから、 EINVAL ということになる。

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

wasm2cが出力したソースコードを読んでみると、該当箇所はココのようだ

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/kernel/fork.c#L2046-L2053

これはwasm2cの出力では:

1251684   var_i0 = i32_load(&instance->w2c_memory, (u64)(var_i0) + 1266096u);
1251685   var_i0 = i32_load(&instance->w2c_memory, (u64)(var_i0));
1251686   var_l7 = var_i0; /* ★ たぶん `current` */
1251687   var_i0 = i32_load(&instance->w2c_memory, (u64)(var_i0) + 764u);
1251688   var_l8 = var_i0; /* ★ たぶん `current->nsp` */
...
1251726   var_i0 = var_l8; 
1251727   var_i0 = i32_load(&instance->w2c_memory, (u64)(var_i0) + 24u); /* ★ time_ns ? */
1251728   var_i1 = var_l8;
1251729   var_i1 = i32_load(&instance->w2c_memory, (u64)(var_i1) + 28u); /* ★ time_ns_for_children ? */
1251730   var_i0 = var_i0 != var_i1;
1251731   if (var_i0) {goto var_B0;} /* ★ 真なら EINVAL を返却するコードブロックに飛ぶ */

オフセット24と28は確かに time_nstime_ns_for_children に相当していそうだ。

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/include/linux/nsproxy.h#L31-L41

(lldb) p var_i0
(u32) $1 = 799219920
(lldb) p var_i1
(u32) $2 = 801120392
(lldb) p var_l8
(u32) $3 = 0

実際の変数の値を観察してみると、どうも current->nsproxy が NULLのようだ。 current のオフセット 1266096u = 0x1351b0 はマップファイルだと:

  1351b0   6bd021        4         ../vmlinux.a(arch/lkl/kernel/threads.o):(.data._current_thread_info)
  1351b0   6bd021        4                 _current_thread_info

これはココで宣言されている:

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

okuokuokuoku

init_task もコピー必要っぽい

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/include/asm-generic/vmlinux.lds.h#L399-L407

これ辛いくない。。?なんか永久に出てきそうなんだけど。。

init_task の本体は、

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/init/init_task.c#L60-L64

というわけで、これもシンボルをリネームして適当にコピーしてやる必要がある。

okuokuokuoku

なんか異常なアドレスにアクセスする

* thread #6, name = 'runner', stop reason = signal SIGSEGV: address access protected (fault address: 0x7ffef7a21ffc)
  * frame #0: 0x0000000000402da8 runner`i32_load(mem=0x00000000020e21d0, addr=4294967292) at lin.c:140:1
    frame #1: 0x0000000000f52079 runner`w2c_lin_complete(instance=0x00000000020e21c0, var_p0=2088656) at lin.c:1267688
    frame #2: 0x0000000000fa27c7 runner`w2c_lin_kthread(instance=0x00000000020e21c0, var_p0=799174848) at lin.c:1300013
    frame #3: 0x0000000000ff487b runner`w2c_lin_thread_bootstrap(instance=0x00007ffde4000b70, var_p0=799228864) at lin.c:1332602
    frame #4: 0x0000000001e69e8c runner`thr_trampoline(objid=13) at runner.cpp:196:12
    frame #5: 0x0000000001e6c195 runner`unsigned long std::__invoke_impl<unsigned long, unsigned long (*)(int), int>((null)=__invoke_other @ 0x00007ffdf5a1cd10, __f=0x00007ffde0000c10, (null)=0x00007ffde0000c08) at invoke.h:61:36
    frame #6: 0x0000000001e6c0ac runner`std::__invoke_result<unsigned long (*)(int), int>::type std::__invoke<unsigned long (*)(int), int>(__fn=0x00007ffde0000c10, (null)=0x00007ffde0000c08) at invoke.h:96:40
    frame #7: 0x0000000001e6bf8d runner`unsigned long std::thread::_Invoker<std::tuple<unsigned long (*)(int), int>>::_M_invoke<0ul, 1ul>(this=0x00007ffde0000c08, (null)=_Index_tuple<0, 1> @ 0x00007ffdf5a1cd70) at std_thread.h:292:26
    frame #8: 0x0000000001e6befe runner`std::thread::_Invoker<std::tuple<unsigned long (*)(int), int>>::operator()(this=0x00007ffde0000c08) at std_thread.h:299:20
    frame #9: 0x0000000001e6bec6 runner`std::thread::_State_impl<std::thread::_Invoker<std::tuple<unsigned long (*)(int), int>>>::_M_run(this=0x00007ffde0000c00) at std_thread.h:244:20
    frame #10: 0x00007ffff7ce31f3 libstdc++.so.6`execute_native_thread_routine + 19
    frame #11: 0x00007ffff7aae947 libc.so.6`start_thread + 759
    frame #12: 0x00007ffff7b34860 libc.so.6`__clone3 + 48

うーん。。 このコード -fwrapv しないと正常に動かないな。。それはそれとして、

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/kernel/sched/swait.c#L21-L31

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/kernel/sched/completion.c#L28-L38

1267677   var_i0 = var_p0; /* complete() の第一引数 struct completion */
1267678   var_i0 = i32_load(&instance->w2c_memory, (u64)(var_i0) + 4u);
1267679   var_l2 = var_i0; /* ★ これが何故かゼロになっている wait_queue_head_t wait */
1267680   var_i1 = var_p0;
1267681   var_i2 = 4u;
1267682   var_i1 += var_i2;
1267683   var_i0 = var_i0 == var_i1;
1267684   if (var_i0) {goto var_B1;} /* 今回はtakeされない if (list_empty(&q->task_list)) */
1267685   var_i0 = var_l2; /* ★ これが何故かゼロになっている */
1267686   var_i1 = 4294967292u;
1267687   var_i0 += var_i1;
1267688   var_i0 = i32_load(&instance->w2c_memory, (u64)(var_i0));
1267689   var_i0 = w2c_lin_wake_up_process(instance, var_i0);

のようなコードで、 i32_load(mem=0x00000000020e21d0, addr=4294967292) となっている。

... スレッド起動時のargumentから追うのが良いのかな。。

(lldb) p objtbl[13]
(hostobj_s) $3 = {
  type = OBJTYPE_THREAD
  id = 13
  obj = {
    thr = {
      func32 = 2657
      arg32 = 799167056
      ret = 0
      thread = 0x00007ffde0000be0
    }
  }
}

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

このargumentは kmalloc したアドレスが入るはずで、パラメーターを見てみると、

(lldb) p *(uint32_t *)&the_linux.w2c_memory.data[799167056]
(uint32_t) $4 = 799301632 /* thread_info へのポインタ */
(lldb) p *(uint32_t *)&the_linux.w2c_memory.data[799167060]
(uint32_t) $6 = 2579 /* 起動する関数 kthread */
(lldb) p *(uint32_t *)&the_linux.w2c_memory.data[799167064]
(uint32_t) $7 = 799167104 /* 引数 struct kthread_create_info へのポインタ */
(lldb) p the_linux.w2c_T0.data[2579].func
(wasm_rt_function_ptr_t) $22 = 0x0000000000fa23ee (runner`w2c_lin_kthread at lin.c:1299943)

状況的には kthread_create_info->done の中身が ゼロ ということになる。

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/kernel/kthread.c#L344

この kthread_create_info の内容は create_kthread で消費される。 ...インライン化されてるな。。一旦Linux側も -O0 した方が良いかな。

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/kernel/kthread.c#L746

okuokuokuoku

kthread_create_info をダンプしてみる

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/kernel/kthread.c#L38-L50

(lldb) p *(uint32_t *)&the_linux.w2c_memory.data[799174848]
(uint32_t) $0 = 2585 /* threadfn */
(lldb) p *(uint32_t *)&the_linux.w2c_memory.data[799174848 + 4]
(uint32_t) $1 = 799203712 /* data */
(lldb) p *(uint32_t *)&the_linux.w2c_memory.data[799174848 + 8]
(uint32_t) $2 = 4294967295 /* node 。0xffffffff 。 */
(lldb) p *(uint32_t *)&the_linux.w2c_memory.data[799174848 + 12]
(uint32_t) $3 = 0 /* result */
(lldb) p *(uint32_t *)&the_linux.w2c_memory.data[799174848 + 16]
(uint32_t) $4 = 2088656 /* done */
(lldb) p the_linux.w2c_T0.data[2585].func
(wasm_rt_function_ptr_t) $5 = 0x0000000000fb134c (runner`w2c_lin_rescuer_thread at lin.c:1305679)

確かに他のスレッドのbacktraceに init_rescuer が居る。で、この doneDECLARE_COMPLETION_ONSTACK で初期化されるもののようだ。

https://github.com/lkl/linux/blob/3023e6f25fbf6d5f95b4e7ebd011fa688434ce5f/kernel/kthread.c#L414-L429

okuokuokuoku

スタック破壊だった

単にスレッドのスタックを正常に割り当ててないだけだった。。そもそもstep実行時にクラッシュしたりしてる時点で気付けよ。。

https://github.com/okuoku/lkl-wasm/commit/b1ccda9e54c8b3aa43fbfcc4b5b1bdbcd2fe51cb

ポイントは:

  1. そもそもWasmのページサイズは64KiBある
  2. wasm2cのコードをスレッドセーフにするには、スレッドの数だけモジュールをインスタンシエートする必要がある。今回はメモリを共有したかったので手でインスタンスをコピーしているが、不十分だった。
  3. WasmのABIには128bytesのred zoneがあるので、その分を考慮して初期スタックを設定する必要がある。 https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md

__attribute__((optnone)) でインライン化を抑止しつつ確認した。

diff --git a/kernel/kthread.c b/kernel/kthread.c
index f97fd01a2932..96400a4e3627 100644
--- a/kernel/kthread.c
+++ b/kernel/kthread.c
@@ -388,7 +388,7 @@ int tsk_fork_get_node(struct task_struct *tsk)
        return NUMA_NO_NODE;
 }

-static void create_kthread(struct kthread_create_info *create)
+static void __attribute__((optnone)) create_kthread(struct kthread_create_info *create)
 {
        int pid;

@@ -715,7 +715,7 @@ int kthread_stop(struct task_struct *k)
 }
 EXPORT_SYMBOL(kthread_stop);

-int kthreadd(void *unused)
+int __attribute__((optnone)) kthreadd(void *unused)
 {
        struct task_struct *tsk = current;

diff --git a/kernel/sched/completion.c b/kernel/sched/completion.c
index d57a5c1c1cd9..cfb493b97eae 100644
--- a/kernel/sched/completion.c
+++ b/kernel/sched/completion.c
@@ -25,7 +25,7 @@
  * If this function wakes up a task, it executes a full memory barrier before
  * accessing the task state.
  */
-void complete(struct completion *x)
+void __attribute__((optnone)) complete(struct completion *x)
 {
        unsigned long flags;

diff --git a/kernel/sched/swait.c b/kernel/sched/swait.c
index 76b9b796e695..1ef0489409bc 100644
--- a/kernel/sched/swait.c
+++ b/kernel/sched/swait.c
@@ -18,7 +18,7 @@ EXPORT_SYMBOL(__init_swait_queue_head);
  * If for some reason it would return 0, that means the previously waiting
  * task is already running, so it will observe condition true (or has already).
  */
-void swake_up_locked(struct swait_queue_head *q)
+void __attribute__((optnone)) swake_up_locked(struct swait_queue_head *q)
 {
        struct swait_queue *curr;