RISC-V의 핵심은 opensbi이다. 어느 RISC-V 문서를 봐도 opensbi를 확인할 수 있다.
부트로더에서 opensbi가 실행되고, 리눅스 커널이 실행될 때도 opensbi에 접근한다.

이번 포스트에서는 opensbi를 빌드하는 방법에 대해 기술한다.

opensbi는 오픈 소스로 관리되며 위치는 아래와 같다.

https://github.com/riscv-software-src/opensbi

아래 명령어로 소스를 내려 받자.

$ git clone https://github.com/riscv-software-src/opensbi

빌드하기 전에 미리 아래와 같은 유틸리티를 설치할 필요가 있다. 

$ sudo apt install gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu

opensbi 빌드 방법

opensbi를 빌드하는 핵심 명령어는 아래와 같다. 

make O=$OUTPUT CROSS_COMPILE=riscv64-linux-gnu- PLATFORM=generic 

계속 CROSS_COMPILE을 명령어로 하기 귀찮으니, 빌드 셸 스크립트(./build_opensbi.sh)를 생성해서 실행하자.

아래는 ./build_opensbi.sh의 구현부이다.

#!/bin/bash

echo "build opensbi"
TOP_PATH=$( cd "$(dirname "$0")" ; pwd )
OUTPUT="$TOP_PATH/out-riscv64"

BUILD_LOG="$TOP_PATH/riscv-build_log.txt"

build_start_time=`date +%s`

#echo "RPi build start" > $BUILD_LOG
#echo "Build start : $build_start_time" >> $BUILD_LOG

echo "open Build start : $build_start_time"

OUTPUT_PATH=$( cd "$(dirname "$0")" ; pwd )
OUTPUT="$OUTPUT_PATH/out-opensbi"

pushd opensbi > /dev/null

make O=$OUTPUT CROSS_COMPILE=riscv64-linux-gnu- PLATFORM=generic -j16  2>&1
popd > /dev/null

아래는 build_opensbi.sh 셸 스크립트를 실행할 때의 아웃풋이다.

$ ./build_opensbi.sh
build opensbi
open Build start : 1768870118
Loaded configuration '/home/austin/riscv_src/package_opensbi/deepack_open_sbi/opensbi/platform/generic/configs/defconfig'
Configuration saved to '/home/austin/riscv_src/package_opensbi/deepack_open_sbi/out-opensbi/platform/generic/kconfig/.config'
 CPP-DEP   platform/generic/firmware/fw_payload.elf.dep
 CPP-DEP   platform/generic/firmware/fw_jump.elf.dep
 CPP-DEP   platform/generic/firmware/fw_dynamic.elf.dep
[...]
 AR        lib/libsbi.a
 ELF       platform/generic/firmware/payloads/test.elf
 ELF       platform/generic/firmware/fw_dynamic.elf
 ELF       platform/generic/firmware/fw_jump.elf
 OBJCOPY   platform/generic/firmware/payloads/test.bin
 AS        platform/generic/firmware/fw_payload.o
 OBJCOPY   platform/generic/firmware/fw_dynamic.bin
 ELF       platform/generic/firmware/fw_payload.elf
 OBJCOPY   platform/generic/firmware/fw_jump.bin
 OBJCOPY   platform/generic/firmware/fw_payload.bin

빌드가 되면 다양한 오브젝트 파일이 생성되는데 핵심 파일은 fw_dynamic.bin이다.
fw_dynamic.bin 파일은 디버깅 정보가 없으니, fw_dynamic.elf 파일을 찾아서 어셈블리 명령어를 추출하자.

$ riscv64-linux-gnu-objdump -d fw_dynamic.elf  > assembly_opensbi.c

이제부터 어셈블리 명령어를 분석할 수 있다.

0000000000000000 <_fw_start>:
       0:       00050433                add     s0,a0,zero
       4:       000584b3                add     s1,a1,zero
       8:       00060933                add     s2,a2,zero
       c:       66c000ef                jal     ra,678 <fw_boot_hart>
[...]
00000000000003c8 <_trap_handler>:
     3c8:       34021273                csrrw   tp,mscratch,tp
     3cc:       06523023                sd      t0,96(tp) # 60 <_try_lottery+0x36>
     3d0:       300022f3                csrr    t0,mstatus
[...]
     46c:       00010533                add     a0,sp,zero
     470:       16e0c0ef                jal     ra,c5de <sbi_trap_handler>


이제부터 전처리 파일을 추출해보자.

전처리 파일(preprocessed file)은 매크로를 전부 파싱해서 보여주므로,
처음 코드를 분석할 때 유용하다.

opensbi에서 전처리 파일을 추출하려면 아래와 같은 패치를 생성하자.

diff --git a/Makefile b/Makefile
index 46541063..85e0f685 100644
--- a/Makefile
+++ b/Makefile
@@ -381,6 +381,7 @@ CFLAGS              =       -g -Wall -Werror -ffreestanding -nostdlib -fno-stack-protector -fno-st
 CFLAGS         +=      -fno-omit-frame-pointer -fno-optimize-sibling-calls
 CFLAGS         +=      -fno-asynchronous-unwind-tables -fno-unwind-tables
 CFLAGS         +=      -std=gnu11
+CFLAGS          +=      -save-temps=obj
 CFLAGS         +=      $(REPRODUCIBLE_FLAGS)
 # Optionally supported flags
 ifeq ($(CC_SUPPORT_VECTOR),y)

CFLAGS에 -save-temps=obj 옵션을 추가하는 코드이다.

전처리 파일과 C 코드에서 확인한 루틴을 비교하자.
sbi_list_del() 함수 in C 소스 파일:

include/sbi/sbi_list.h
static inline void sbi_list_del(struct sbi_dlist *entry)
{
        __sbi_list_del(entry->prev, entry->next);
        entry->next = (void *)SBI_LIST_POISON_NEXT;
        entry->prev = (void *)SBI_LIST_POISON_PREV;
}

sbi_list_del() 함수 in 전처리 소스 파일: SBI_LIST_POISON_NEXT 매크로가
0xFADEBABE으로 파싱되어서 출력된다.

static inline void sbi_list_del(struct sbi_dlist *entry)
{
 __sbi_list_del(entry->prev, entry->next);
 entry->next = (void *)0xFADEBABE;
 entry->prev = (void *)0xDEADBEEF;
}

RISC-V에서 QEMU를 설정하고 빌드하는 방법은 아래 링크를 참고하자.

https://lore.kernel.org/all/20251112-v5_user_cfi_series-v23-0-b55691eacf4f@rivosinc.com/

How to test this series
=======================

Toolchain
---------
$ git clone git@github.com:sifive/riscv-gnu-toolchain.git -b cfi-dev
$ riscv-gnu-toolchain/configure --prefix=<path-to-where-to-build> --with-arch=rv64gc_zicfilp_zicfiss --enable-linux --disable-gdb  --with-extra-multilib-test="rv64gc_zicfilp_zicfiss-lp64d:-static"
$ make -j$(nproc)

Qemu
----
Get the lastest qemu
$ cd qemu
$ mkdir build
$ cd build
$ ../configure --target-list=riscv64-softmmu
$ make -j$(nproc)

Opensbi
-------
$ git clone git@github.com:deepak0414/opensbi.git -b v6_cfi_spec_split_opensbi
$ make CROSS_COMPILE=<your riscv toolchain> -j$(nproc) PLATFORM=generic

Linux
-----
Running defconfig is fine. CFI is enabled by default if the toolchain
supports it.

$ make ARCH=riscv CROSS_COMPILE=<path-to-cfi-riscv-gnu-toolchain>/build/bin/riscv64-unknown-linux-gnu- -j$(nproc) defconfig
$ make ARCH=riscv CROSS_COMPILE=<path-to-cfi-riscv-gnu-toolchain>/build/bin/riscv64-unknown-linux-gnu- -j$(nproc)

Running
-------

Modify your qemu command to have:
-bios <path-to-cfi-opensbi>/build/platform/generic/firmware/fw_dynamic.bin
-cpu rv64,zicfilp=true,zicfiss=true,zimop=true,zcmop=true

References
==========
[1] - https://github.com/riscv/riscv-cfi
[2] - https://lore.kernel.org/all/20240814081126.956287-1-samuel.holland@sifive.com/
[3] - https://lwn.net/Articles/889475/
[4] - https://developer.arm.com/documentation/109576/0100/Branch-Target-Identification
[5] - https://www.intel.com/content/dam/develop/external/us/en/documents/catc17-introduction-intel-cet-844137.pdf
[6] - https://lwn.net/Articles/940403/

+ Recent posts