Below link is pretty good.

 

https://www.cs.cornell.edu/courses/cs3410/2019sp/riscv/interpreter/#

 

RISC-V Interpreter

Credit to Danny Qiu for the creation of the original MIPS interpreter.

www.cs.cornell.edu

 

 

'current' macro is mainly used to read 'task_struct' address of current process.

arch/riscv/include/asm/current.h
register struct task_struct *riscv_current_is_tp __asm__("tp");

static __always_inline struct task_struct *get_current(void)
{
return riscv_current_is_tp;
}

#define current get_current()

Body of current is 'get_current()' macro. We can deduce that "tp"(X4) holds start address of current.

Core routine of context switching in RISC-V Linux kernel is below.

ENTRY(__switch_to)
/* Save context into prev->thread */
li    a4,  TASK_THREAD_RA
add   a3, a0, a4
add   a4, a1, a4
REG_S ra,  TASK_THREAD_RA_RA(a3)
REG_S sp,  TASK_THREAD_SP_RA(a3)
REG_S s0,  TASK_THREAD_S0_RA(a3)
REG_S s1,  TASK_THREAD_S1_RA(a3)
REG_S s2,  TASK_THREAD_S2_RA(a3)
REG_S s3,  TASK_THREAD_S3_RA(a3)
REG_S s4,  TASK_THREAD_S4_RA(a3)
REG_S s5,  TASK_THREAD_S5_RA(a3)
REG_S s6,  TASK_THREAD_S6_RA(a3)
REG_S s7,  TASK_THREAD_S7_RA(a3)
REG_S s8,  TASK_THREAD_S8_RA(a3)
REG_S s9,  TASK_THREAD_S9_RA(a3)
REG_S s10, TASK_THREAD_S10_RA(a3)
REG_S s11, TASK_THREAD_S11_RA(a3)
/* Restore context from next->thread */
REG_L ra,  TASK_THREAD_RA_RA(a4)
REG_L sp,  TASK_THREAD_SP_RA(a4)
REG_L s0,  TASK_THREAD_S0_RA(a4)
REG_L s1,  TASK_THREAD_S1_RA(a4)
REG_L s2,  TASK_THREAD_S2_RA(a4)
REG_L s3,  TASK_THREAD_S3_RA(a4)
REG_L s4,  TASK_THREAD_S4_RA(a4)
REG_L s5,  TASK_THREAD_S5_RA(a4)
REG_L s6,  TASK_THREAD_S6_RA(a4)
REG_L s7,  TASK_THREAD_S7_RA(a4)
REG_L s8,  TASK_THREAD_S8_RA(a4)
REG_L s9,  TASK_THREAD_S9_RA(a4)
REG_L s10, TASK_THREAD_S10_RA(a4)
REG_L s11, TASK_THREAD_S11_RA(a4)
/* Swap the CPU entry around. */
lw a3, TASK_TI_CPU(a0)
lw a4, TASK_TI_CPU(a1)
sw a3, TASK_TI_CPU(a1)
sw a4, TASK_TI_CPU(a0)
/* The offset of thread_info in task_struct is zero. */
move tp, a1
ret
ENDPROC(__switch_to)
   

'move tp, a1' instruction corresponding to 'move X4, X11' is added to save the starting address of 'task_struct.

Body of 'alloc_fd' function.

fs/file.c
static int alloc_fd(unsigned start, unsigned end, unsigned flags)
{
struct files_struct *files = current->files;

The 'current' macro is used as a way to read starting address of 'task_struct'.

SP:FFFFFFFF8014E5B0|711D      alloc_fd: c.addi16sp -0x60         ; -96
SP:FFFFFFFF8014E5B2|E8A2                c.sdsp     x8,0x50(x2)   ; x8,80(x2)
SP:FFFFFFFF8014E5B4|F852                c.sdsp     x20,0x30(x2)   ; x20,48(x2)
SP:FFFFFFFF8014E5B6|F456                c.sdsp     x21,0x28(x2)   ; x21,40(x2)
SP:FFFFFFFF8014E5B8|F05A                c.sdsp     x22,0x20(x2)   ; x22,32(x2)
SP:FFFFFFFF8014E5BA|EC5E                c.sdsp     x23,0x18(x2)   ; x23,24(x2)
SP:FFFFFFFF8014E5BC|E862                c.sdsp     x24,0x10(x2)   ; x24,16(x2)
SP:FFFFFFFF8014E5BE|EC86                c.sdsp     x1,0x58(x2)   ; x1,88(x2)
SP:FFFFFFFF8014E5C0|E4A6                c.sdsp     x9,0x48(x2)   ; x9,72(x2)
SP:FFFFFFFF8014E5C2|E0CA                c.sdsp     x18,0x40(x2)   ; x18,64(x2)
SP:FFFFFFFF8014E5C4|FC4E                c.sdsp     x19,0x38(x2)   ; x19,56(x2)
SP:FFFFFFFF8014E5C6|E466                c.sdsp     x25,0x8(x2)   ; x25,8(x2)
SP:FFFFFFFF8014E5C8|1080                c.addi4spn x8,0x60       ; x8,96
SP:FFFFFFFF8014E5CA|67823903            ld         x18,0x678(x4)   ; x18,1656(x4)

After 'ld x18,0x678(x4)' instruction is executed, x18 is holding the &(current->files) where 0x678 is offset of 'struct task_struct.files.
This is true because x4 holds the entry address of 'task_struct'.

fs/file.c
int __close_range(unsigned fd, unsigned max_fd, unsigned int flags)
{
struct task_struct *me = current;
struct files_struct *cur_fds = me->files, *fds = NULL;
 
      SP:FFFFFFFF8014EC14|__close_range:    c.addi16sp -0x60         ; -96
      SP:FFFFFFFF8014EC16|                  c.sdsp     x8,0x50(x2)   ; x8,80(x2)
      SP:FFFFFFFF8014EC18|                  c.sdsp     x1,0x58(x2)   ; x1,88(x2)
      SP:FFFFFFFF8014EC1A|                  c.sdsp     x21,0x28(x2)   ; x21,40(x2)
      SP:FFFFFFFF8014EC1C|                  c.addi4spn x8,0x60       ; x8,96
      SP:FFFFFFFF8014EC1E|                  ld         x15,0x460(x4)   ; x15,1120(x4)
      SP:FFFFFFFF8014EC22|                  sd         x15,-0x58(x8)   ; x15,-88(x8)
      SP:FFFFFFFF8014EC26|                  c.li       x15,0x0       ; x15,0

From:
https://msyksphinz-self.github.io/riscv-isadoc/html/index.html

...
MP:FFFFFFFF80002FEA|                    csrrc      x9,sstatus,x5  // atomic read and clear bits in CSR.
MP:FFFFFFFF80002FEE|                    csrr       x18,sepc
MP:FFFFFFFF80002FF2|                    csrr       x19,stval
MP:FFFFFFFF80002FF6|                    csrr       x20,scause
MP:FFFFFFFF80002FFA|                    csrr       x21,sscratch

void handle_exception() 
{
   word scause;
   word offset_vect;
   void *exception_func;
   bool interrupt_status;

   scause = sys_csr_reg_read(scause); // csrr       x20,scause

   interrupt_status = scause >> (SXLEN -1); // 

   if ( interrupt_status == true) {
       la_reg = ret_from_exception;  // la ra, ret_from_exception
       handle_arch_irq(); 
   }
   else {
      la_reg = ret_from_exception;  // la ra, ret_from_exception
      if (scause & 0b1000) {
         handle_syscall();
      }
      else {
         offset_vect = get_exception_reason(scause);
         exception_func = excp_vect_table + offset_vect;
         (void*)exception_func; // c.jr x5
      }
   }
}

From: https://doc-en.rvspace.org/VisionFive2/SWTRM/VisionFive2_SW_TRM/swtrm_compiling_linux_kernel%20-%20vf2.html

이번 포스트에서는 RISC-V 툴체인을 설치해 리눅스 커널을 빌드하는 방법을 소개합니다.

먼저 RISC-V 툴체인을 설치하는 명령어를 입력합시다.

RISC-V 툴체인 소스를 내려받기  

다음 명령어를 입력해 RISC-V 툴체인 소스를 내려받습니다.

$ git clone --recursive https://github.com/riscv/riscv-gnu-toolchain
 
아래는 리눅스 터미널에서 위 명령어를 입력한 후의 출력 결과입니다. 소스를 내려받는데 1시간 정도 걸리네요.

austindh.kim:~/src/risc-v_toolchain$ git clone --recursive https://github.com/riscv/riscv-gnu-toolchain
Cloning into 'riscv-gnu-toolchain'...
remote: Enumerating objects: 21, done.
remote: Counting objects: 100% (21/21), done.
remote: Compressing objects: 100% (14/14), done.
remote: Total 7795 (delta 7), reused 16 (delta 4), pack-reused 7774
Receiving objects: 100% (7795/7795), 4.75 MiB | 970.00 KiB/s, done.
Resolving deltas: 100% (3940/3940), done.
Submodule 'qemu' (https://git.qemu.org/git/qemu.git) registered for path 'qemu'
Submodule 'riscv-binutils' (https://github.com/riscv/riscv-binutils-gdb.git) registered for path 'riscv-binutils'
Submodule 'riscv-dejagnu' (https://github.com/riscv/riscv-dejagnu.git) registered for path 'riscv-dejagnu'
Submodule 'riscv-gcc' (https://github.com/riscv/riscv-gcc.git) registered for path 'riscv-gcc'
Submodule 'riscv-gdb' (https://github.com/riscv/riscv-binutils-gdb.git) registered for path 'riscv-gdb'
...

시간이 오래 걸리니 밥 먹기 전에 명령어를 입력해 놔야 겠습니다.

RISC-V 툴체인 소스 빌드하고 설치하기 

소스를 전부 내려받은 다음에 아래와 같이 디렉터리를 생성합니다.

$ mkdir -p opt/riscv
$ mkdir build
$ cd build
$ ../configure --prefix=/home001/austindh.kim/src/risc-v_toolchain/riscv-gnu-toolchain/opt/riscv --enable-multilib

혹은

$ ../configure --prefix=/home001/austindh.kim/src/risc-v_toolchain/riscv-gnu-toolchain/opt/riscv  



아래는 리눅스 터미널에서 위 명령어를 실행한 후 출력 결과입니다. 빌드되는데 2시간 정도가 소요됩니다.

austindh.kim:~/src/risc-v_toolchain$ mkdir -p opt/riscv
austindh.kim:~/src/risc-v_toolchain$ mkdir build
austindh.kim:~/src/risc-v_toolchain$ cd build
austindh.kim:~/src/risc-v_toolchain/build$ ../configure --prefix=/home001/austindh.kim/src/risc-v_toolchain/riscv-gnu-toolchain/opt/riscv 
...

 

이번에는 다음 명령어를 실행해서 빌드를 수행합니다.

 

austindh.kim:~/src/risc-v_toolchain/build$ make linux


빌드가 끝나고 "~src/risc-v_toolchain/riscv-gnu-toolchain/opt/riscv/bin" 디렉터리로 이동하니, 다음과 같은 파일이 생성됐음을 확인할 수 있습니다.

austindh.kim:~/src/risc-v_toolchain/riscv-gnu-toolchain/opt/riscv/bin$ ls
riscv64-unknown-linux-gnu-addr2line  riscv64-unknown-linux-gnu-gcc         riscv64-unknown-linux-gnu-gdb            riscv64-unknown-linux-gnu-objcopy  ... 
riscv64-unknown-linux-gnu-elfedit    riscv64-unknown-linux-gnu-gcov-dump   riscv64-unknown-linux-gnu-lto-dump       riscv64-unknown-linux-gnu-strings
riscv64-unknown-linux-gnu-g++        riscv64-unknown-linux-gnu-gcov-tool   riscv64-unknown-linux-gnu-nm             riscv64-unknown-linux-gnu-strip

이제 RISC-V 툴체인 소스 빌드를 마무리했습니다.

 RISC-V 툴체인으로 리눅스 커널 빌드하기 

다음 명령어를 입력해 리눅스 커널 소스 코드를 내려 받습니다.

$ git clone https://kernel.googlesource.com/pub/scm/linux/kernel/git/next/linux-next

소스를 내려 받은 다음에 확인한 디렉터리 정보는 다음과 같습니다.

austindh.kim:~/src/dev_kernel/59_linux_kernel$ ls
linux-next

'riscv_kernel_build.sh' 이름으로 셸 스크립트 파일을 생성한 다음에, 아래 빌드 스크립트를 입력합니다.

 

#!/bin/bash

export PATH=$PATH:/home001/austindh.kim/src/risc-v_toolchain/riscv-gnu-toolchain/opt/riscv/bin

echo "configure build output path"
TOP_PATH=$( cd "$(dirname "$0")" ; pwd )
OUTPUT="$TOP_PATH/out"

BUILD_LOG="$TOP_PATH/rpi_build_log.txt"

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

pushd linux-next > /dev/null

# configure defconfig
make ARCH=riscv O=$OUTPUT CROSS_COMPILE=riscv64-unknown-linux-gnu- defconfig -j16  2>&1

# build kernel with riscv tool-chain
make ARCH=riscv O=$OUTPUT CROSS_COMPILE=riscv64-unknown-linux-gnu- modules dtbs -j16  2>&1 | tee $BUILD_LOG

popd > /dev/null


위 빌드 스크립트에서 가장 중요한 명령어는 아래와 같습니다.


# configure defconfig
make ARCH=riscv O=$OUTPUT CROSS_COMPILE=riscv64-unknown-linux-gnu- defconfig -j16  2>&1

# build kernel with riscv tool-chain
make ARCH=riscv O=$OUTPUT CROSS_COMPILE=riscv64-unknown-linux-gnu- modules dtbs -j16  2>&1 | tee $BUILD_LOG



'arch/riscv/configs/defconfig' 파일을 다음과 같이 수정해 vmlinux에 디버깅 심벌이 추가되도록 합시다.

diff --git a/arch/riscv/configs/defconfig b/arch/riscv/configs/defconfig
index d58c93e..6ea8e83 100644
--- a/arch/riscv/configs/defconfig
+++ b/arch/riscv/configs/defconfig
@@ -20,6 +20,7 @@ CONFIG_SMP=y
 CONFIG_JUMP_LABEL=y
 CONFIG_MODULES=y
 CONFIG_MODULE_UNLOAD=y
+CONFIG_DEBUG_INFO=y
 CONFIG_NET=y
 CONFIG_PACKET=y
 CONFIG_UNIX=y


'riscv_kernel_build.sh' 빌드 셸 스크립트를 실행해, RISCV 툴 체인으로 커널을 빌드합니다.

아래는 'riscv_kernel_build.sh' 빌드 셸 스크립트를 터미널에서 실행한 출력 화면입니다.
austindh.kim:~/src/dev_kernel/59_linux_kernel$ ./riscv_kernel_build.sh
configure build output path
make[1]: Entering directory '/home001/austindh.kim/src/dev_kernel/59_linux_kernel/out'
  GEN     Makefile
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/kconfig/conf.o
...
  KSYMS   .tmp_vmlinux.kallsyms2.S
  AS      .tmp_vmlinux.kallsyms2.S
  LD      vmlinux
  SYSMAP  System.map
  MODPOST Module.symvers
  CC [M]  fs/nfs/flexfilelayout/nfs_layout_flexfiles.mod.o
  LD [M]  fs/nfs/flexfilelayout/nfs_layout_flexfiles.ko


'out' 폴더를 가니 제대로 커널이 빌드됐음을 확인할 수 있습니다.

austindh.kim:~/src/dev_kernel/59_linux_kernel/out$ ls
arch   crypto   include  kernel    mm                       modules.order   scripts   source      virt       vmlinux.symvers
block  drivers  init     lib       modules.builtin          Module.symvers  security  System.map  vmlinux
certs  fs       ipc      Makefile  modules.builtin.modinfo  net             sound     usr         vmlinux.o


RISC-V 바이너리 유틸리티 사용해보기 

이번엔 다음 명령어를 입력해 vmlinux의 셉션 정보를 확인해보겠습니다.

  
$ ./riscv64-unknown-linux-gnu-objdump -x vmlinux | more


아래는 './riscv64-unknown-linux-gnu-objdump -x vmlinux | more' 빌드 셸 스크립트를 터미널에서 실행한 출력 화면입니다.

austindh.kim:~/src/dev_kernel/59_linux_kernel/out$ ./riscv64-unknown-linux-gnu-objdump -x vmlinux | more

vmlinux:     file format elf64-littleriscv
vmlinux
architecture: riscv:rv64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0xffffffe000000000

Program Header:
    LOAD off    0x0000000000001000 vaddr 0xffffffe000000000 paddr 0x0000000000000000 align 2**12
         filesz 0x0000000000025af4 memsz 0x0000000000025af4 flags r-x
    LOAD off    0x0000000000027000 vaddr 0xffffffe000026000 paddr 0x0000000000026000 align 2**12
         filesz 0x0000000000019de8 memsz 0x0000000000019de8 flags rwx
    LOAD off    0x0000000000041000 vaddr 0xffffffe000200000 paddr 0x0000000000200000 align 2**12
         filesz 0x00000000006ce0a2 memsz 0x00000000006ce0a2 flags r-x
    LOAD off    0x0000000000710000 vaddr 0xffffffe000a00000 paddr 0x0000000000a00000 align 2**12
         filesz 0x000000000022a9fc memsz 0x000000000022a9fc flags rw-
    LOAD off    0x000000000093b000 vaddr 0xffffffe000e00000 paddr 0x0000000000e00000 align 2**12
         filesz 0x0000000000002400 memsz 0x0000000000002400 flags r--
    LOAD off    0x000000000093e000 vaddr 0xffffffe001000000 paddr 0x0000000001000000 align 2**12
         filesz 0x00000000000e19b4 memsz 0x0000000000131920 flags rw-
    NOTE off    0x000000000093a9c0 vaddr 0xffffffe000c2a9c0 paddr 0x0000000000c2a9c0 align 2**2
         filesz 0x000000000000003c memsz 0x000000000000003c flags r--
   STACK off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**4
         filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-

  
출력 결과를 보니 아키텍처의 정보는 'riscv:rv64'이고 스타트업 코드의 위치는 0xffffffe000000000 주소임을 알 수 있습니다.

+ Recent posts