C runtime/lib 通常都會有一個測試自己實做正確性的testsuite,像在glibc內部、就有兩套測試,一套是檢驗正確性、回歸測試用的testsuite
,另外一套是效能的benchtest
;而musl-libc的同等物叫作libc-test
的testsuite,目前由libm的作者 Nagy 在進行維運。
然而,在測試libc-test之前,我們先來處理一件事情會比較好,那就是 dynamic linking/loading 的問題。因為musl-libc的檢驗方式,會測試 statically-linked build, statically-linked run, dynamically-linked build, dynamically-linked run 這四個排列組合。而且因為他的編譯方式,我們先初步bringup dynamica,會有意外的好處。
為了打通dynamic linking/loading,我們必須修正原本來自RV64平台 arch/riscv32/reloc.h
中的relocation type定義:
diff ./arch/riscv32/reloc.h ./arch/riscv64/reloc.h
9c9
< #define LDSO_ARCH "riscv32" RISCV_FP_SUFFIX
---
> #define LDSO_ARCH "riscv64" RISCV_FP_SUFFIX
13c13
< #define REL_SYMBOLIC R_RISCV_32
---
> #define REL_SYMBOLIC R_RISCV_64
17,19c17,19
< #define REL_DTPMOD R_RISCV_TLS_DTPMOD32
< #define REL_DTPOFF R_RISCV_TLS_DTPREL32
< #define REL_TPOFF R_RISCV_TLS_TPREL32
---
> #define REL_DTPMOD R_RISCV_TLS_DTPMOD64
> #define REL_DTPOFF R_RISCV_TLS_DTPREL64
> #define REL_TPOFF R_RISCV_TLS_TPREL64
緊接著,我們在編譯的時候需要加上對應的參數:
CROSS_COMPILE=riscv32-linux- configure --target=riscv32 --enable-shared --enable-debug --prefix=/path/to/install
DESTDIR=/path/to/install make install
在編譯時,可以觀察到除了有link出shared library libc.so
外,還有一份 musl-gcc 的檔案被寫了出去。
此時我們把他打開來一探究境:
#!/bin/sh
exec "${REALGCC:-riscv32-linux-gcc}" "$@" -specs "/path/to/install/musl-gcc.specs"
簡單說,他是會去呼叫configure時,吃進去的 compiler name,並且使用特殊的spec file:
%rename cpp_options old_cpp_options
*cpp_options:
-nostdinc -isystem /path/to/install/include -isystem include%s %(old_cpp_options)
*cc1:
%(cc1_cpu) -nostdinc -isystem /path/to/install/include -isystem include%s
*link_libgcc:
-L/path/to/install/lib -L .%s
*libgcc:
libgcc.a%s %:if-exists(libgcc_eh.a%s)
*startfile:
%{!shared: /path/to/install/lib/Scrt1.o} /path/to/install/lib/crti.o crtbeginS.o%s
*endfile:
crtendS.o%s /path/to/lib/crtn.o
*link:
-dynamic-linker /lib/ld-musl-riscv32.so.1 -nostdlib %{shared:-shared} %{static:-static} %{rdynamic:-export-dynamic}
*esp_link:
*esp_options:
*esp_cpp_options:
簡單來說,這份檔案是會讓gcc執行時,會在對應的使用情境下,開啟不同的編譯選項,例如nostdinc
,所以不會去尋找toolchain stage2時設定的C runtime/library,CRT檔案在何方、以及最重要的 — — 在load time時會把so放好、做好symbol resolve的dynamic-linker 。
這時,我們可以非常快速的補正一下上面對應的路徑,到你安裝的位置上,從此之後就能非常輕鬆地使用 musl-gcc這個wrapper來編譯程式。
於是,頭號拿來編的對象就是,libc-test ~~~
按照readme複製一份config.mak出來,
cp config.mak.def config.mak
接著修改Makefile中的RUN_TEST
為qemu-riscv32
,並且開始編譯/執行:
CC=musl-gcc make all run
編譯、運行完libc-test號,我們發現有兩個syscall目前有狀況,直接壞在testsuite的大門上,他們便是:sigtimedwait
以及waitpid
。
第一件事情比較簡單,我們快速翻閱qemu的./linux-user/riscv/syscall32_nr.h
發現,在riscv32跟其他32平台上,該syscall已經被重新定義成sigtimedwait_time64
,快速修正一下:
diff --git a/arch/riscv32/bits/syscall.h.in b/arch/riscv32/bits/syscall.h.in
index 480adc58..67070141 100644
--- a/arch/riscv32/bits/syscall.h.in
+++ b/arch/riscv32/bits/syscall.h.in
@@ -133,7 +134,8 @@
#define __NR_rt_sigaction 134
#define __NR_rt_sigprocmask 135
#define __NR_rt_sigpending 136
-#define __NR_rt_sigtimedwait 137
+//#define __NR_rt_sigtimedwait 137
+#define __NR_rt_sigtimedwait_time64 421
#define __NR_rt_sigqueueinfo 138
#define __NR_rt_sigreturn 139
#define __NR_setpriority 140
diff --git a/arch/riscv32/syscall_arch.h b/arch/riscv32/syscall_arch.h
index ecf2b8a5..5f8760f7 100644
--- a/arch/riscv32/syscall_arch.h
+++ b/arch/riscv32/syscall_arch.h
@@ -2,6 +2,7 @@
#define SYSCALL_FADVISE_6_ARG
#define SYSCALL_IPC_BROKEN_MODE
#define SYS_clock_gettime SYS_clock_gettime64
+#define SYS_rt_sigtimedwait SYS_rt_sigtimedwait_time64
第二件事情就複雜了,簡單說Linux kernel 32bit平台在搬到64bit time_t後,wait4這個syscall直接被幹掉了。而新的waitid其實跟waitpid、wait4都差很遠。
萬幸的是我們可以找到glibc那邊有一組可以用、但是沒有被收進去的patch: https://sourceware.org/legacy-ml/libc-alpha/2019-09/msg00443.html
具體修正長這樣:
#include <sys/wait.h>
+#include <signal.h>
#include "syscall.h"
pid_t waitpid(pid_t pid, int *status, int options)
{
+ #if __riscv_xlen != 32
return syscall_cp(SYS_wait4, pid, status, options, 0);
+ #else // generic wait4
+ pid_t ret;
+ idtype_t idtype = P_PID;
+ siginfo_t infop;
+
+ if (pid < -1)
+ {
+ idtype = P_PGID;
+ pid *= -1;
+ }
+ else if (pid == -1)
+ {
+ idtype = P_ALL;
+ }
+ else if (pid == 0)
+ {
+ /* Linux Kernels 5.4+ support pid 0 with P_PGID to specify wait on
+ * the current PID's group. Earlier kernels will return -EINVAL.
+ */
+ idtype = P_PGID;
+ }
+
+ options |= WEXITED;
+
+ ret = syscall_cp (SYS_waitid, idtype, pid, &infop, options, 0);
+ //ret = SYSCALL_CANCEL (waitid, idtype, pid, &infop, options, NULL);
+
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ if (status)
+ {
+ *status = 0;
+ switch (infop.si_code)
+ {
+ case CLD_EXITED:
+ *status = infop.si_status << 8;
+ break;
+ case CLD_DUMPED:
+ *status = 0x80;
+ /* Fallthrough */
+ case CLD_KILLED:
+ *status |= infop.si_status;
+ break;
+ case CLD_TRAPPED:
+ case CLD_STOPPED:
+ *status = infop.si_status << 8 | 0x7f;
+ break;
+ case CLD_CONTINUED:
+ *status = 0xffff;
+ break;
+ }
+ }
+
+ return infop.si_pid;
+ #endif // rv32 waitid hack
}
在上了這兩組patch後,我們至少敲得進大門了QQ
$ qemu-riscv32 ./src/common/runtest.exe -w /usr/bin/qemu-riscv32 -t 1 ./src/functional/argv.exe
$ echo $?
0
然而,一放下去給他全跑:
RUN_WRAP=/usr/bin/qemu-riscv32 make run
馬上又是debug地獄了XD
先讓我富堅一下XDDDDD