RISC-V Spikeシミュレータでは printf()
や std::cout
を使ってもコンソールで出力することができる。
#include <stdio.h>
int main ()
{
printf ("Hello World, C\n");
return 0;
}
#include <iostream>
int main ()
{
std::cout << "Hello World, C++\n";
return 0;
}
all: test_output_cpp test_output_c
run: run_cpp run_c
test_output_cpp: test_output.cpp Makefile
riscv64-unknown-elf-g++ -O3 $< -o $@
test_output_c: test_output.c Makefile
riscv64-unknown-elf-gcc -O3 $< -o $@
run_cpp: test_output_cpp Makefile
spike -l pk $< > run_output_cpp.log
run_c: test_output_c Makefile
spike -l pk $< > run_output_c.log
$ make run
Hello World, C
Hello World, C++
このprintf()
が正しく実行されるためには、riscv-pk (Proxy Kernel) という仕組みを利用していて、Host とのインタフェースを取り持つモジュール(HTIF)が接続されている。
$ spike pk run_output_cpp
$ spike pk run_output_c
命令のトレースログを取っていき、どのような仕組みで printf()
が実現されているのかを探していく。
だいたい以下のようになっていき、途中からecall
命令で特権モードに移動するようだ。
main() (00000000000100b0)
- puts () (00000000000103ac)
- _puts_r () (0000000000010332)
- strlen ()
- __sfvwrite_r () (00000000000106c2)
...
core 0: 0x0000000000011e22 (0x00004781) li a5, 0
0 0x0000000000011e22 (0x4781) x15 0x0000000000000000
core 0: 0x0000000000011e24 (0x05000893) li a7, 80
0 0x0000000000011e24 (0x05000893) x17 0x0000000000000050
core 0: 0x0000000000011e28 (0x00000073) ecall <-- printf() が呼ばれる際のEcall
core 0: exception trap_user_ecall, epc 0x0000000000011e28
core 0: 0x0000000080001854 (0x14011173) csrrw sp, sscratch, sp
1 0x0000000080001854 (0x14011173) x 2 0x0000000080025000
core 0: 0x0000000080001858 (0x00011463) bnez sp, pc + 8
1 0x0000000080001858 (0x00011463)
core 0: 0x0000000080001860 (0x00007129) addi sp, sp, -320
1 0x0000000080001860 (0x7129) x 2 0x0000000080024ec0
core 0: 0x0000000080001862 (0x0000e406) sd ra, 8(sp)
1 0x0000000080001862 (0xe406)
core 0: 0x0000000080001864 (0x0000ec0e) sd gp, 24(sp)
1 0x0000000080001864 (0xec0e)
core 0: 0x0000000080001866 (0x0000f012) sd tp, 32(sp)
1 0x0000000080001866 (0xf012)
core 0: 0x0000000080001868 (0x0000f416) sd t0, 40(sp)
1 0x0000000080001868 (0xf416)
core 0: 0x000000008000186a (0x0000f81a) sd t1, 48(sp)
1 0x000000008000186a (0xf81a)
core 0: 0x000000008000186c (0x0000fc1e) sd t2, 56(sp)
1 0x000000008000186c (0xfc1e)
core 0: 0x000000008000186e (0x0000e0a2) sd s0, 64(sp)
1 0x000000008000186e (0xe0a2)
...
2018/06/11追記。libpkではなく ${RISCV}/riscv64-unknown-elf/bin/pk
だった。このダンプ結果は無意味。
ここから先はriscv-pkに渡っていく。${RISCV}/riscv64-unknown-elf/lib/riscv-pk/libpk.a
を見てみる。riscv64-unknown-elf-objdump
でダンプしていく。
セクション .text の逆アセンブル:
0000000000000000 <trap_entry-0x2>:
0: 0001 nop
0000000000000002 <trap_entry>:
2: 14011173 csrrw sp,sscratch,sp <-- ここにジャンプされる?
6: 00011463 bnez sp,e <.L1^B1>
a: 14002173 csrr sp,sscratch
000000000000000e <.L1^B1>:
e: 7129 addi sp,sp,-320
10: e406 sd ra,8(sp)
12: ec0e sd gp,24(sp)
14: f012 sd tp,32(sp)
16: f416 sd t0,40(sp)
18: f81a sd t1,48(sp)
1a: fc1e sd t2,56(sp)
1c: e0a2 sd s0,64(sp)
1e: e4a6 sd s1,72(sp)
このとき、 riscv-pk/pk/entry.S
を追いかけていく。handle_trap()
に渡っていく。
trap_entry :entry.S
- handle_trap : riscv-pk/pk/handlers.c
handlers.c
を参照した。handle_syscall()
が呼ばれているのかな。
void handle_trap(trapframe_t* tf)
{
if ((intptr_t)tf->cause < 0)
return handle_interrupt(tf);
typedef void (*trap_handler)(trapframe_t*);
const static trap_handler trap_handlers[] = {
[CAUSE_MISALIGNED_FETCH] = handle_misaligned_fetch,
[CAUSE_FETCH_PAGE_FAULT] = handle_fault_fetch,
[CAUSE_ILLEGAL_INSTRUCTION] = handle_illegal_instruction,
[CAUSE_USER_ECALL] = handle_syscall,
[CAUSE_BREAKPOINT] = handle_breakpoint,
[CAUSE_MISALIGNED_STORE] = handle_misaligned_store,
[CAUSE_LOAD_PAGE_FAULT] = handle_fault_load,
[CAUSE_STORE_PAGE_FAULT] = handle_fault_store,
};
kassert(tf->cause < ARRAY_SIZE(trap_handlers) && trap_handlers[tf->cause]);
trap_handlers[tf->cause](tf);
}
static void handle_syscall(trapframe_t* tf)
{
tf->gpr[10] = do_syscall(tf->gpr[10], tf->gpr[11], tf->gpr[12], tf->gpr[13],
tf->gpr[14], tf->gpr[15], tf->gpr[17]);
tf->epc += 4;
}
次に、do_syscall()
が呼ばれる。ここからどこへ進むんだ?
long do_syscall(long a0, long a1, long a2, long a3, long a4, long a5, unsigned long n)
{
const static void* syscall_table[] = {
[SYS_exit] = sys_exit,
[SYS_exit_group] = sys_exit,
[SYS_read] = sys_read,
[SYS_pread] = sys_pread,
[SYS_write] = sys_write,
[SYS_openat] = sys_openat,
[SYS_close] = sys_close,
[SYS_fstat] = sys_fstat,
[SYS_lseek] = sys_lseek,
[SYS_fstatat] = sys_fstatat,
[SYS_linkat] = sys_linkat,
[SYS_unlinkat] = sys_unlinkat,
[SYS_mkdirat] = sys_mkdirat,
[SYS_renameat] = sys_renameat,
[SYS_getcwd] = sys_getcwd,
[SYS_brk] = sys_brk,
[SYS_uname] = sys_uname,
[SYS_getpid] = sys_getpid,
[SYS_getuid] = sys_getuid,
[SYS_geteuid] = sys_getuid,
[SYS_getgid] = sys_getuid,
[SYS_getegid] = sys_getuid,
[SYS_mmap] = sys_mmap,
[SYS_munmap] = sys_munmap,
[SYS_mremap] = sys_mremap,
[SYS_mprotect] = sys_mprotect,
[SYS_prlimit64] = sys_stub_nosys,
[SYS_rt_sigaction] = sys_rt_sigaction,
[SYS_gettimeofday] = sys_gettimeofday,
[SYS_times] = sys_times,
[SYS_writev] = sys_writev,
[SYS_faccessat] = sys_faccessat,
[SYS_fcntl] = sys_fcntl,
[SYS_ftruncate] = sys_ftruncate,
[SYS_getdents] = sys_getdents,
[SYS_dup] = sys_dup,
[SYS_readlinkat] = sys_stub_nosys,
[SYS_rt_sigprocmask] = sys_stub_success,
[SYS_ioctl] = sys_stub_nosys,
[SYS_clock_gettime] = sys_stub_nosys,
[SYS_getrusage] = sys_stub_nosys,
[SYS_getrlimit] = sys_stub_nosys,
[SYS_setrlimit] = sys_stub_nosys,
[SYS_chdir] = sys_chdir,
[SYS_set_tid_address] = sys_stub_nosys,
[SYS_set_robust_list] = sys_stub_nosys,
};
...
set_tohost
によりこれらの処理が実現されているようだ。tohost
にアクセスすることで処理が行われている?
long frontend_syscall(long n, uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6)
{
static volatile uint64_t magic_mem[8];
static spinlock_t lock = SPINLOCK_INIT;
spinlock_lock(&lock);
magic_mem[0] = n;
magic_mem[1] = a0;
magic_mem[2] = a1;
magic_mem[3] = a2;
magic_mem[4] = a3;
magic_mem[5] = a4;
magic_mem[6] = a5;
magic_mem[7] = a6;
htif_syscall((uintptr_t)magic_mem);
long ret = magic_mem[0];
spinlock_unlock(&lock);
return ret;
}
extern uint64_t __htif_base;
volatile uint64_t tohost __attribute__((section(".htif")));
volatile uint64_t fromhost __attribute__((section(".htif")));
volatile int htif_console_buf;
static spinlock_t htif_lock = SPINLOCK_INIT;
uintptr_t htif;
static void __set_tohost(uintptr_t dev, uintptr_t cmd, uintptr_t data)
{
while (tohost)
__check_fromhost();
tohost = TOHOST_CMD(dev, cmd, data);
}
static void do_tohost_fromhost(uintptr_t dev, uintptr_t cmd, uintptr_t data)
{
spinlock_lock(&htif_lock);
__set_tohost(dev, cmd, data);
while (1) {
uint64_t fh = fromhost;
if (fh) {
if (FROMHOST_DEV(fh) == dev && FROMHOST_CMD(fh) == cmd) {
fromhost = 0;
break;
}
__check_fromhost();
}
}
spinlock_unlock(&htif_lock);
}
void htif_syscall(uintptr_t arg)
{
do_tohost_fromhost(0, 0, arg);
}