iT邦幫忙

2021 iThome 鐵人賽

DAY 3
0
Software Development

Port Alpine Linux to open source RISC-V platform系列 第 3

musl libc 簡介與其 porting(二)Say hello to my little friend!

今天到了 day3,上次有預告了這次會開始進行RISC-V 32平台的musl libc porting。
好的,那麼先來概算一下我們若要以最小限度進行porting,有哪些方法論?

在已經有RISC-V 64的前提下,個人覺得最快的方法是:研究一下musl的build system如何選取要編譯的source set,繼而增加新的平台參數、嘗試duplicate riscv64作基底,先處理靜態連解版本(musl最一開始主打的特技也就是極方便的static link),然後qemu-linux-user進行libc-test的測試修bug。爾後再對動態連結時的版本進行微調。

於是事不宜遲,我們來看一下musl如何得到平台資料以及選取對應的source set:
(1) 我們可以發現手工撰寫的configure script中,會透過case switch將build tuple中的target換成他自己定義的ARCH,並且在最後寫出去到config.mak當中,而musl純手工的Makefile會 include 兩份檔案,一份是生出來的config.mak、另一份是基於config.mak 當中的 ARCH arch.mak

(2) 在Makefile中,我們看到了以下的敘述:

MALLOC_DIR = mallocng
SRC_DIRS = $(addprefix $(srcdir)/,src/* src/malloc/$(MALLOC_DIR) crt ldso $(COMPAT_SRC_DIRS))
BASE_GLOBS = $(addsuffix /*.c,$(SRC_DIRS))
ARCH_GLOBS = $(addsuffix /$(ARCH)/*.[csS],$(SRC_DIRS))
BASE_SRCS = $(sort $(wildcard $(BASE_GLOBS)))
ARCH_SRCS = $(sort $(wildcard $(ARCH_GLOBS)))
BASE_OBJS = $(patsubst $(srcdir)/%,%.o,$(basename $(BASE_SRCS)))
ARCH_OBJS = $(patsubst $(srcdir)/%,%.o,$(basename $(ARCH_SRCS)))
REPLACED_OBJS = $(sort $(subst /$(ARCH)/,/,$(ARCH_OBJS)))
ALL_OBJS = $(addprefix obj/, $(filter-out $(REPLACED_OBJS), $(sort $(BASE_OBJS) $(ARCH_OBJS))))

首先先定義出有哪些top-level資料夾要編,分別是src底下所有的第一層、以及musl目前有兩套的malloc實做、crt、以及runtime dynamic loader,還有出於abi相容需要的程式碼(spoiler warning,here's a catch)。

macro的部份簡單翻譯、翻譯,這是想要拼出一組給musl編譯時候的兩組參數:
${CROSS_COMPILE}gcc -c ${1}.c -o ${2}.o 代表著要將某個C/組語檔案,要編成什麼objec檔。而我們最在意的是ARCH_GLOBSARCH_SRC這部份,意思就是先拼出所有architecure C/asm檔案與對應的object檔名關係。

而先前在configure中看見的SUBARCH幾乎在Makefile中沒有什麼被使用到的場所。

看到這裡我們可以先有個初步的概念,雖然初步略看可以使用SUBARCH的機制來進行porting,但是我們會需要很大量的精力深入去看詳細的編譯流程。
作為30天要跑完整個前中後端的工作,我們最簡便、合理的作法是:複製多數 riscv64 一份給 riscv32,並且修改configure

事不宜遲,我們對configure做出以下修改:

diff --git a/configure b/configure
index a5231a0e..b7d06689 100755
--- a/configure
+++ b/configure
@@ -336,6 +336,7 @@ or1k*) ARCH=or1k ;;
 powerpc64*|ppc64*) ARCH=powerpc64 ;;
 powerpc*|ppc*) ARCH=powerpc ;;
 riscv64*) ARCH=riscv64 ;;
+riscv32*) ARCH=riscv32 ;;
 sh[1-9bel-]*|sh|superh*) ARCH=sh ;;
 s390x*) ARCH=s390x ;;
 unknown) fail "$0: unable to detect target arch; try $0 --target=..." ;;
@@ -699,6 +700,11 @@ trycppif __riscv_float_abi_soft "$t" && SUBARCH=${SUBARCH}-sf
 trycppif __riscv_float_abi_single "$t" && SUBARCH=${SUBARCH}-sp
 fi

+if test "$ARCH" = "riscv32" ; then
+trycppif __riscv_float_abi_soft "$t" && SUBARCH=${SUBARCH}-sf
+trycppif __riscv_float_abi_single "$t" && SUBARCH=${SUBARCH}-sp
+fi
+
 if test "$ARCH" = "sh" ; then
 tryflag CFLAGS_AUTO -Wa,--isa=any
 trycppif __BIG_ENDIAN__ "$t" && SUBARCH=${SUBARCH}eb

duplicate出以下的檔案/資料夾:

./arch/riscv32
./src/fenv/riscv32
./src/ldso/riscv32
./src/math/riscv32
./src/setjmp/riscv32
./src/signal/riscv32
./src/thread/riscv32

接下來,我們可以使用riscv-gnu-toolchain中的riscv32 toolchain先行編譯看看:

mkdir install_dir; CROSS_COMPILE=riscv32-linux- ./configure --target=riscv32 --disable-shared --prefix=$PWD/install_dir
make

這時我們馬上就遇到了在setjmp/riscv32/setjmp中的setjmp.Slongjmp.S,於備份及還原register值時,因為riscv32的暫存器寬度僅為32bit,所以riscv64的ld/sd指令並不支援。將其邏輯更改成lw/sw便可以正常執行。相似的還有對於浮點數暫存器作存儲的fld/fsd,修改成flw/fsw即可。

類似的問題,我們可以反覆透過make找出,數次迭代後我們就有了非常初期能編譯成功的版本,與其相應的patchset:

diff -dr ./arch/riscv64/atomic_arch.h ./arch/riscv32/atomic_arch.h
29c29
< 		"\n1:	lr.d.aqrl %0, (%2)\n"
---
> 		"\n1:	lr.w.aqrl %0, (%2)\n"
31c31
< 		"	sc.d.aqrl %1, %4, (%2)\n"
---
> 		"	sc.w.aqrl %1, %4, (%2)\n"
diff -dr ./src/setjmp/riscv64/longjmp.S ./src/setjmp/riscv32/longjmp.S
10,23c10,23
< 	ld s0,    0(a0)
< 	ld s1,    8(a0)
< 	ld s2,    16(a0)
< 	ld s3,    24(a0)
< 	ld s4,    32(a0)
< 	ld s5,    40(a0)
< 	ld s6,    48(a0)
< 	ld s7,    56(a0)
< 	ld s8,    64(a0)
< 	ld s9,    72(a0)
< 	ld s10,   80(a0)
< 	ld s11,   88(a0)
< 	ld sp,    96(a0)
< 	ld ra,    104(a0)
---
> 	lw s0,    0(a0)
> 	lw s1,    8(a0)
> 	lw s2,    16(a0)
> 	lw s3,    24(a0)
> 	lw s4,    32(a0)
> 	lw s5,    40(a0)
> 	lw s6,    48(a0)
> 	lw s7,    56(a0)
> 	lw s8,    64(a0)
> 	lw s9,    72(a0)
> 	lw s10,   80(a0)
> 	lw s11,   88(a0)
> 	lw sp,    96(a0)
> 	lw ra,    104(a0)
26,37c26,37
< 	fld fs0,  112(a0)
< 	fld fs1,  120(a0)
< 	fld fs2,  128(a0)
< 	fld fs3,  136(a0)
< 	fld fs4,  144(a0)
< 	fld fs5,  152(a0)
< 	fld fs6,  160(a0)
< 	fld fs7,  168(a0)
< 	fld fs8,  176(a0)
< 	fld fs9,  184(a0)
< 	fld fs10, 192(a0)
< 	fld fs11, 200(a0)
---
> 	flw fs0,  112(a0)
> 	flw fs1,  120(a0)
> 	flw fs2,  128(a0)
> 	flw fs3,  136(a0)
> 	flw fs4,  144(a0)
> 	flw fs5,  152(a0)
> 	flw fs6,  160(a0)
> 	flw fs7,  168(a0)
> 	flw fs8,  176(a0)
> 	flw fs9,  184(a0)
> 	flw fs10, 192(a0)
> 	flw fs11, 200(a0)
diff -dr ./src/setjmp/riscv64/setjmp.S ./src/setjmp/riscv32/setjmp.S
10,23c10,23
< 	sd s0,    0(a0)
< 	sd s1,    8(a0)
< 	sd s2,    16(a0)
< 	sd s3,    24(a0)
< 	sd s4,    32(a0)
< 	sd s5,    40(a0)
< 	sd s6,    48(a0)
< 	sd s7,    56(a0)
< 	sd s8,    64(a0)
< 	sd s9,    72(a0)
< 	sd s10,   80(a0)
< 	sd s11,   88(a0)
< 	sd sp,    96(a0)
< 	sd ra,    104(a0)
---
> 	sw s0,    0(a0)
> 	sw s1,    8(a0)
> 	sw s2,    16(a0)
> 	sw s3,    24(a0)
> 	sw s4,    32(a0)
> 	sw s5,    40(a0)
> 	sw s6,    48(a0)
> 	sw s7,    56(a0)
> 	sw s8,    64(a0)
> 	sw s9,    72(a0)
> 	sw s10,   80(a0)
> 	sw s11,   88(a0)
> 	sw sp,    96(a0)
> 	sw ra,    104(a0)
26,37c26,37
< 	fsd fs0,  112(a0)
< 	fsd fs1,  120(a0)
< 	fsd fs2,  128(a0)
< 	fsd fs3,  136(a0)
< 	fsd fs4,  144(a0)
< 	fsd fs5,  152(a0)
< 	fsd fs6,  160(a0)
< 	fsd fs7,  168(a0)
< 	fsd fs8,  176(a0)
< 	fsd fs9,  184(a0)
< 	fsd fs10, 192(a0)
< 	fsd fs11, 200(a0)
---
> 	fsw fs0,  112(a0)
> 	fsw fs1,  120(a0)
> 	fsw fs2,  128(a0)
> 	fsw fs3,  136(a0)
> 	fsw fs4,  144(a0)
> 	fsw fs5,  152(a0)
> 	fsw fs6,  160(a0)
> 	fsw fs7,  168(a0)
> 	fsw fs8,  176(a0)
> 	fsw fs9,  184(a0)
> 	fsw fs10, 192(a0)
> 	fsw fs11, 200(a0)
diff -dr ./src/signal/riscv64/sigsetjmp.s ./src/signal/riscv32/sigsetjmp.s
11,12c11,12
< 	sd ra, 208(a0)
< 	sd s0, 224(a0)
---
> 	sw ra, 208(a0)
> 	sw s0, 224(a0)
19,20c19,20
< 	ld s0, 224(a0)
< 	ld ra, 208(a0)
---
> 	lw s0, 224(a0)
> 	lw ra, 208(a0)
diff -dr ./src/thread/riscv64/clone.s ./src/thread/riscv32/clone.s
12,13c12,13
< 	sd a0, 0(a1)
< 	sd a3, 8(a1)
---
> 	sw a0, 0(a1)
> 	sw a3, 8(a1)
28,29c28,29
< 1:      ld a1, 0(sp)
< 	ld a0, 8(sp)
---
> 1:      lw a1, 0(sp)
> 	lw a0, 8(sp)
diff -dr ./src/thread/riscv64/syscall_cp.s ./src/thread/riscv32/syscall_cp.s
23c23
< 	ld a6, 0(sp)
---
> 	lw a6, 0(sp)

到這裡,我們可以運行make install將獲得的libc.a,安裝出去,然後火速寫個hello world、配qemu-riscv32來驗驗看:

$ cat ./hello.c
#include <stdio.h>

int main(int ac, char* av) {
    printf("Say hello to my little friend.\n");
    return 0;
    }
$ riscv32-unknown-elf-gcc -nostdlib -nostdinc -L./install_dir/lib -I./install_dir/include ./test.c ./lib/Scrt1.o -lc -o ./test
$ qemu-riscv32 ./test
Say hello to my little friend.

Easy peasy, huh ?

但是接下來問題就來了:

$ cat ./test_time.c 
#include <stdio.h>
#include <time.h>

int main()
{
  time_t t = time(NULL);
  struct tm tm = *localtime(&t);
  printf("now: %d-%02d-%02d %02d:%02d:%02d\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
}
$ riscv32-unknown-elf-gcc -nostdlib -nostdinc -static -L./lib -I./include ./test_time.c ./lib/Scrt1.o -static-libgcc -lc -lgcc -o ./test_time
$ qemu-riscv32 ./test_time
Segmentation Fault.

嘿嘿,這是為什麼咧?其實就跟上面的ARCH COMPAT有關。且待下回分解~


上一篇
musl libc 簡介與其 porting(一)
下一篇
musl libc 簡介與其 porting(三)No time to die.
系列文
Port Alpine Linux to open source RISC-V platform30

尚未有邦友留言

立即登入留言