WasmLinux: pid1が走行しない問題
なぜか start_kernel
(→ arch_call_rest_init
) → rest_init
→ user_mode_thread
→ kernel_clone
→ copy_process
で失敗しているようだ。。
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
ということになる。
wasm2cが出力したソースコードを読んでみると、該当箇所はココのようだ
これは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_ns
と time_ns_for_children
に相当していそうだ。
(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
これはココで宣言されている:
init_task
もコピー必要っぽい
これ辛いくない。。?なんか永久に出てきそうなんだけど。。
init_task
の本体は、
というわけで、これもシンボルをリネームして適当にコピーしてやる必要がある。
なんか異常なアドレスにアクセスする
* 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
しないと正常に動かないな。。それはそれとして、
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
}
}
}
この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
の中身が ゼロ ということになる。
この kthread_create_info
の内容は create_kthread
で消費される。 ...インライン化されてるな。。一旦Linux側も -O0 した方が良いかな。
kthread_create_info
をダンプしてみる
(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
が居る。で、この done
は DECLARE_COMPLETION_ONSTACK
で初期化されるもののようだ。
スタック破壊だった
単にスレッドのスタックを正常に割り当ててないだけだった。。そもそもstep実行時にクラッシュしたりしてる時点で気付けよ。。
ポイントは:
- そもそもWasmのページサイズは64KiBある
- wasm2cのコードをスレッドセーフにするには、スレッドの数だけモジュールをインスタンシエートする必要がある。今回はメモリを共有したかったので手でインスタンスをコピーしているが、不十分だった。
- 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;
とりあえず起動はした
まだまだ先は長い。。 legacy ptyを正常に初期化できない。