RISC-V Spikeシミュレータでは printf()
や std::cout
を使ってもコンソールで出力することができる。
- test_output.c
#include <stdio.h> int main () { printf ("Hello World, C\n"); return 0; }
- test_output.cpp
#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)
- run_output_cpp.log
... 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
でダンプしていく。
- libpk.a のダンプ結果
セクション .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()
が呼ばれているのかな。
- riscv-pk/pk/handlers.c
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); }
- handlers.c
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()
が呼ばれる。ここからどこへ進むんだ?
- riscv-pk/pk/syscalls.c
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
にアクセスすることで処理が行われている?
- riscv-pk/pk/htif.c
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; }
- riscv-pk/machine/htif.c
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); }