musl libc 简介与其 porting(五) Knocking on Heaven's Door

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_TESTqemu-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


<<:  [Day12] Android - Kotlin笔记:JetPack - Fragments在Navigation中的参数传递(Safe Args)

>>:  java 类别方法

全端入门Day22_後端程序撰写之Node.js

介绍完前端就是要接着後端介绍阿! 今天挑Node.js Node.js入门 首先先进入到点选左边的长...

Day11 Sideproject(作品集) from 0 to 1 - docker化前端篇

docker的使用也是一直都想学了 我们是开始满久才开始套用进来的 因为刚开始都觉得这是一个很难的东...

Java 开发 WEB 的好平台 -- Grails -- (3) 建立一个 SPA 的 Grails 专案

或许有人会问「那我要开发 SPA 网站时,要如何跟 Grails 搭配呢?」。这个问题的答案很简单,...

[区块链&DAPP介绍 Day30] 最後的总结

很快的地狱般的铁人赛终於要结束了,今天就来聊聊这30天的学习心得。 其实一开始挑选这个题目时,也是无...

来举例一下 Neo4j 的实务应用

前情提要 最後一篇正篇,稍稍回顾了一下之前的每一篇 发现对於现实上的使用案例,好像没有太多的描述 所...