b *0xe06 //打断点
hbreak *0x3ffffff000 //打硬件断点
(gdb) b *0xe06
Breakpoint 1 at 0xe06
(gdb) c
Continuing.
此时停在0xe06,将要执行ecall,可以看到此时pc正是0xe06,且stvec为0x3ffffff000,这是trampoline.S的入口,uservec函数
stvec寄存器:trap的入口,xv6是Direct模式(另一种是Vectored),所有trap同一入口
(gdb) hbreak *0x3ffffff000
Hardware assisted breakpoint 2 at 0x3ffffff000
(gdb) c
Continuing.
此时停在csrw sscratch,a0,uservec的第一个指令。
sepc为ecall指令的地址,这是因为trap会保存用户程序的pc,以便系统调用结束正常执行用户程序
(gdb) print/x $sepc
$4 = 0xe06
(gdb) print/x $pc
$5 = 0x3ffffff000
到目前为止,都没有保存寄存器。采用汇编语言来进行保存正是为了防止编译器使用寄存器,从而覆盖未保存的寄存器
trapframe
前五个是内核事先存放在trapframe中的数据。比如第一个数据保存了kernel page table地址,这将会是trap处理代码将要加载到SATP寄存器的数值。
struct trapframe {
/* 0 */ uint64 kernel_satp; // kernel page table
/* 8 */ uint64 kernel_sp; // top of process's kernel stack
/* 16 */ uint64 kernel_trap; // usertrap()
/* 24 */ uint64 epc; // saved user program counter
/* 32 */ uint64 kernel_hartid; // saved kernel tp
/* 40 */ uint64 ra;
// ...
// ...
// ...
/* 280 */ uint64 t6;
};
uservec
在“用户页表 + S 模式 + 无栈可用的极限环境下,完成:
① 保存所有用户寄存器到 trapframe
② 从 trapframe 中取出未来要用的 kernel 信息(stack / satp / trap 函数 / hartid)
③ 切换到内核栈 + 内核页表,然后跳进 usertrap()。