Download presentation
Presentation is loading. Please wait.
1
MIPS汇编及KDB入门 czk
2
目录 通用寄存器及常用CP0寄存器功能介绍 基本汇编指令 Mips汇编中的一些隐式规则 常用kdb命令 使用kdb定位问题的具体方法
杭州迪普科技有限公司
3
通用寄存器 汇编语言以行为单位,由指令和寄存器组成,mips汇编沿袭c语言赋值语句风格,目标寄存器在左,源操作数在右
Mips共有32个通用寄存器,其中$0,$31这两个寄存器有特殊作用,其它寄存器硬件没有做限制,理论上可以随便使用,但为了可读性和兼容性,基本上所有mips处理器都遵循下面的习惯用法,相应于习惯用法对寄存器有一套命名约定,定义在内核<regdef.h>头文件中,经过这个头文件的预处理后,反汇编后的汇编代码中寄存器就不再是$0,$1…$30,$31这些编号,而是头文件中定义的习惯命名,也叫助记符 杭州迪普科技有限公司
4
寄存器作用说明 <0>$ 0 : dc cdb388e <0>$ 4 : dfd ffb3d58 8ffb3e28 <0>$ 8 : da84f394 da84f374 ffffff <0>$12 : c4a9920 bc bfc04800 <0>$16 : e cdb3800 <0>$20 : <0>$24 : dfcc0000 <0>$28 : 8ffb2000 8ffb3d48 db0589f4 dfcc0054 编号 别名 作用 $ zero 永远返回0 $ at (assembly temporary)保留给汇编器使用 $2-$3 v0-v1 子程序返回值 $4-$7 a0-a3 (argument)调用子程序时前4个参数 杭州迪普科技有限公司
5
寄存器作用说明 $8-$15 t0-t7 (temporaries)临时变量,子程序使用时不必保存原值
$16-$23 s0-s7 (stack)子程序使用时必须将原值保存在栈里,在返回调用函数时恢复原值 $24-$25 t8-t9 同t0-t7 $26-$27 k0-k1 保留给异常处理程序使用 $ gp (global pointer) 一般用于存取static变量,因为mips汇编一条指令是4个字节,存取一个数据的偏移最大只能是2^16(前后32k),这样存取一个数据就需要两条指令,先获取地址高位,再在高位基础上加偏移,gp的作用就是对于一些静态变量,让gp指向静态数据的中间,对于前后32k的数据存取一条指令就可完成 杭州迪普科技有限公司
6
寄存器作用说明 $29 sp (stack pointer) 堆栈指针,用于子函数存取临时变量和返回调用函数时需要还原的s0-s7寄存器的值,通常每个函数的第一条指令都是压栈操作,即在子程序的入口将sp压至该子程序可能用到的最低点 $30 f8/s8 (frame pointer)帧指针,作用类似于sp,只在当栈底在编译时还不能确定的情况下使用 $31 ra (return address)子程序返回地址,在每个子程序的入口ra自动保存调用函数的返回地址,典型的子程序都以一条jr ra指令结尾,当该子程序又需要嵌套调用其它子程序时,必须先将ra的值压栈 a函数:… b函数:ra = … c函数:ra = … jal b jal c lw t1 vo lw t1 vo …. jr ra 杭州迪普科技有限公司
7
常用cpo寄存器 异常程序计数器即异常返回地址(EPC):保存异常返回点,即导致异常的指令地址,使用kdb定位问题的入口
原因寄存器(cause):记录异常类型,只有一位比较重要,最高位表示异常发生在分支延迟槽,这时真正导致异常指令应该是epc的下一条指令 状态寄存器(SR):与通常状态寄存器是只读的不同,cpo的SR是可写的,用于控制cpu的工作模式:尾端,协处理器使能,中断使能,大写端配置等 ErrorEPC寄存器:发生cache error时,异常受害指令为ErrorEPC 杭州迪普科技有限公司
8
基本汇编指令 加载和存储(load/store)
ld,lw,lh(lhu),lb(lbu): 加载双字(double 8字节,64位系统才有),字(word),半字(half),字节(byte) lw v0,4(s0) v0 = *(s0 +4); lhu,lbu(u:unsigned)指加载无符号数,因为mips通用寄存器都是32位的,而判断一个数的正负时,只看最高位,所以在加载半字和一个字节时需要根据加载数的最高位(lhu:bit15,lb:bit7)符号扩展或0扩展到所有高位(lbu即0扩展) li (load immediate)加载一个常数:li a0,0x1234 a0 = 0x1234 lui 上位加载一个立即数: lui a0,0x1234 a0 = 0x ; sd,sw,sh,sb:存储双字,字,半字,字节,存储指令不存在符号扩展的问题: sw v0,4(s0) *(s0 +4) = v0; 杭州迪普科技有限公司
9
基本汇编指令 算术/逻辑运算 addu,daddu addiu,daddiu 32/64位加法(指令前缀d代表64位操作,一般只在64位系统上出现,i代表操作数有常数,后续不再列举) addu a2,a2,s5 a2 = a2+s5; subu 减法 : subu v0,a0,v0 v0 = a0-v0; and 逻辑与: and v0,a0,v0 v0 = a0&v0; or 逻辑或: or v0,a0,vo v0 = a0|v0; xor 逻辑异或: xor v0,a0,vo v0 = a0 ^vo; nor 逻辑取反: nor v0,a0,vo v0 = ~(a0|v0); sll 逻辑左移(shift left logic): sll v1,v1,0x18 v1 = v1 << 0x18; srl 逻辑右移: srl v1,v1,0x18 v1 = v1 >> 0x18; sra 算术右移(shift right arithmetic) 适用于有符号数,最高位用原第 31位填充(没有算术左移) 杭州迪普科技有限公司
10
基本汇编指令 条件设置 slt,stlu,slti,sltiu, (set if less than) 条件满足将目标寄存器置为1
sltu v0,a0,a1 v0 = a0 < a1 ? 1:0 sle,sleu (set if less or equal) sle v0,t0,t v0 = to <= t1 ? 1:0 seq (set if equal) seq v0,s0, v0 = s0 == 1? 1:0 sgt,sgtu, sge,sgeu (set if great or equal) sgeu t0,s0,s1 t0 = s0 >=s1 ? 1:0 sne (set if not equal) sne s0,v0,v1 s0 = v0 != v1 ? 1:0 杭州迪普科技有限公司
11
基本汇编指令 跳转 j,jr(jump reg) 跳转,一般指函数内部,ra值不变 j label goto label
jr v0 goto vo jal,jalr 跳转到子函数,ra值自动保存为返回地址 jar lable goto label jalr v0 goto vo beq,beqz (branch if equal zero) 比较分支跳转,用于函数内部 beq v0,v1,label if (v0 == v1) goto label blt,bltz,ble,blez,blezl (branch if less than) bltz v0,label if (vo < 0) goto label bne,bnez (branch if not equal) bgt,bgtz,bge,bgez (branch if great than zero) 杭州迪普科技有限公司
12
基本汇编指令 断点和自陷 break 断点 (直接重启)linux中宏BUG() 反汇编后即break
tne 自陷 (trap if not equal)可理解为跟break一样 寄存器传送 move move a0,s a0 =s0 杭州迪普科技有限公司
13
Mips汇编中的一些隐式规则 存取全变变量或者调用子函数时总是使用两条指令 lui s4,0xdfcc /*加载地址的高位*/
addiu v0,s4, /*加载低位的偏移量*/ 在kdb中输入0xdfcc 可以显示出对应的符号(全局变量或者函数名),用来快速找出汇编代码对应的c代码 分支和跳转的下一条指令先于分支和跳转指令本身执行 lui v0,0xdfc /*加载函数名地址高位*/ move a0,s /*对第一个参数赋值*/ addiu v0,v0, /*加载函数名地址低位*/ jalr v /*跳转到子函数*/ move a1,s /*对第二个参数赋值在跳转前执行*/ * bne a1,a0,0xdfc2cdac lw s0,0(s1) /*该指令先于bne执行*/ 杭州迪普科技有限公司
14
Mips汇编中的隐式规则 子函数的返回值约定存入v0, 赋值语句在子函数中 jalr v0 /*调用子函数*/
move a1,s /*参数赋值*/ beqz v0,0xdfc2ce /*这时v0的值已经自动改为子函数的返回值*/ 跳转进子函数时,a0-a4 是前四个参数的值,在调用函数中已经赋好,ra自动改为返回地址(隐式的),没有显式赋值语句 调用函数: 子函数: lui v0,0xcfb addiu sp,sp, -40 move a0,a sw ra,36(sp) /*ra = A,先保存*/ addiu v0,v0, lui v0,0x802c jalr v addiu v0,v0,18544 move a1,a jalr v /*ra = B*/ A: move v0,s addiu a2,a2,-12 B: move a0,s0 lw ra,36(sp) jr ra /*返回A*/ 杭州迪普科技有限公司
15
Mips汇编中的隐式规则 计算结构体数组元素偏移时,数组元素的大小总是采用左移结合加减法完成的,因为乘除效率太低
下面是函数eth_mode_init()计算port_inf结构体大小的汇编代码:v0 = portid; sll s0,v0,0x /*s0 = v0 << 3 = 8v0*/ addu s0,s0,v /*s0 = s0+v0 = 9v0*/ sll s0,s0,0x /*s0 = 8s0 = 72v0*/ subu s0,s0,v /*s0 = s0 –v0 = 71v0*/ sll s0,s0,0x /*s0 = 16s0 = 1136v0*/ subu s0,s0,v /* s0 = s0 –v0 = 1135v0 */ sll s0,s0,0x /*s0 = 8s0 = 9080v0*/ 从上面可以看出,如果反汇编代码对一个变量有一连串的左移与加减运算就可猜测是在存取数组元素,有助于快速找到对应的C代码 杭州迪普科技有限公司
16
常用kdb命令 Kdb: (build_in kernel debugger)内核调试工具,可以查看内存内容,寄存器信息,跟踪调用栈
1> 启动设备到shell命令行输入:sysctl kernel.kdb=1 2> 键入Esc键 3> 键入大写KDB 进入kdb后输入help有帮助列表 下面是一些常用的: 命令 <参数> 功能 id <函数名/函数地址> 反汇编函数 rd <无参数> 显示32个通用寄存器当前值 rm <reg_id> <value> 修改寄存器值 md <全局变量名/地址> 内存读 杭州迪普科技有限公司
17
Kdb常用命令 mm <全局变量名/地址> <value> 内存写
bt <无参数> 打印当前cpu当前进程调用栈 btc <无参数> 打印所有cpu当前进程调用栈,在串口没有 响应时非常有用 cpu <cpu_id> 切换到指定cpu,每个cpu的进程,寄存 器都是 独立的,需要查看其它cpu信息的寄 存器,堆栈,或是调用栈信息时就需要该 命令切换 bp <函数名/地址> 设置断点(注意弱符号) bl <无参数> 列出当前设置的所有断点 bc <断点号> 清除指定断点,bc * :清除所有断点 bd <断点号> 无效指定断点 be <断点号> 使能指定断点 ss <无参数> 单步跟踪 杭州迪普科技有限公司
18
常用kdb命令 slabinfo <无参数>显示块内存使用信息,一般在内存泄露时 查看下是否因为skb没有释放造成的,正 常情况下size = 21788左右,如果达 到7,8万就说明有流程skb没有释放 go <无参数> 退出kdb 杭州迪普科技有限公司
19
使用kdb定位问题的具体方法 最常见的死机原因:访问空指针 oops
<1>CPU 1 Unable to handle kernel paging request at virtual address c, epc == c94fed64 , ra == c94fed14 unaligned Oops[#1]: Cpu 1 $ 0 : c94fe1ac 8f57cc38 $ 4 : c950af e a1 $ 8 : c729c 8f4620c0 8f4620e0 $12 : ffffffff 4b8f fffffff $16 : 8f57cc c $20 : c94f362c 8162d c64 $24 : $28 : e c94fed14 Hi : Lo : epc : c94fed64 dpx_channel_cmd_init_outband+0xb4/0x18c [module_dpx_channel] Tainted: P ra : c94fed14 dpx_channel_cmd_init_outband+0x64/0x18c [module_dpx_channel] Status: 10005c03 KERNEL EXL IE Cause : BadVA : PrId : 000c4403 杭州迪普科技有限公司
20
Oops定位方法 Process insmod (pid: 134, threadinfo=80490000, task=83011000)
Stack : f c64 c9511c9c c94fe188 c94fe178 c9511c9c 8162d c c9511ca8 c950f5e4 f cfb7d80c cfa9bf cffff980 c94f30b4 c94f2f6c 8162fda f b Call Trace: [<c94fed64>] dpx_channel_cmd_init_outband+0xb4/0x18c [module_dpx_channel] [<c94fe188>] init_module+0x5c/0x6c [module_dpx_channel] [< >] sys_init_module+0x188/0x1d08 [< >] stack_done_ra+0x0/0x1c 定位过程 1> 看下第一行,初步了解挂死的直接原因,用处不大,有个概念就行,常见的有 不能处理页请求(一般就是访问空地址),访问不对齐的地址 2> 找到正确的异常受害指令: cache error 就是error epc寄存器,其它情况都是epc,如果cause寄 存器最高位置位受害指令是epc+4,这时的epc是一条跳转指令) 反汇编异常受害指令附近代码,找到对应c代码 [0]kdb> id dpx_channel_cmd_init_outband 0xc94fecb0 dpx_channel_cmd_init_outband: addiu sp,sp,-32 0xc94fecb4 dpx_channel_cmd_init_outband+0x4: sw s1,20(sp) 0xc94fecb8 dpx_channel_cmd_init_outband+0x8: sw s0,16(sp) 杭州迪普科技有限公司
21
Oops定位方法 0xc94fecbc dpx_channel_cmd_init_outband+0xc: sw ra,24(sp)
0xc94fecc0 dpx_channel_cmd_init_outband+0x10: lui v0,0x8044 0xc94fecc4 dpx_channel_cmd_init_outband+0x14: lw a0,-21188(v0) 0xc94fecc8 dpx_channel_cmd_init_outband+0x18: lui v0,0x801b 0xc94feccc dpx_channel_cmd_init_outband+0x1c: addiu v0,v0,-7028 0xc94fecd0 dpx_channel_cmd_init_outband+0x20: jalr v0 0xc94fecd4 dpx_channel_cmd_init_outband+0x24: li a1,208 0xc94fecd8 dpx_channel_cmd_init_outband+0x28: move s0,v0 0xc94fecdc dpx_channel_cmd_init_outband+0x2c: move a0,v0 0xc94fece0 dpx_channel_cmd_init_outband+0x30: lui v0,0x8036 0xc94fece4 dpx_channel_cmd_init_outband+0x34: move a1,zero 0xc94fece8 dpx_channel_cmd_init_outband+0x38: li a2,84 0xc94fecec dpx_channel_cmd_init_outband+0x3c: beqz s0,0xc94fedfc dpx_channel_cmd_init_outband+0x14c [0]kdb> 0xc94fecf0 dpx_channel_cmd_init_outband+0x40: addiu s1,v0,3208 0xc94fecf4 dpx_channel_cmd_init_outband+0x44: lui v0,0x802c 0xc94fecf8 dpx_channel_cmd_init_outband+0x48: addiu v0,v0,29120 0xc94fecfc dpx_channel_cmd_init_outband+0x4c: jalr v0 0xc94fed00 dpx_channel_cmd_init_outband+0x50: nop 0xc94fed04 dpx_channel_cmd_init_outband+0x54: lui v0,0xcfc 打印出来帮助定位 0xc94fed08 dpx_channel_cmd_init_outband+0x58: addiu v0,v0,-32696 杭州迪普科技有限公司
22
Oops定位方法 0xc94fed0c dpx_channel_cmd_init_outband+0x5c: jalr v 调用dpx_channel_info_get_outband() 0xc94fed10 dpx_channel_cmd_init_outband+0x60: nop 0xc94fed14 dpx_channel_cmd_init_outband+0x64: lui v1,0xc951 0xc94fed18 dpx_channel_cmd_init_outband+0x68: sw v0,36(s0) 这时v0是函数返回值 0xc94fed1c dpx_channel_cmd_init_outband+0x6c: addiu v1,v1,-20600 0xc94fed20 dpx_channel_cmd_init_outband+0x70: move a1,s0 0xc94fed24 dpx_channel_cmd_init_outband+0x74: lbu at,0(v1) 0xc94fed28 dpx_channel_cmd_init_outband+0x78: addiu v1,v1,1 0xc94fed2c dpx_channel_cmd_init_outband+0x7c: sb at,0(a1) [0]kdb> 0xc94fed30 dpx_channel_cmd_init_outband+0x80: bnez at,0xc94fed24 dpx_channel_cmd_init_outband+0x74 0xc94fed34 dpx_channel_cmd_init_outband+0x84: addiu a1,a1,1 0xc94fed38 dpx_channel_cmd_init_outband+0x88: lui a0,0xc951 0xc94fed3c dpx_channel_cmd_init_outband+0x8c: addiu a0,a0,-20596 0xc94fed40 dpx_channel_cmd_init_outband+0x90: addiu v1,s0,16 0xc94fed44 dpx_channel_cmd_init_outband+0x94: lbu at,0(a0) 0xc94fed48 dpx_channel_cmd_init_outband+0x98: addiu a0,a0,1 0xc94fed4c dpx_channel_cmd_init_outband+0x9c: sb at,0(v1) 0xc94fed50 dpx_channel_cmd_init_outband+0xa0: bnez at,0xc94fed44 dpx_channel_cmd_init_outband+0x94 0xc94fed54 dpx_channel_cmd_init_outband+0xa4: addiu v1,v1,1 0xc94fed58 dpx_channel_cmd_init_outband+0xa8: lw a1,4(v0) 0xc94fed5c dpx_channel_cmd_init_outband+0xac: lui v0,0xc950 0xc94fed60 dpx_channel_cmd_init_outband+0xb0: addiu v0,v0,-7764 0xc94fed64 dpx_channel_cmd_init_outband+0xb4: lw v1,28(a1) epc 0xc94fed68 dpx_channel_cmd_init_outband+0xb8: sw v0,68(s0) 0xc94fed6c dpx_channel_cmd_init_outband+0xbc: lui v0,0xc950 杭州迪普科技有限公司
23
Oops定位方法 3> EPC所在指令为lw v1,28(a1),查看a1的值,注意a1的值最好从挂死时打印信息来看,如果要从rd中读,先得从打印信息中获取挂死的cpu_id,然后使用kdb命令cpu <cpu_id>将当前kdb切换到死机所在cpu的kdb才能正确读取 4> 查看发现a1 = 0,挂死时打印信息中的第一行unable handle page address c就是a1+28 = 28,这时已经确认挂死是因为a1 =0,访问了空指针导致的 5> 向上追溯a1的来源,结合lui v0,0xc950,addiu v0,v0,-7764这种指令将对应符号打印出来,快速定位反汇编对应的c代码 [0]kdb> 0xc 0xc = 0xc94fe1ac ([module_dpx_channel]dpx_cmd_state_outband) [0]kdb> 0xcfc 0xcfc50000 = 0xcfc48048 ([module_cpu_na]dpx_channel_info_get_outband) 根据dpx_cmd_state_outband定位c代码,可以猜测lw v1,28(a1)是pstcmd->dev_state = dpx_cmd_state_outband附近代码,查看c代码发现pstcmd->dev_state前面这 句,pstcmd->ifindex = channel_info->pstpost_info->ifindex, ifindex在port_info中的偏移 就是28,而追溯a1的来源时,找到这句lw a1,4(v0),port_info在channel_info中的 偏移又刚好是4,再向上追溯v0的来源时会发现v0就是dpx_channel_info_get_outband 的返回值,这时可以确定死机是因为从channel_info中取出的port_info指针为空 杭州迪普科技有限公司
24
Oops定位方法 6> 看c代码可知channel_info是全局变量g_drv_channel_info_outband指针,在kdb中读出 g_drv_channel_info_outband 的值发现port_inf确实为0 [0]kdb> md g_drv_channel_info_outband 0xcfdc 0xcfdc0060-0xcfdc00bf zero suppressed 0xcfdc00c 7> 到此kdb的任务基本完成,回到c代码,追查port_info赋值的地方,很快就能找到错误的原因(后面的过程跟业务流程相关,在这里就不多说了) 快速定位反汇编对应c语句的一些技巧: 1> 在kdb中打印epc附近利用lui指令获取的符号(全局变量地址或函数名) 2> 结构体偏移,如这里的lw v1,28(a1)中的28, lw a1,4(v0)中的4,特别是比较大的偏移,如skb->data,偏移是380多,很少有结构体这么大,这样的偏移就非常有助于定位 3> mips汇编隐式规则中提到的计算数组大小的指令 杭州迪普科技有限公司
25
死锁问题定位方法 现象:串口不响应或者是数据面cpu不收包 分析步骤: 1> 进入kdb通过btc 查看各cpu当前在运行什么进程
btc: cpu status: Currently on cpu 0 Available cpus: 0-7 Stack traceback for pid 1121 0x8e3a R 0x8e3a81b0 *umc SP PC Function 0x8e3ff890 0x803806d4 _spin_trylock_bh+0xe0 Stack traceback for pid 0 0x R 0x841499b0 swapper 0x8416ffa0 0x80117da4 phoenix_wait+0x4 0x8416ffa0 0x8011a190 cpu_idle+0x50 Stack traceback for pid 154 0x842a R 0x842a91b0 datatask_2 0x8e7a3f38 0xdfc1a448 [module_cpu_na]drv_skb_que_proc+0x9c Stack traceback for pid 155 0x8e647c R 0x8e647db0 datatask_3 0x8e7a5f38 0xdfc1a440 [module_cpu_na]drv_skb_que_proc+0x94 杭州迪普科技有限公司
26
死锁问题定位方法 2> 死锁问题的关键就是推导调用栈,只要找出是哪个函数调用的加锁函数, 问题基本就解决了
2> 死锁问题的关键就是推导调用栈,只要找出是哪个函数调用的加锁函数, 问题基本就解决了 3> rd 打印出当前进程各寄存器值,反汇编ra对应的函数 [0]kdb> rd zero = 0x at = 0x v0 = 0x v1 = 0x803801b0 a0 = 0xdfcb8aa a1 = 0xc a2 = 0xdfcb8a a3 = 0x665e5705 t0 = 0x8e3ff8a t1 = 0x8e3ff8a0 t2 = 0x t3 = 0x t4 = 0xdfa9df t5 = 0xdfa9dfa0 t6 = 0xdfa7fa t7 = 0x s0 = 0xdfcb8aa s1 = 0xc5b05118 s2 = 0xc5b s3 = 0x5050c200 s4 = 0x5050c s5 = 0x8e3ff918 s6 = 0x s7 = 0x t8 = 0x t9 = 0xdf6b8680 k0 = 0x82e k1 = 0x82e10000 gp = 0x8e3fe sp = 0x8e3ff890 s8 = 0xdfcc ra = 0x803801c4 hi = 0x lo = 0x pc = 0x803806d sr = 0x10005c03 cause = 0x badva = 0x82e10000 杭州迪普科技有限公司
27
死锁问题定位方法 4 >将ra所在的函数反汇编找到调用_spin_trylock_bh代码,确认找到的ra是正确的
[0]kdb> id 0x803801c4 0x803801c4 _spin_lock_bh+0x14: ll v0,0(s0) 0x803801c8 _spin_lock_bh+0x18: bnez v0,0x803806d0 _spin_trylock_bh+0xdc 0x803801cc _spin_lock_bh+0x1c: li v0,1 0x803801d0 _spin_lock_bh+0x20: sc v0,0(s0) 0x803801d4 _spin_lock_bh+0x24: beqz v0,0x803806d0 _spin_trylock_bh+0xdc 0x803801d8 _spin_lock_bh+0x28: nop 0x803801dc _spin_lock_bh+0x2c: sync 0x803801e0 _spin_lock_bh+0x30: lw ra,20(sp) 0x803801e4 _spin_lock_bh+0x34: lw s0,16(sp) 0x803801e8 _spin_lock_bh+0x38: jr ra 0x803801ec _spin_lock_bh+0x3c: addiu sp,sp,24 这里要注意的是_spin_lock_bh调用__spin_trylock_bh时是直接跳转到_spin_trylock_bh+0xdc的,不是像一般的函数从__spin_trylock_bh函数第一行开始执行,也就是说__spin_trylock_bh没有执行压栈操作,当前sp值是函数_spin_lock_bh的栈 杭州迪普科技有限公司
28
死锁问题定位方法 5 > 反汇编_spin_lock_bh函数头部(ra赋值的地方总在函数头部),找到_spin_lock_bh函数的ra,也就是谁调用了_spin_lock_bh [0]kdb> id _spin_lock_bh 0x803801b0 _spin_lock_bh: addiu sp,sp,-24 0x803801b4 _spin_lock_bh+0x4: sw s0,16(sp) 0x803801b8 _spin_lock_bh+0x8: sw ra,20(sp) 0x803801bc _spin_lock_bh+0xc: jal 0x8014bb10 local_bh_disable 0x803801c0 _spin_lock_bh+0x10: move s0,a0 0x803801c4 _spin_lock_bh+0x14: ll v0,0(s0) 0x803801c8 _spin_lock_bh+0x18: bnez v0,0x803806d0 _spin_trylock_bh+0xdc 0x803801cc _spin_lock_bh+0x1c: li v0,1 0x803801d0 _spin_lock_bh+0x20: sc v0,0(s0) 0x803801d4 _spin_lock_bh+0x24: beqz v0,0x803806d0 _spin_trylock_bh+0xdc 0x803801d8 _spin_lock_bh+0x28: nop 0x803801dc _spin_lock_bh+0x2c: sync 0x803801e0 _spin_lock_bh+0x30: lw ra,20(sp) 0x803801e4 _spin_lock_bh+0x34: lw s0,16(sp) 0x803801e8 _spin_lock_bh+0x38: jr ra 0x803801ec _spin_lock_bh+0x3c: addiu sp,sp,24 杭州迪普科技有限公司
29
死锁问题定位方法 6 > 理清sw ra,20(sp)中的sp与rd读出的sp关系,:当前进程运行至_spin_lock_bh+0x18跳转到__spin_trylock_bh+0xdc后就挂死,从反汇编代码中可看出 addiu sp,sp,-24这条指令之后sp的值就没再改变,也就是说rd中的sp值就是sw ra,20(sp)中的sp值 【ps:由于这次死锁中sw ra,20(sp)与跳转到__spin_trylock_bh+0xdc指令离的很近,一眼就能看出sp的值没变,看下汇编也不费事,在实战时是没必要再看汇编的。因为sp的值只会在函数调用时改变,一般都在调用函数的第一条指令就把sp值压到该函数能用到的最低点(见 addiu sp,sp,-24 ),在函数内部sp的值不会变,__spin_trylock_bh这个函数比较特殊,调用它时都是直接跳转到+0xdc的位置,没有执行函数开头的压栈,所以rd出的sp就是rd出的ra函数的栈】 7> md sp+20 读出ra的值 [0]kdb> md 0x8e3ff890 0x8e3ff890 7f f ed 0x8e3ff8a0 dfcc0000 dfc1f728 82e e ( 0x8e3ff8b0 82e e e e f^W..... 0x8e3ff8c e3ff918 8e5b ?...[v. 0x8e3ff8d c34a e3ff PP.J.....?.. 0x8e3ff8e0 5050c302 dfc2009c 82e e PP 0x8e3ff8f0 82e e c200 8e3ff9e PP...?.. 0x8e3ff c200 df6b4b04 82e e PP...kK 杭州迪普科技有限公司
30
死锁问题定位方法 8> 反汇编dfc1f728,找到调用__spin_lock_bh的函数
[0]kdb> id 0xdfc1f728 0xdfc1f728 drv_ff_tab_add+0xe4: lui v0,0xdfcc 0xdfc1f72c drv_ff_tab_add+0xe8: addiu a3,v0,-30088 0xdfc1f730 drv_ff_tab_add+0xec: move a0,zero 0xdfc1f734 drv_ff_tab_add+0xf0: j 0xdfc1f744 drv_ff_tab_add+0x100 0xdfc1f738 drv_ff_tab_add+0xf4: lui a2,0x3 0xdfc1f73c drv_ff_tab_add+0xf8: beqz a1,0xdfc1f7c0 drv_ff_tab_add+0x17c 0xdfc1f740 drv_ff_tab_add+0xfc: addiu s1,s1,8 将0xdfc1f728前面的代码反汇编,找到调用__spin_lock_bh的指令,确认找到的ra是正确的 0xdfc1f714 drv_ff_tab_add+0xd0: lui v1,0x8038 0xdfc1f718 drv_ff_tab_add+0xd4: addu v0,v0,s3 0xdfc1f71c drv_ff_tab_add+0xd8: addiu v1,v1,432 0xdfc1f720 drv_ff_tab_add+0xdc: jalr v1 0xdfc1f724 drv_ff_tab_add+0xe0: addiu s4,v0,252 [0]kdb> 0x 显示地址对应的符号 0x = 0x803801b0 (_spin_lock_bh) 杭州迪普科技有限公司
31
死锁问题定位方法 9> kdb的定位到此基本结束,回到si,将drv_ff_tab_add函数所有加锁,解锁,return高亮,很快就能发现那个流程没有解锁 死锁问题小结 1> 调用__spin_trylock_bh没有压栈操作,sp值是ra函数的栈 2> 推栈就是不断通过sp回溯ra的过程,具体步骤: a > 反汇编rd读出的ra值找到第一层调用函数 b > 确认rd中的sp是什么函数的栈(只要ra所在函数跳转到挂死时所在函数是正常的函数调用,不是像调用__spin_trylock_bh直接跳到函数中间,sp就是挂死所在函数的栈) c > 反汇编第一层调用函数头部(在这里就是_spin_lock_bh), 因为保存上层调用函数的ra值都在前几条指令 d > 根据ra赋值语句,从堆栈中读出(md sp+20),反汇编第二层ra找到第二层调用函数 e > 通过第一层调用函数第一条指令,即压栈操作(addiu sp,sp,-24),计算第二层调用函数的栈, _spin_lock_bh函数的sp是drv_ff_tab_add函数-24得到的,所以drv_ff_tab_add函数的栈就是sp+24,这个地方有点绕,但非常关键,好好理解下,要明白rd中的sp值永远是当前指令所在函数的栈(__spin_trylock_bh比较特殊) f > 反汇编第二层调用函数头部,找到第三层ra赋值语句 [0]kdb> id drv_ff_tab_add 0xdfc1f644 drv_ff_tab_add: addiu sp,sp,-64 0xdfc1f664 drv_ff_tab_add+0x20: sw s1,28(sp) 0xdfc1f668 drv_ff_tab_add+0x24: sw ra,60(sp) 杭州迪普科技有限公司
32
死锁问题定位方法 g> 根据ra赋值语句,从堆栈中找出第三层ra(md sp+24+60)值,反汇编
h> 重复步骤e,f,g可一直推到根函数 3> 新修改代码有return语句时,将整个函数浏览一遍,如前面有加锁操作,一定要解锁 4> 加锁时注意软中断(定时器,tasklet等),如果软中断也有可能调用,注意关中断(如在网卡驱动发包函数中) 杭州迪普科技有限公司
33
踩内存问题定位方法 踩内存是指由于其它流程引用指针时没有初始化,或是memcpy越界等导致一段正常的程序内存被写,设备挂死在这段正常的程序
怎么确认是踩内存 使用kdb定位到c语句后发现挂死时变量的值是一个完全不可能的值或挂死的函数是已经上线运行很长时间理论上不可能出问题的代码,这时可以出一个调试版本:在挂死变量前后各加一个用于观察的字段,初始化为一个特殊值,如0xabcd1234,代码中不再对这个字段做任何操作,问题复现时,如果观察字段的值被改,这时就可确认是被踩内存了 定位踩内存问题的一些方法: 1> 将挂死变量附近的内容全部打印出来,观察这一段内存的值有没有什么特殊性,可不可以联系上什么流程,因为写内存经常是将一串都写了。 2> 将挂死内存前面的内存地址对应的符号一个个的打印出来,如果能找到某个地址对应的是一个全局变量,很可能就是对这个全局变量的操作导致写内存的,回到c代码搜索该全局变量,重点关注memcpy语句 2> 比较版本差异,如果使用同样的环境上个版本是正常的,就说明是新版本引入的,直接比对代码,重点关注指针赋值,memcpy的地方 3> 寻找复现规律,找到之后,根据业务流程,逐个函数设置断点,每断住一个函数就从kdb中查看观察字段值是否还等于0xabcd1234,找到踩内存的函数,定位到函数这一层看c代码应该就能解决了 杭州迪普科技有限公司
34
异常信息分析方法 异常信息格式: 0 begin Exception: bus/cache error 死机原因:cache error Bridge: Phys Addr = 0x , Device_AERR = 0x Bridge: The devices reporting AERR are: cpu 0 CPU: (XLR specific) Cache Error log = 0x , Phy Addr = 0x CPU: epc = 0x802c561c, errorepc = 0x802c55f8, cacheerr = 0x 异常受害指令Jiffies: Occur time: Wed Jan 21 02:00:17 2009( s) 异常发生时间 Cpu 0 $ 0 : 89ad5022 89ad500e $ 4 : 89ad50b4 8ffffffa ffe7ffff $ 8 : $12 : a bfc5f600 $16 : 802c54a0 000005ea 8f92fe40 89ad500e $20 : 000005c8 fc3a2b58 e 8c3a4e40 $24 : 07eede40 $28 : 8f830000 8f831d60 000005dc d2b5b6e0 Hi : Lo : epc : 802c561c Tainted: P ra : d2b5b6e0 Status: 1000dc05 KERNEL ERL IE Cause : PrId : 000c0b04 杭州迪普科技有限公司
35
异常信息分析方法 Process <NULL> (pid: 0, threadinfo=813dc000, task= ) Stack : 8e3b8060 c 8fffff68 000005c8 000005c8 03c5ceb8 d30ab564 8c3a4e40 fffffffa 8f831ed8 d30a1bc4 d30e28bc 8c3a317c 000000f4 d2b5b994 1000dc03 0026db38 8f831ed8 d2b548f8 c 0000adc3 8cf73060 1000dc00 8014be58 8014be58 8f831ea0 b ... Code: ac890004 ac8a0008 ac8b000c <14d8fff1> 10c00018 30d80003 a (sizeof(unsigned long) * 128) bytes trace back from stack: 512字节堆栈信息 8e3b8060 c 8fffff68 000005c8 000005c8 03c5ceb8 d30ab564 8c3a4e40 fffffffa 8f831ed8 d30a1bc4 d30e28bc 8c3a317c 000000f4 d2b5b994 1000dc03 0026db38 8f831ed8 d2b548f8 c 0000adc3 8cf73060 1000dc00 8014be58 8014be58 8f831ea0 b d2b4eef4 bfc5f600 0dfb2882 bc400000 0dfb2860 81359a88 8135c018 5000dc00 8ca3366f 8ca33082 000005f6 8f831e1d 8f831ed8 d30e28bc fffffffa e d2b54bbc d2be8dac 81384b30 0d10d860 8f831ed8 d2b566b4 8f830000 8f831ec0 b d2b4d9cc 8c3a4e40 fffffffa e 8c3a3060 d2b4b9a8 81384b30 1000dc01 8d2c1060 8f831e1d fffffffa 8c3a3060 8c3a308e 8c3a3060 8c3a308e 8c3a309c d2be8dac 00004b30 0d09b860 b d2e40000 d2b4eef4 d2defd3c d2b848a4 d2be8dac d2e40000 d2b4d9a0 ffffffff d2b4eef4 d2defd3c 90c13200 0c3a3060 ffffffff 81384b30 0 end 杭州迪普科技有限公司
36
异常信息分析方法 所谓异常信息是相对于kdb信息而言的,设备进kdb时,所有寄存器,堆栈,全局变量,内存值都是死机当时的值,我们称之为现场,但设备上线运行时都必须把kdb关掉,发生异常后设备会自动重启,不至于一发生异常就无法恢复,减小对现网的影响,这时用于定位的信息就只有发生异常时,cpu记录的异常信息,设备重启后通过exceptions disp total查看。异常信息的格式跟进入kdb时信息差不多,但只有打印出来的寄存器跟堆栈信息是发生异常时刻的值,内存,全局变量的值都是重启之后正常值,不能用于定位。 异常相关的命令: 查看异常信息:exceptions disp total (最前面的是最新的) 清除异常信息:excepiotns clear 异常问题定位方法: 1> 跟kdb差不多,最重要的还是通过EPC或是ERROR EPC,反汇编找到挂死的c语句 2> 因为异常信息中只有寄存器跟打印出的堆栈中的512字节信息可用,所以推栈跟找到s0—s7这些寄存器对应的c变量就显得更加重要 杭州迪普科技有限公司
37
进程栈演示 ADrv_ff_tab_add-Spin_lock--spin_trylock sp =sp- 24 rd_sp
杭州迪普科技有限公司
38
设备模块加载顺序 相关脚本\release\conplat\mips\root_fs\etc\rc.d rc.sysinit 内核态加载脚本 rc.conplat 用户态脚本 release\conplat\mips\root_fs\etc\startup_order 杭州迪普科技有限公司
Similar presentations