最近debug的一些记录

0. 前言

不算一篇正经的文章,只是记录一下最近几天在k210板子上跑xv6的经历,也许可以供以后参考。

1. 遇到的困难

1.1 S态对U态的访问(qemu)

在之前的xv6-k210项目中是proc用两个页表进行处理,尝试将两个页表合为1个,此时要求S态能够自由地访问U态的页表,自然地认为这是显然的事情,结果触发异常,debug找了很久的原因,发现S态是不能访问设置了PTE_U位的页。查了一下riscv手册,发现需要设置sstatus的SUM位为1才能正常访问。

1.2 C与C++的混合编译

解决了问题1.1后,总算在qemu上成功运行了,接下来开始搬sd卡驱动的代码。因为现在的xv6代码已经用C++改写了,本希望简单地把sd卡驱动的代码文件换成cpp后缀然后用g++编译,结果代码许多地方并不符合C++规范,编译不过。尝试改写无望(个中收获:C99允许 点号+属性 给结构体乱序初始化,之前没有见过这种写法。嗯,搜索“C99 结构体指定初始化” 就可以了)。

于是尝试C与C++混合编译,xv6代码部分使用g++,而sd卡驱动部分使用gcc,比较麻烦的是sd卡驱动部分会用到xv6中的一些函数。问题是怎么在C中调用C++的函数或者反过来。呃,最后的收获是明白了下面的一些东西

#ifdef __cplusplus
//若干代码
#endif

extern "C" int f{    // C++ f便可在C中调用,其实只要在f声明时加上 extern "C"就可以了

}
extern "C" int f(class A* a){  // 在C++文件中定义wrapper函数,便可以在C文件中调用类A的成员函数。
	return a->t();
}

//C文件中声明
int f(struct A*);

1.3 关于.bin文件和elf文件

之前有个误区,一直觉得elf文件就是二进制文件,虽然这样说也没错。但是它和.bin这样的二进制文件是有区别的,.bin文件是实际的内存镜像,elf文件中包含那些section之类的信息不会加载到内存中。现在经过1.2的调整把内核编译完成了,是没办法直接运行的,因为cpu不能识别二进制文件。现在就能够理解objcopy工具的作用了,它用来进行二进制文件之间的格式转换,就可以用它来把elf文件转换为bin文件,实际烧录进内存的是bin文件而不是elf文件。

1.4 关于链接

1.3的问题解决了后,发现内核依然不能正常运行。反汇编查看内核代码,发现内核的入口函数并不在代码段的起始位置0x80020000。因此一开始运行的函数不是入口函数(因为作为bios的rustsbi运行后,会自动跳转到0x80020000处运行此处的代码),发现对链接器的参数有些误解。

通常,使用链接器ld链接若干个.o文件,参数 -Ttext 用于指定代码段的起始地址,这里设置为0x80020000。然后用参数 -e 指定入口地址,即程序开始运行时epc应该指向的位置。问题就出在这个入口地址上,入口地址并不等于代码段的起始地址,对于elf二进制文件,入口地址由程序头指定。但是现在这些信息已经都由objcopy给去掉了,只剩下孤零零的代码段和数据段。所以这里指定入口地址其实没实际的作用。

试了一下,貌似编译后的代码段的排列是按照函数声明的顺序,就是说明同一个.c文件中的函数,声明得越靠前,编译后的.o文件中该函数的代码所在地址就越靠前。而链接时,若干个.o文件的代码合在一起,传参数时越靠前的.o文件,它的text段在最终elf文件的位置就越靠前。因此,如果希望编译完成后入口函数在地址0x80020000处,需要保证入口函数在相应的.c文件中第一个声明,且传参时第一个.o文件包含入口函数

1.5 注意硬件遵循的指令集版本

现在终于板子上跑起来了,然后在用户态的运行始终有毛病,经过一系列的debug定位,发现设置了sstatus的SUM位后,内核没办法访问用户态的页。后来知道k210遵循的riscv版本是1.9,而qemu上模拟的版本会更新一点,qemu上模拟的版本的标准是需要设置SUM位为1才能访问。而k210上恰恰相反,设置为0才能访问

1.6 指令缓存的同步

现在用户态能够运行后,在fork后尝试写却会爆奇怪的page fault。因为是共享页面,在fork之后会将页表的PTE_W位置0,然后将 PTE_RSW位(预留位)置1,表明这是一个COW页。但是因为非法写陷入内核后,发现该页的PTE_RSW位为0且PTE_W位也为0,这就很奇怪了,因为在设置了PTE_W=0后总是会把PTE_RSW设置为1。根据之前rustsbi报错的经验,我怀疑这里可能是因为指令缓存同步出现了问题。尝试在页表修改后调用sfence.vma和fence.i指令清空缓存。果然报错就消失了。

当然这里其实有一些遗留问题,我想指令缓存同步的问题出在TLE对页表项的缓存上,而没有看见对内存中页表的写。但为什么硬件看见了对PTE_W的写,但是却没有看见对PTE_RSW的写?(选择性眼盲?)。以及对内存序问题,fence.i指令的理解,这些希望在学习体系结构的时候能够思考一下。

当然最重要的一点是记住:修改页表项时要进行指令缓存的同步!

2. 结语

老实说系统层面的代码debug真是令人头秃,没有可以直接使用的debug工具,只能一路print对问题进行定位(噢,这还是蛮重要的心得,定位报错所在,print上下文对问题进行分析)。而且上面这些问题除了1.2之外,估计只有在写系统代码可能才会遇到了。大概算是不想第二次遇到的难得体验吧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值