`
mmdev
  • 浏览: 12950818 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

Loongson1B板上CPU0错误跟踪方法 (以RT3070驱动错误-BUG为例)

 
阅读更多

文档简介

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.1BUG现场分析

首先我们查看现场的输出信息与错误LOG,查看文件minicom.cap:

------------------------------------------------------------

Clientconnectingto192.168.50.101,TCPport5001

TCPwindowsize:24.6KByte(default)

------------------------------------------------------------

[5]local192.168.50.102port45158connectedwith192.168.50.101port5001

[ID]IntervalTransferBandwidth

[5]0.0-1.0sec1.25MBytes10.5Mbits/sec

[5]1.0-2.0sec1.25MBytes10.5Mbits/sec

[5]61578.0-61579.0sec1.25MBytes10.5Mbits/sec

[5]61579.0-61580.0sec1.25MBytes10.5Mbits/sec

CPU0Unabletohandlekernelpagingrequestatvirtualaddress0000000c,epc==c0a122a0,ra==c0a122a0

Oops[#1]:

Cpu0

$0:000000001000bc00000000040e9e8000

$4:000000000000600000408000ae9e8000

$8:000000008fc048008fbf8c80ac4ce000

$12:52441080000006143736353400004a47

$16:c00e3bb00000000000000002c00e7000

$20:8022e5608022e9b08074000000015650

$24:00000000c0a121b8

$28:8e8720008e873ac02aacd9b8c0a122a0

Hi:000005e4

Lo:00000000

epc:c0a122a0DoBulkIn+0xe8/0x1b0[rt3070sta]Nottainted

ra:c0a122a0DoBulkIn+0xe8/0x1b0[rt3070sta]

Status:1000bc03KERNELEXLIE

Cause:10800008

BadVA:0000000c

PrId:00004220

Moduleslinkedin:rt3070sta

Processiperf(pid:306,threadinfo=8e872000,task=81284ca0)

Stack:fffffffe8074000080740000000156500000000080740000807400008071e9e0

fffffffe807400008022f0448022f044ffffffff807734d0806b000000015650

00000000807460640000000180747b30000000098022e7f88fa323c033323130

00000000c09ffc581000bc018fa323c0000000008e9614808e961494fffffff0

806b00008022e92c8053315c8053315cc0a130f439383736807400008022ea60

...

CallTrace:

[<c0a122a0>]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:24420dd00040f80902202821<8e24000c>3c02804d2442b4b80040f8092405002014400012

Kernelpanic-notsyncing:Fatalexceptionininterrupt

我们把重点放在分析CPU0错误的打印信息:

CPU0无法处理的内核虚拟地址0000000c的分页请求,其中异常程序计数寄存器EPC的值为c0a122a0,返回地址ra为c0a122a0。

首先看看EPC的值的作用:EPC寄存器用于指向异常发生时指令跳转前的执行位置,一般是受害指令地址。异常发生时,程序计数器(PC)的高31位有效位被记录在EPC寄存器的高31位字段中,所以这里异常发生的32位指令地址是60507150,但是由于驱动指令模块化,我们并不能根据这个地址在反汇编文件中找到对应的指令,所以在这个例子里关键还是得根据其他打印信息来定位发生异常的指令位置。

接着下边系统打印了系统的堆栈信息,然后是系统的CallTrace调试信息,在Linux环境下开启系统的CallTrace调试,则CallTrace会在程序出现异常的时候把当前的函数的调用栈打印出来,调用顺序是由下往上。

直接看第一条信息:[<c0a122a0>]DoBulkIn+0xe8/0x1b0[rt3070sta],寄存器值为c0a122a0,同时提示在rt3070sta模块内的DoBulkIn函数内+偏移地址0xe8位置,前边已经提到在这里我们无法根据epc的值来跟踪到异常指令位置,但是在rt3070sta.ko的反汇编文件rt1.asm可以跟踪到DoBulkIn函数的指令段,所以先去查找rt1.asm中的DoBulkIn函数:

0007c1b8<DoBulkIn>:

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:8e24000clwa0,12(s1)

这里的指令操作是将寄存器s1作为基地址,基地址+偏移地址12=0x0c所指的一个字的数据读取到寄存器a0中,而我们观察到一开始CPU0提示无法处理的内核虚拟地址0000000c的分页请求,也就是这里s1+0x0c=0x0000000c,可以算出s1=0。到这里就很明显看出是s1的地址出现异常了。

2.2代码跟踪

已经发现异常所在的指令了,那么如何在程序代码里边追踪到对应的位置呢?关键在于找出程序代码与汇编指令之间的联系。使用代码查看工具,建立rt3070sta源代码工程,并跳转到DoBulkIn函数入口,与rt1.asm中的DoBulkIn函数指令段一起分析:

VOIDDoBulkIn(INRTMP_ADAPTER*pAd)//函数头

{

PRX_CONTEXT pRxContext;

PURB pUrb;

int ret=0;

unsignedlong IrqFlags;

RTMP_IRQ_LOCK(&pAd->BulkInLock,IrqFlags);//子函数

首先函数头传进来一个结构体指针,而在MIPS体系结构中,MIPS会通过a0~a3这四个寄存器传参,第一参数传给寄存器a0,依次类推,那么这里的结构体基地址pAd便传给寄存器a0,然后我们分析一下汇编指令:

0007c1b8<DoBulkIn>://函数入口

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:00808021moves0,a0//注意这里用到a0,开始传参,将a0传给s0,也就是现在结构体基地址pAd便传给寄存器s0了

7c1d8: afbf0028 sw ra,40(sp)

7c1dc: afb50024 sw s5,36(sp)

7c1e0:0280f809jalrs4//注意这是跳转指令,这里标志着进入新的函数

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定义位置:

#ifdefRTMP_MAC_USB

RX_CONTEXTRxContext[RX_RING_SIZE]; /*1forredundantmultipleIRPbulkin.*/

NDIS_SPIN_LOCKBulkInLock; /*BulkInspinlockfor4ACs*/

UCHARPendingRx;

发现RxContext以及PendingRx的定义并不是连续的,中间隔了一个NDIS_SPIN_LOCK定义,那与前边的看到的偏移量相差1并不相符,是不是找错成员了呢?其实不然,因为NDIS_SPIN_LOCK是一个空定义,所以偏移量刚好相差1,所以可以定位到,pAd偏移2584即为成员PendingRx,pAd偏移2585即为操作成员RxContext,从接下来的汇编指令可以得到验证:

7c1e8: 92020a19 lbu v0,2585(s0)

7c1ec:92030a18lbuv1,2584(s0)//v1为pAd的成员PendingRx

7c1f0: 24040001 li a0,1

7c1f4: 00021140 sll v0,v0,0x5

7c1f8: 00509021 addu s2,v0,s0

7c1fc:14600009bnezv1,7c224<DoBulkIn+0x6c>//判断v1是否为0,如果不等于0则跳转到7c224

观察上边标记为蓝色字体的汇编指令和程序代码,可以看出其内在联系,可能会有疑问,为什么去判断v1不等于0,但是代码里边是去判断pAd->PendingRx>0,原因是UCHARPendingRx,这是一个unsignedchar,判断其不等于0与判断其大于0是等价的。

那么汇编指令中接下来的几个判断指令均是程序代码中if语句中的条件判断:

7c1fc: 14600009 bnez v1,7c224<DoBulkIn+0x6c>

7c208: 10440006 beq v0,a0,7c224<DoBulkIn+0x6c>

7c21c: 1444000c bne v0,a0,7c250<DoBulkIn+0x98>

刚好是三个条件判断,与程序代码中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);

/*InitRxcontextdescriptor*/

NdisZeroMemory(pRxContext->TransferBuffer,pRxContext->BulkInOffset);

RTUSBInitRxDesc(pAd,pRxContext);

pUrb=pRxContext->pUrb;

这样就定位到最后红色字体标记的程序语句了,我们分析一下汇编指令的操作:

7c29c: 02202821 move a1,s1

7c2a0:8e24000clwa0,12(s1)

按照以上指令分析,即使将寄存器s1+偏移地址0x12指向的一个字的数据加载到寄存器a0中,配合程序代码语句分析,即寄存器a0的值对应是pUrb,而寄存器s1的值对应是pRxContext,而pUrb在pRxContext中的偏移量是0x12。

需要再加验证一下,列出在DoBulkIn指令段里边与s1相关的几句汇编指令:

0007c1b8<DoBulkIn>:

7c1f0: 24040001 li a0,1

7c250:a2240017sba0,23(s1)

7c254:a2240015sba0,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原因分析

CPU0无法处理的内核虚拟地址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.根据CallTrace调试信息跟踪到内核相关文件的反汇编文件;

3.结合CPU0打印信息及跟踪到异常位置的汇编指令分析出现问题的可能原因;

4.通过反汇编文件与程序代码对比分析,抓住主要跳转指令、判断指令以及某些重点参数与寄存器之间的关系,从而定位到某一行代码语句;

本文档的目的也仅仅在于讲述一种跟踪Loongson1B开发板上CPU0错误到具体程序代码的方法,而解决BUGS的重点最终还是对于定位到异常之后的分析过程,这个往往需要具备调试经验及对系统的深入了解。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics