Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1529768
  • 博文数量: 290
  • 博客积分: 3468
  • 博客等级: 中校
  • 技术积分: 3461
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-28 22:21
文章分类

全部博文(290)

文章存档

2016年(13)

2015年(3)

2014年(42)

2013年(67)

2012年(90)

2011年(75)

分类: 嵌入式

2012-03-12 09:10:12

Loongson1 B 板上 CPU 0 错误跟踪方法

文档简介
Loongson1B 开发板上驱动错误常见 CPU0 错误,而本文档的目的在于以禅
道项目管理系统上 RT3070 驱动错误 BUG 为例,
讲述利用反汇编文件,
跟踪 CPU0
错误的出错位置,并解析可能原因。共享出来希望对大家能够有所帮助。
文档总体来说分为四个部分:
A. RT3070 驱动错误 BUG 详情
B. 跟踪过程
C. 原因分析
D. 方法总结
1. RT3070 驱动错误 BUG 详情
RT3070 无线网卡分两种驱动,
分别是 STA 模式和软 AP 模式, Loongson1B

板中使用的是 STA 模式。由于测试需要,使用网络性能测试工具 iperf 测试
Usb-Wifi 的稳定性,连续测试 22 小时的时候出现 CPU0 错误,现场有保留 ,
BUG 由邝华款提交到禅道项目管理系统,以下列出禅道上该 BUG 的详情:
BUG #177::rt3070 驱动错误
重现步骤:
[步骤]
使 用 iperf 测 试 usb-wifi 约 22 小 时 , 输 出 信 息 与 错 误 见 文 件
minicom.cap;调试文件见 rt1.asm;使用的驱动模块为 rt3070sta.ko;出错
代码段见 DoBulkln.c
RT3070 驱动源码存储地址:\\192.168.1.48\share\测试项目\wifi
[结果]
出现 CPU0 错误
[期望]
2. BUG 跟踪过程
2.1 BUG 现场分析
首先我们查看现场的输出信息与错误 LOG,查看文件 minicom.cap:
------------------------------------------------------------

Client connecting to 192.168.50.101, TCP port 5001
TCP window size: 24.6 KByte (default)
------------------------------------------------------------
[
5] local 192.168.50.102 port 45158 connected with 192.168.50.101 port 5001
[ ID] Interval Transfer Bandwidth
[ 5] 0.0- 1.0 sec 1.25 MBytes 10.5 Mbits/sec
[ 5] 1.0- 2.0 sec 1.25 MBytes 10.5 Mbits/sec
...
[ 5] 61578.0-61579.0 sec 1.25 MBytes 10.5 Mbits/sec
[ 5] 61579.0-61580.0 sec 1.25 MBytes 10.5 Mbits/sec
CPU 0 Unable to handle kernel paging request at virtual address 0000000c, epc ==
c0a122a0, ra == c0a122a0
Oops[#1]:
Cpu 0
$ 0 : 00000000 1000bc00 00000004 0e9e8000
$ 4 : 00000000 00006000 00408000 ae9e8000
$ 8 : 00000000 8fc04800 8fbf8c80 ac4ce000
$12 : 52441080 00000614 37363534 00004a47
$16 : c00e3bb0 00000000 00000002 c00e7000
$20 : 8022e560 8022e9b0 80740000 00015650
$24 : 00000000 c0a121b8
$28 : 8e872000 8e873ac0 2aacd9b8 c0a122a0
Hi : 000005e4
Lo : 00000000
epc : c0a122a0 DoBulkIn+0xe8/0x1b0 [rt3070sta]
ra : c0a122a0 DoBulkIn+0xe8/0x1b0 [rt3070sta]
Status: 1000bc03
Not tainted
KERNEL EXL IE
Cause : 10800008
BadVA : 0000000c
PrId
: 00004220
Modules linked in: rt3070sta
Process iperf (pid: 306, threadinfo=8e872000, task=81284ca0)
Stack : fffffffe 80740000 80740000 00015650 00000000 80740000 80740000 8071e9e0
fffffffe 80740000 8022f044 8022f044 ffffffff 807734d0 806b0000 00015650
00000000 80746064 00000001 80747b30 00000009 8022e7f8 8fa323c0 33323130
00000000 c09ffc58 1000bc01 8fa323c0 00000000 8e961480 8e961494 fffffff0
806b0000 8022e92c 8053315c 8053315c c0a130f4 39383736 80740000 8022ea60
...
Call Trace:
[] DoBulkIn+0xe8/0x1b0 [rt3070sta]

[<8022f044>] tasklet_hi_action+0xc0/0x18c
[<8022e7f8>] __do_softirq+0x8c/0x134
[<8022e92c>] do_softirq+0x8c/0xb8
[<8022ea60>] local_bh_enable+0xb0/0xd0
[<80521448>] dev_queue_xmit+0x1b4/0x31c
[<8054996c>] ip_output+0x1c8/0x410
[<805485d0>] ip_queue_xmit+0x4dc/0x648
[<8055ea38>] tcp_transmit_skb+0x5bc/0x9d8
[<80560a70>] tcp_push_one+0x100/0x16c
[<80552718>] tcp_sendmsg+0x6f0/0xe7c
[<805114bc>] sock_aio_write+0x100/0x128
[<8028a09c>] do_sync_write+0xd8/0x158
[<8028a280>] vfs_write+0x164/0x178
[<8028a388>] sys_write+0x54/0xa0
[<8020b420>] stack_done+0x20/0x3c
Code: 24420dd0
24050020
0040f809
02202821 <8e24000c> 3c02804d
2442b4b8
0040f809
14400012
Kernel panic - not syncing: Fatal exception in interrupt
我们把重点放在分析 CPU0 错误的打印信息:
CPU 0 无法处理的内核虚拟地址 0000000c 的分页请求,其中异常程序计数
寄存器 EPC 的值为 c0a122a0,返回地址 ra 为 c0a122a0。
首先看看 EPC 的值的作用:EPC 寄存器用于指向异常发生时指令跳转前的
执行位置,一般是受害指令地址 。异常发生时,程序计数器(PC)的高 31 位
有效位被记录在 EPC 寄存器的高 31 位字段中,
所以这里异常发生的 32 位指令
地址是 60507150,但是由于驱动指令模块化,我们并不能根据这个地址在反
汇编文件中找到对应的指令,
所以在这个例子里关键还是得根据其他打印信息
来定位发生异常的指令位置。
接着下边系统打印了系统的堆栈信息,然后是系统的 Call Trace 调试信
息,在 Linux 环境下开启系统的 Call Trace 调试,则 Call Trace 会在程序出
现异常的时候把当前的函数的调用栈打印出来,调用顺序是由下往上。
直接看第一条信息:[] DoBulkIn+0xe8/0x1b0 [rt3070sta],
寄存器值为 c0a122a0,同时提示在 rt3070sta 模块内的 DoBulkIn 函数内+偏
移地址 0xe8 位置,前边已经提到在这里我们无法根据 epc 的值来跟踪到异常
指令位置,但是在 rt3070sta.ko 的反汇编文件 rt1.asm 可以跟踪到 DoBulkIn
函数的指令段,所以先去查找 rt1.asm 中的 DoBulkIn 函数:
0007c1b8 :
7c1b8: 27bdffd0 addiu sp,sp,-48
7c1bc: 3c020000 lui v0,0x0
7c1c0: afb40020 sw s4,32(sp)
...

7c360: 03e00008 jr ra
7c364: 27bd0030 addiu sp,sp,48
其中 DoBulkIn 函数的起始地址是 0x0007c1b8,那么 DoBulkIn+0xe8 的值
就是 0x0007c1b8+0xe8=0x0007c2a0,查看 rt1.asm 文件中的 0x0007c2a0 位置
的指令为:
7c2a0: 8e24000c lw a0,12(s1)
这里的指令操作是将寄存器 s1 作为基地址,基地址+偏移地址 12=0x0c 所
指的一个字的数据读取到寄存器 a0 中,而我们观察到一开始 CPU 0 提示无法
处理的内核虚拟地址 0000000c 的分页请求,
也就是这里 s1+0x0c=0x0000000c,
可以算出 s1=0。到这里就很明显看出是 s1 的地址出现异常了。
2.2 代码跟踪
已经发现异常所在的指令了,那么如何在程序代码里边追踪到对应的位置
呢?关键在于找出程序代码与汇编指令之间的联系 。使用代码查看工具,
建立
rt3070sta 源 代 码 工 程 , 并 跳 转 到 DoBulkIn 函 数 入 口 , 与 rt1.asm 中 的
DoBulkIn 函数指令段一起分析:
VOID DoBulkIn(IN RTMP_ADAPTER *pAd)
//函数头
{
PRX_CONTEXT
pRxContext;
PURB
pUrb;
int
ret = 0;
unsigned long IrqFlags;
RTMP_IRQ_LOCK(&pAd->BulkInLock, IrqFlags);//子函数
...
首先函数头传进来一个结构体指针,而在 MIPS 体系结构中,MIPS 会通过
a0 ~ a3 这四个寄存器传参,第一参数传给寄存器 a0,依次类推,那么这里
的结构体基地址 pAd 便传给寄存器 a0,然后我们分析一下汇编指令:
0007c1b8 :
//函数入口
7c1b8: 27bdffd0 addiu sp,sp,-48
7c1bc: 3c020000 lui v0,0x0
7c1c0: afb40020 sw s4,32(sp)
7c1c4: afb20018 sw s2,24(sp)
7c1c8: afb10014 sw s1,20(sp)
7c1cc: afb00010 sw s0,16(sp) //以上操作均为将可能用到的寄存
器值保存进堆栈
7c1d0: 24540000 addiu s4,v0,0
7c1d4: 00808021 move s0,a0 //注意这里用到 a0,开始传参,


a0 传给 s0,也就是现在结构体基地址 pAd 便传给寄存器 s0 了
7c1d8: afbf0028 sw ra,40(sp)
7c1dc: afb50024 sw s5,36(sp)
7c1e0: 0280f809 jalr s4
//注意这是跳转指令,这里标志着
进入新的函数
7c1e4: afb3001c sw s3,28(sp)
分析到这里,我们将代码与指令联系起来,通过标记为红色字体的语句,
能很容易看出与寄存器 s0 相关的操作就可能是对结构体相关成员的操作,而
7c1e0 处的跳转指令,则是代码中第一个子函数的使用。这里说明一点,根据
汇编指令中的跳转指令,
可以较容易的定位到汇编指令操作对应的程序代码范
围,接下来分析下一个重要位置:
7c1e8: 92020a19 lbu v0,2585(s0)
7c1ec: 92030a18 lbu v1,2584(s0)
这里是与寄存器 s0 相关的操作,即将 s0+偏移地址 2585(2584)所指的
一个无符号字节加载到 v0(v1)
,上边我们知道 s0 存储的是结构体的基地址
pAd,也就是这两句指令是与 pAd 相关的操作,而且被操作的结构体的成员偏
移量相差 1,再看回程序代码段:
RTMP_IRQ_LOCK(&pAd->BulkInLock, IrqFlags);
//子函数
pRxContext = &(pAd->RxContext[pAd->NextRxBulkInIndex]);
if ((pAd->PendingRx > 0) || (pRxContext->Readable == TRUE) ||
(pRxContext->InUse == TRUE))
{
RTMP_IRQ_UNLOCK(&pAd->BulkInLock, IrqFlags);
return;
}
在子函数之后对 pAd 的操作分别为 RxContext 以及 PendingRx 成员,查看
RxContext 与 PendingRx 定义位置:
#ifdef RTMP_MAC_USB
RX_CONTEXT RxContext[RX_RING_SIZE]; /*
1
for
redundant
multiple IRP bulk in. */
NDIS_SPIN_LOCK BulkInLock; /* BulkIn spinlock for 4 ACs */
UCHAR PendingRx;
发 现 RxContext 以 及 PendingRx 的 定 义 并 不 是 连 续 的 , 中 间 隔 了 一 个
NDIS_SPIN_LOCK 定义,那与前边的看到的偏移量相差 1 并不相符,是不是找
错成员了呢?其实不然,因为 NDIS_SPIN_LOCK 是一个空定义,所以偏移量刚
好相差 1,
所以可以定位到,
pAd 偏移 2584 即为成员 PendingRx,
pAd 偏移 2585
即为操作成员 RxContext,从接下来的汇编指令可以得到验证:
7c1e8: 92020a19 lbu v0,2585(s0)
7c1ec: 92030a18 lbu v1,2584(s0)
//v1 为 pAd 的成员 PendingRx
7c1f0: 24040001 li a0,1
广州龙芯电子科技有限公司
网址:
Page 8 of 11
7c1f4: 00021140 sll v0,v0,0x5
7c1f8: 00509021 addu s2,v0,s0
7c1fc: 14600009 bnez v1,7c224 // 判 断 v1 是 否
为 0,如果不等于 0 则跳转到 7c224
观察上边标记为蓝色字体的汇编指令和程序代码,可以看出其内在联系,
可 能 会 有 疑 问 , 为 什 么 去 判 断 v1 不 等 于 0 , 但 是 代 码 里 边 是 去 判 断
pAd->PendingRx > 0,原因是 UCHAR PendingRx,这是一个 unsigned char,
判断其不等于 0 与判断其大于 0 是等价的。
那么汇编指令中接下来的几个判断指令均是程序代码中 if 语句中的条件
判断:
7c1fc: 14600009 bnez v1,7c224
...
7c208: 10440006 beq v0,a0,7c224
...
7c21c: 1444000c bne v0,a0,7c250
刚好是三个条件判断,与程序代码中 if 三个条件判断相符,直接跳转到
7c250,这时定位到这段条件语句之后,然后把到 7c2a0 之前的所有跳转指令
列出来如下:
7c250: a2240017 sb a0,23(s1)
7c254: a2240015 sb a0,21(s1)
...
7c26c: 02a0f809 jalr s5
...
7c284: 0040f809 jalr v0
...
7c298: 0040f809 jalr v0
...
有三个跳转指令,说明之间经过了三次函数调用,在条件语句之后的程序
代码中找出三次函数调用如下:
pRxContext->InUse = TRUE;
pRxContext->IRPPending = TRUE;
pAd->PendingRx++;
pAd->BulkInReq++;
RTMP_IRQ_UNLOCK(&pAd->BulkInLock, IrqFlags);
/* Init Rx context descriptor*/
NdisZeroMemory(pRxContext->TransferBuffer,
pRxContext->BulkInOffset);
RTUSBInitRxDesc(pAd, pRxContext);
pUrb = pRxContext->pUrb;
这样就定位到最后红色字体标记的程序语句了 ,我们分析一下汇编指令的
广州龙芯电子科技有限公司
网址:
Page 9 of 11
操作:
7c29c: 02202821 move a1,s1
7c2a0: 8e24000c lw a0,12(s1)
按照以上指令分析,
即使将寄存器 s1+偏移地址 0x12 指向的一个字的数据
加载到寄存器 a0 中,配合程序代码语句分析,即寄存器 a0 的值对应是 pUrb,
而寄存 器 s1 的值对 应是 pRxContext,而 pUrb 在 pRxContext 中的偏 移量是
0x12。
需要再加验证一下,列出在 DoBulkIn 指令段里边与 s1 相关的几句汇编指
令:
0007c1b8 :
...
7c1f0: 24040001 li a0,1
...
7c250: a2240017 sb a0,23(s1)
7c254: a2240015 sb a0,21(s1)
...
7c2a0: 8e24000c lw a0,12(s1)
对比程序代码段,可以发现与以下代码相匹配:
pRxContext->InUse = TRUE;
pRxContext->IRPPending = TRUE;
所以寄存器 s1 的值对应的是 pRxContext,再结合 7c2a0 这句汇编指令,
可以证明其作用是将 pRxContext 中的对象 pUrb (偏移值为 0x0c)的值赋给
pUrb。
到此,根据汇编指令跟踪到程序代码段里边的具体语句已经完成 。系统在
执行 pUrb = pRxContext->pUrb 赋值时出现异常,然后打印出 CPU0 错误信息,
程序退出。
3. BUG 原因分析
CPU 0 无法处理的内核虚拟地址 0000000c 的分页请求,而对应出错位置的
汇编指令为:
7c2a0: 8e24000c lw a0,12(s1)
对应出错位置的程序语句为:
pUrb = pRxContext->pUrb;
有以下分析:
系统在执行这条赋值语句时,需要取到结构体指针 pRxContext 的基地址,
然后加上 pUrb 在该结构体中偏移地址,得到 pRxContext->pUrb 的地址,再进
行赋值,通过汇编指令,表明 pUrb 在该结构体中偏移地址为 0x0c,所以此时
结构体指针 pRxContext 的基地址为 NULL,这是非法的,所以这个 BUG 出现的

原因总结为:pRxContext 被赋 NULL 导致。
那么会有什么原因导致 pRxContext 被赋 NULL 导致呢?
分析以下几种可能:
第 一 种 可 能为 执 行 这 个 语句 之 前 的 RTUSBInitRxDesc(pAd, pRxContext)
函数调用导致的堆栈溢出;
但是由于该函数里面只是初始化了一个 URB 接收描
述符,这个调用过程耗用的堆栈极少,所以可能性比较低,可以通过在每次进
入 DoBulkIn()函数之前查看堆栈位置看有没有接近栈底来进行验证。
第二种可能为内存改写;死机时间发生在中断下半部,如果有内存改写只
可能是中断程序改写或者 DMA 改写。如果是 DMA 改写应该是改写一块区域,在
DoBulkIn()增加 Magic 看是否被改写。
试图重现该 BUG,但是虽然有其他 CPU0 错误的出现,并没有再出现类似同
样出现在 DoBulkIn()函数里的错误,所以暂时无法验证问题所在原因。
4. 方法总结
前边以 RT3070 驱动错误 BUG 为例,讲述了根据反汇编文件来定位跟踪到
程序代码中具体某一行代码出现异常的方法,主要需要掌握以下几点:
1. 及时保存现场,包括调试打印信息以及内核相关文件,最好是能够现
场调试跟踪,因为有些 BUGS 可能较难重现;
2. 根据 Call Trace 调试信息跟踪到内核相关文件的反汇编文件;
3. 结合 CPU0 打印信息及跟踪到异常位置的汇编指令分析出现问题的可能
原因;
4. 通过反汇编文件与程序代码对比分析,抓住主要跳转指令、判断指令
以及某些重点参数与寄存器之间的关系 ,从而定位到某一行代码语句;
本文档的目的也仅仅在于讲述一种跟 踪 Loongson1B 开发板上 CPU0 错误到
具体程序代码的方法,而解决 BUGS 的重点最终还是对于定位到异常之后的分
析过程,这个往往需要具备调试经验及对系统的深入了解。


阅读(4179) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~