经过前面的调试,目前已经验证了加减乘除的整数运算是没问题的,包括循环控制流。但是想要实现printf等功能还需要完善系统调用的模拟执行。
RISC-V规定当执行ecall时,会把系统调用号放在a7寄存器中,a0到a6是参数,所以当遇到ecall指令后,可以读取a7的系统调用号id,然后去做相应的操作,具体就是借用X86的系统调用来模拟执行。
int main(int argc, char *argv[])
{
// ...
while (true)
{
machine_step(&machine);
uint64_t syscall_id = machine_get_gp_reg(&machine, a7);
uint64_t ret = do_syscall(&machine, syscall_id);
machine_set_gp_reg(&machine, a0, ret);
}
return 0;
}
系统调用执行前,需要读a7的值,从而决定执行什么样的系统调用,系统调用执行完之后会把返回结果写回a0寄存器,即是否成功执行。
uint64_t do_syscall(machine_t *machine, uint64_t syscall_id)
{
uint64_t ret = 0;
switch (syscall_id)
{
case SYS_exit:
ret = sys_exit(machine);
break;
case SYS_fstat:
ret = sys_fstat(machine);
break;
case SYS_brk:
ret = sys_brk(machine);
break;
case SYS_close:
ret = sys_close(machine);
break;
case SYS_write:
ret = sys_write(machine);
break;
default:
MYEXIT("syscall unimplement");
}
return ret;
}
这里只实现了printf所用到的一些系统调用,以sys_brk为例:
uint64_t sys_brk(machine_t *machine)
{
uint64_t addr = machine_get_gp_reg(machine, a0);
if (addr == 0)
addr = machine->mmu.guest_alloc;
assert(addr > TO_GUEST(machine->mmu.host_base));
uint64_t size = addr - machine->mmu.guest_alloc;
mmu_alloc(&machine->mmu, size);
return addr;
}
sys_brk是Linux中管理进程堆内存的一个系统调用,malloc和free会用到这个系统调用。
目前模拟器已经算是基本完成了,但是还有许多指令有待验证,后续会慢慢debug。
这里记录一些碎的东西: