简单记录一下Debug的折磨历程
问题描述
书接上文,Linux内核ioremap函数问题小记,这篇里我们加载镜像文件成功并成功执行 ret
指令返回内核模块。
本文则是开始执行镜像入口函数之后,一直崩。一路 Debug 的过程记录。
先贴图:执行真正的镜像代码之后,内核直接无输出,崩掉了,没有任何打印,借助 QEMU 看到各寄存器状态如下:
问题很明确:有指令触发了 instruction page fault
,代码进入 stvec
指向的地址执行,但该地址没有映射,不可执行。
小 插曲
原本镜像加载的虚拟地址固定为 IOREMAP 的区间中的:0xff20000000c00000
。这个地址是 RISCV Ubuntu
系统在 SV57
分页模式下的高地址,读者可验证符号扩展的正确性。
但老遇到问题,时而加载不了,时而可以加载。也就是这段虚拟地址空间时而可用,时而不可用。
非常的烦人,不可用就要重启重新尝试。这个错误还是类似奇偶出现的形式,重启一次就好了,再来就不行,然后又可以这样。
遂,打印 [VMALLOC_START, VMALLOC_END]
这段区间的内容,具体如下:[VMALLOC_START = ff20000000000000, VMALLOC_END = ff60000000000000]
。
尝试探究偶发失败的原因,遂执行命令:cat /proc/vmallocinfo
部分截图如下所示:
说明从 0xff20000000c00000
开始的这段区间是内核常用分配 IOREMAP 的位置。
遂把映射地址改为:0xff30000000c00000
。这个问题解决了。
原因分析:
回到原来的问题:既然镜像经常崩溃,在镜像中又无法打印,那么我们只能通过在镜像代码中加入死循环的方式来进行非常原始的单步行为。
中间繁杂的尝试、暂停、打印寄存器组的内容等等行为就不一一展示了。最终初步定位到问题如下。
在镜像入口的设置页表前的代码中加入死循环,如下:
temp_entry:j temp_entry/** 设置SATP寄存器启用页表* MODE=10 (Sv57), ASID=0, PPN=bootstrap_pt_l0的物理地址>>12*/la x12, bootstrap_pt_l0virt2phys x12 // 转换为物理地址srli x12, x12, 12 // 右移12位得到PPNli x10, 10 // Sv57模式 slli x10, x10, 60 // MODE字段位于[63:60]or x12, x12, x10 // 组合MODE和PPNcsrw satp, x12 // 写入SATP寄存器sfence.vma // 刷新TLBfence.i
重新加载镜像并执行,暂停 CPU 打印寄存器组的内容如下:
随后,利用 objdump 反汇编镜像文件的内容并对应,可以看到:
那么,在设置页表之前,是没有问题的。同样的,把死循环放在这段设置页表的代码之后,崩了。
更细致的来说,我们把死循环放在:
csrw satp, x12 // 写入SATP寄存器
temp_entry:j temp_entrysfence.vma // 刷新TLBfence.i
还是崩。
但我们把死循环换一个位置:
or x12, x12, x10 // 组合MODE和PPN
temp_entry:j temp_entrycsrw satp, x12 // 写入SATP寄存器sfence.vma // 刷新TLBfence.i
这就不崩了。CPU info 如下所示,对应到反汇编的循环位置:
问题就出在页表设置的代码中,是页表内容错了
解决方案:
今天还没有解决方案,明天慢慢 Debug 补上。
昨天分析到,页表内容错了。具体 Debug 原因如下:
.macro set_block table, index, addr, lvl# RISC-V Sv57: 每级9位,第lvl级的页面大小为2^(12+(4-lvl)*9)# 地址对齐掩码计算li t0, 9li t1, 4addi t1, t1, -\lvl # t1 = 4 - lvlmul t1, t1, t0 # t1 = (4 - lvl) * 9li t0, 12add t0, t0, t1 # t0 = 12 + (4 - lvl) * 9# 创建对齐掩码li t1, 1sll t1, t1, t0 # t1 = 1 << (12 + (4 - lvl) * 9)addi t1, t1, -1 # t1 = (1 << (12 + (4 - lvl) * 9)) - 1not t1, t1 # t1 = ~((1 << (12 + (4 - lvl) * 9)) - 1)and t0, \addr, t1 # t0 = addr & 对齐掩码set_pte \table, \index, t0, PAGE_DEFAULT_FLAGS
.endm
这是原本设置大页映射的代码。这里错了,在调用 set_pte 宏的时候应当传入 PTE 的值,PTE中PPN需要左移 10bit,即便做了对齐但也不是物理地址 4k 一页的 12bit。故需要做移位。修改为如下内容:
.macro set_block table, index, addr, lvl/* -------- 1. 计算 shift = 12 + (4-lvl)*9 -------- */li t0, 9li t1, 4addi t1, t1, -\lvl /* t1 = 4-lvl */mul t1, t1, t0 /* t1 = (4-lvl)*9 */li t0, 12add t0, t0, t1 /* t0 = shift *//* -------- 2. 用掩码把物理地址对齐到大页边界 -------- */li t1, -1 /* 全 1 */sll t1, t1, t0 /* t1 = ~((1 << shift)-1) */and t1, \addr, t1 /* t1 = aligned phys base *//* -------- 3. 把 PPN 移到 bit10,并加上叶子标志 -------- */srli t1, t1, 12 /* strip page offset */slli t1, t1, 10 /* PPN ↦ bits 53:10 */ori t1, t1, PAGE_DEFAULT_FLAGS/* -------- 4. 写入 table[index] -------- */la t2, \tableslli t0, \index, 3 /* t0 = index * 8 */add t2, t2, t0sd t1, 0(t2)
.endm
总结
没完结,不能撒花。
完结撒花!!!