今天到了 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_GLOBS
跟ARCH_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.S
跟longjmp.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有關。且待下回分解~