''虫虫特工队" 开张了:) 浪影捉虫记!!
记录所遭遇的每只可爱的虫虫,不管需要解决的,还是正在解决的,还是已解决的,又或者是没解决的。
MIPS Watchpoint 问题
MIPS64 Arch, 32位Linux OS。
内核态改写用户态地址空间
故障时间:4月20
环境:NetLogic XLS系列,Linux 2.6 SMP prempt内核。
问题描述:
用户态PIC代码,20(gp)处所存储的函数地址被该,改后的值为原函数地址 + 1,造成指令地址非对齐异常,出现异常后,异常处理函数会再次调用20(gp)处的函数,但此时却能正确调用成功。百思不得其解!
该故障在不同的版本中出现过两回,都出现了20(gp)被修改,两个版本中的20(gp)存储的原函数地址不一样。
分析:
既然20(gp)处的值被改写,首先怀疑的是硬件Cache或者内存出现了单bit错误,但每次都是20(gp)处出现,但别的地址不会出现,因此初步排除硬件问题。
那只能从软件开始查起了。20(gp)这个地址有点特殊,遂遍历了用户态该进程的代码段,搜寻任何能改写20(gp)值的指令:
sd/sw/sh/sb xx, 20(gp),结果未查询到。只能再查询内核代码段,结果搜寻到许多sw xx, 20(gp)指令。内核态时,gp寄存器有特殊用途,20(gp)处正好表示prempt计数,到这就初步怀疑对prempt的加操作导致了该故障的出现。但既然是内核态下所操作的都是内核地址空间,怎么会直接改写了用户态的20(gp)地址值呢?会不会所用的gp仍是用户态下的gp值呢,即根本就没有对上下文进行保存。
突然想到fast syscall是不会保存上下文的,隐隐地感觉到了出问题的地方了:
内核中有个fast syscall的实现有问题,该实现代码中使用了__prempt_disable, __prempt_enable这两个汇编宏,但正如上所述,因fast syscall 不会保存上下文,所用的gp就是用户态带入的gp。这两个宏用在这边本就是个错误,因syscall异常产生后,CPU会自动关闭中断,隐含地就disable prempt了。
故障情景分析1:
内核态, VCPU-x 用户态, VCPU-y
__prempt_disable
do_somethingA <---- *(20(gp))(。。。);//调用该函数,此时因vcpu-x刚把20(gp)的值 + 1,肯定会出现异常
__prempt_enable
do_somethingB <---- *(20(gp))(。。。);//异常处理函数再次调用该函数,此时因vcpu-x刚把20(gp)的值 - 1,恢复后调用正常
故障情景分析2:
因是SMP,__prempt_disable/enable对20(gp)的操作并没有多核互斥保护,在多VCPU同时频繁地调用这个有问题的快速系统调用时,20(gp)处的值极有可能不能被恢复。
实验:
起多个频繁调用该有问题的系统调用的线程,并绑定到不同的VCPU上,并在调用完系统调用后检查20(gp)处的值是否会被更改。
结果:
出现了上述情景1、2。至此本故障解决。
附:
Linux MIPS syscall x指令的使用分为以下两类:
1、x=0; 普通的系统调用,v0: 系统调用号,a0~a3传递参数,若所传递的参数超过4个,则用栈传递。
2、x != 0; 快速系统调用,x值即为快速系统调用号,此种情况下,不会保存上下文,而是直接使用用户态下的寄存器值。