38
Beyond Your Imagination MIPS 汇汇汇 KDB 汇汇 czk

MIPS 汇编及 KDB 入门

  • Upload
    shanta

  • View
    246

  • Download
    1

Embed Size (px)

DESCRIPTION

MIPS 汇编及 KDB 入门. czk. 目录. 通用寄存器及常用 CP0 寄存器功能介绍 基本汇编指令 Mips 汇编中的一些隐式规则 常用 kdb 命令 使用 kdb 定位问题的具体方法. 通用寄存器. 汇编语言以行为单位,由指令和寄存器组成, mips 汇编沿袭 c 语言赋值语句风格,目标寄存器在左,源操作数在右 - PowerPoint PPT Presentation

Citation preview

Beyond Your Imagination

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 : 00000000 5000dc00 00000005 8cdb388e

<0>$ 4 : dfd51400 00000000 8ffb3d58 8ffb3e28<0>$ 8 : da84f394 da84f374 ffffff00 00000000<0>$12 : 00000000 8c4a9920 bc400000 bfc04800<0>$16 : 00000000 0000000e 00000014 8cdb3800<0>$20 : 00000001 00000000 00000005 00000050<0>$24 : 00000073 dfcc0000<0>$28 : 8ffb2000 8ffb3d48 db0589f4 dfcc0054

编号 别名 作用$0 zero 永远返回 0

$1 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 保留给异常处理程序使用 $28 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 = 0x12340000; 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,t1 v0 = to <= t1 ? 1:0 seq (set if equal) seq v0,s0,1 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,s0 a0 =s0

13杭州迪普科技有限公司

Mips汇编中的一些隐式规则 存取全变变量或者调用子函数时总是使用两条指令

lui s4,0xdfcc /* 加载地址的高位 */ addiu v0,s4,17412 /* 加载低位的偏移量 */ 在 kdb 中输入 0xdfcc0000+17142 可以显示出对应的符号(全局变量或者函数名),用来快速找出汇编代码对应的 c 代码

分支和跳转的下一条指令先于分支和跳转指令本身执行lui v0,0xdfc3 /* 加载函数名地址高位 */move a0,s0 /* 对第一个参数赋值 */

addiu v0,v0,-17932 /* 加载函数名地址低位 */ jalr v0 /*跳转到子函数 */

move a1,s1 /* 对第二个参数赋值在跳转前执行 */ * bne a1,a0,0xdfc2cdac lw s0,0(s1) /* 该指令先于 bne执行 */

14杭州迪普科技有限公司

Mips汇编中的隐式规则 子函数的返回值约定存入 v0, 赋值语句在子函数中

jalr v0 /* 调用子函数 */ move a1,s1 /* 参数赋值 */ beqz v0,0xdfc2ce04 /* 这时 v0 的值已经自动改为子函数的返回值 *

/ 跳转进子函数时, a0-a4 是前四个参数的值,在调用函数中已经赋好, ra

自动改为返回地址(隐式的),没有显式赋值语句调用函数: 子函数:lui v0,0xcfb5 addiu sp,sp, -40

move a0,a1 sw ra,36(sp) /*ra = A, 先保存 */ addiu v0,v0,24820 lui v0,0x802c jalr v0 addiu v0,v0,18544 move a1,a2 jalr v0 /*ra = B*/A: move v0,s2 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,0x3 /*s0 = v0 << 3 = 8v0*/ addu s0,s0,v0 /*s0 = s0+v0 = 9v0*/ sll s0,s0,0x3 /*s0 = 8s0 = 72v0*/ subu s0,s0,v0 /*s0 = s0 –v0 = 71v0*/ sll s0,s0,0x4 /*s0 = 16s0 = 1136v0*/ subu s0,s0,v0 /* s0 = s0 –v0 = 1135v0 */ sll s0,s0,0x3 /*s0 = 8s0 = 9080v0*/ 从上面可以看出,如果反汇编代码对一个变量有一连串的左移与加减运算就可猜测是在存取数组元素,有助于快速找到对应的 C 代码

16杭州迪普科技有限公司

常用 kdb命令 Kdb: (build_in kernel debugger) 内核调试工具,可以查看内存内容,寄存器信息,跟踪调用栈

进入 kdb 方法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-2048 = 21788 左右,如果达 到 7,8万就说明有流程 skb 没有释放go <无参数 > 退出 kdb

19杭州迪普科技有限公司

使用 kdb定位问题的具体方法 最常见的死机原因:访问空指针 oops <1>CPU 1 Unable to handle kernel paging request at virtual address 0000001c, e

pc == c94fed64 , ra == c94fed14 unaligned Oops[#1]: Cpu 1 $ 0 : 00000000 00000000 c94fe1ac 8f57cc38 $ 4 : c950af94 00000000 00000000 0000001e a1 $ 8 : 00000014 802c729c 8f4620c0 8f4620e0 $12 : ffffffff 4b8f2939 00000000 7fffffff $16 : 8f57cc20 80360c88 80440000 80440000 $20 : c94f362c 8162d648 00000023 80171c64 $24 : 00000000 80380000 $28 : 80490000 80491e10 00000000 c94fed14 Hi : 00000000 Lo : 00000000 epc : c94fed64 dpx_channel_cmd_init_outband+0xb4/0x18c [module_dpx_cha

nnel] Tainted: P ra : c94fed14 dpx_channel_cmd_init_outband+0x64/0x18c [module_dpx_cha

nnel] Status: 10005c03 KERNEL EXL IE Cause : 10808008 BadVA : 00000000 PrId : 000c4403

20杭州迪普科技有限公司

Oops定位方法Process insmod (pid: 134, threadinfo=80490000, task=83011000)Stack : 76657468 305f3331 00000023 80171c64 c9511c9c 80440000 c94fe188 c94fe178

c9511c9c 8162d400 80440000 80440000 80172910 8017427c c9511ca8 c950f5e4 00000000 80491f30 00000000 00000000 00000214 00000038 cfb7d80c 00000000 cfa9bf64 00000001 cffff980 c94f30b4 c94f2f6c 8162fda0 00000000 0000000f 0000000b 00000016 00000000 00000012 00000000 00000000 00000000 00000000 ...

Call Trace:[<c94fed64>] dpx_channel_cmd_init_outband+0xb4/0x18c [module_dpx_channel][<c94fe188>] init_module+0x5c/0x6c [module_dpx_channel][<80172910>] sys_init_module+0x188/0x1d08[<80123714>] 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_cha

nnel_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,0xcfc5 打印出来帮助定位

0xc94fed08 dpx_channel_cmd_init_outband+0x58: addiu v0,v0,-32696

22杭州迪普科技有限公司

Oops定位方法 0xc94fed0c dpx_channel_cmd_init_outband+0x5c: jalr v0 调用 dpx_channel_info_get_out

band() 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_outba

nd+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_outba

nd+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杭州迪普科技有限公司

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 0000001c 就是 a1+28 = 28 ,这时已经确认挂死是因为 a1 =0,访问了空指针导致的5> 向上追溯 a1 的来源,结合 lui v0,0xc950 , addiu v0,v0,-7764 这种指令将对应符号打印出来,快速定位反汇编对应的 c 代码

[0]kdb> 0xc9500000-77640xc9500000 = 0xc94fe1ac ([module_dpx_channel]dpx_cmd_state_outband)[0]kdb> 0xcfc50000-326960xcfc50000 = 0xcfc48048 ([module_cpu_na]dpx_channel_info_get_outban

d) 根据 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 指针为空

Oops定位方法

24杭州迪普科技有限公司

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_outband0xcfdc0050 00000000 00000000 00000000 00000000 ................0xcfdc0060-0xcfdc00bf zero suppressed0xcfdc00c0 00000000 00000000 00000000 00000000 ................

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 汇编隐式规则中提到的计算数组大小的指令

Oops定位方法

25杭州迪普科技有限公司

死锁问题定位方法 现象:串口不响应或者是数据面 cpu 不收包 分析步骤:

1> 进入 kdb 通过 btc 查看各 cpu 当前在运行什么进程[0]kdb> btc

btc: cpu status: Currently on cpu 0Available cpus: 0-7Stack traceback for pid 11210x8e3a8000 1121 1 1 0 R 0x8e3a81b0 *umcSP PC Function0x8e3ff890 0x803806d4 _spin_trylock_bh+0xe0Stack traceback for pid 00x84149800 0 1 1 1 R 0x841499b0 swapperSP PC Function0x8416ffa0 0x80117da4 phoenix_wait+0x40x8416ffa0 0x8011a190 cpu_idle+0x50Stack traceback for pid 1540x842a9000 154 1 1 2 R 0x842a91b0 datatask_2SP PC Function0x8e7a3f38 0xdfc1a448 [module_cpu_na]drv_skb_que_proc+0x9cStack traceback for pid 1550x8e647c00 155 1 1 3 R 0x8e647db0 datatask_3SP PC Function0x8e7a5f38 0xdfc1a440 [module_cpu_na]drv_skb_que_proc+0x94

26杭州迪普科技有限公司

死锁问题定位方法2> 死锁问题的关键就是推导调用栈,只要找出是哪个函数调用的加锁函数,

问题基本就解决了 3> rd 打印出当前进程各寄存器值,反汇编 ra 对应的函数 [0]kdb> rd zero = 0x00000000 at = 0x00000000 v0 = 0x00000001 v1 = 0x803801b0 a0 = 0xdfcb8aa8 a1 = 0xc0000028 a2 = 0xdfcb8a78 a3 = 0x665e5705 t0 = 0x8e3ff8a0 t1 = 0x8e3ff8a0 t2 = 0x00000000 t3 = 0x00000000 t4 = 0xdfa9df80 t5 = 0xdfa9dfa0 t6 = 0xdfa7fa68 t7 = 0x00080180 s0 = 0xdfcb8aa8 s1 = 0xc5b05118 s2 = 0xc5b05118 s3 = 0x5050c200 s4 = 0x5050c344 s5 = 0x8e3ff918 s6 = 0x00000000 s7 = 0x00000003 t8 = 0x00000000 t9 = 0xdf6b8680 k0 = 0x82e10000 k1 = 0x82e10000 gp = 0x8e3fe000 sp = 0x8e3ff890 s8 = 0xdfcc0000 ra = 0x803801c4 hi = 0x00000000 lo = 0x00000000 pc = 0x803806d4 sr = 0x10005c03

cause = 0x10808000 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_b

h+0xdc0x803801cc _spin_lock_bh+0x1c: li v0,10x803801d0 _spin_lock_bh+0x20: sc v0,0(s0)0x803801d4 _spin_lock_bh+0x24: beqz v0,0x803806d0 _spin_trylock_b

h+0xdc0x803801d8 _spin_lock_bh+0x28: nop0x803801dc _spin_lock_bh+0x2c: sync0x803801e0 _spin_lock_bh+0x30: lw ra,20(sp)0x803801e4 _spin_lock_bh+0x34: lw s0,16(sp)0x803801e8 _spin_lock_bh+0x38: jr ra0x803801ec _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_bh0x803801b0 _spin_lock_bh: addiu sp,sp,-240x803801b4 _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_disable0x803801c0 _spin_lock_bh+0x10: move s0,a00x803801c4 _spin_lock_bh+0x14: ll v0,0(s0)0x803801c8 _spin_lock_bh+0x18: bnez v0,0x803806d0 _spin_trylock_bh+0xdc0x803801cc _spin_lock_bh+0x1c: li v0,10x803801d0 _spin_lock_bh+0x20: sc v0,0(s0)0x803801d4 _spin_lock_bh+0x24: beqz v0,0x803806d0 _spin_trylock_bh+0xdc0x803801d8 _spin_lock_bh+0x28: nop0x803801dc _spin_lock_bh+0x2c: sync0x803801e0 _spin_lock_bh+0x30: lw ra,20(sp)0x803801e4 _spin_lock_bh+0x34: lw s0,16(sp)0x803801e8 _spin_lock_bh+0x38: jr ra0x803801ec _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 7f000001 7f000001 00000011 003595ed .............5..0x8e3ff8a0 dfcc0000 dfc1f728 82e10000 82e10000 .......(........0x8e3ff8b0 82e10000 82e10000 665e5705 82e10000 ........f^W.....0x8e3ff8c0 00000003 00000001 8e3ff918 8e5b7600 .........?...[v.0x8e3ff8d0 00000000 5050c34a 00000000 8e3ff918 ....PP.J.....?..0x8e3ff8e0 5050c302 dfc2009c 82e10000 82e10000 PP..............0x8e3ff8f0 82e10000 82e10000 5050c200 8e3ff9e0 ........PP...?..0x8e3ff900 5050c200 df6b4b04 82e10000 82e10000 PP...kK.........

死锁问题定位方法

30杭州迪普科技有限公司

死锁问题定位方法8> 反汇编 dfc1f728 ,找到调用 __spin_lock_bh 的函数[0]kdb> id 0xdfc1f728

0xdfc1f728 drv_ff_tab_add+0xe4: lui v0,0xdfcc0xdfc1f72c drv_ff_tab_add+0xe8: addiu a3,v0,-300880xdfc1f730 drv_ff_tab_add+0xec: move a0,zero0xdfc1f734 drv_ff_tab_add+0xf0: j 0xdfc1f744 drv_ff_tab_add+0x1000xdfc1f738 drv_ff_tab_add+0xf4: lui a2,0x30xdfc1f73c drv_ff_tab_add+0xf8: beqz a1,0xdfc1f7c0 drv_ff_tab_add+0x17c0xdfc1f740 drv_ff_tab_add+0xfc: addiu s1,s1,8

将 0xdfc1f728 前面的代码反汇编,找到调用 __spin_lock_bh 的指令,确认找到的 ra 是正确的0xdfc1f714 drv_ff_tab_add+0xd0: lui v1,0x80380xdfc1f718 drv_ff_tab_add+0xd4: addu v0,v0,s30xdfc1f71c drv_ff_tab_add+0xd8: addiu v1,v1,4320xdfc1f720 drv_ff_tab_add+0xdc: jalr v10xdfc1f724 drv_ff_tab_add+0xe0: addiu s4,v0,2520xdfc1f728 drv_ff_tab_add+0xe4: lui v0,0xdfcc0xdfc1f72c drv_ff_tab_add+0xe8: addiu a3,v0,-30088

[0]kdb> 0x80380000+432 显示地址对应的符号0x80380000 = 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,-640xdfc1f664 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 = 0x0010000000, Device_AERR = 0x00000001 Bridge: The devices reporting AERR are: cpu 0 CPU: (XLR specific) Cache Error log = 0x0000000800000108, Phy Addr = 0x0010000000 CPU: epc = 0x802c561c, errorepc = 0x802c55f8, cacheerr = 0x00000000 异常受害指令 Jiffies:

2920010 Occur time: Wed Jan 21 02:00:17 2009(1232474417s) 异常发生时间 Cpu 0 $ 0 : 00000000 00000000 89ad5022 89ad500e $ 4 : 89ad50b4 8ffffffa 00000526 ffe7ffff $ 8 : 00000000 00000000 00000000 00000000 $12 : 00000000 00000000 a0400000 bfc5f600 $16 : 802c54a0 000005ea 8f92fe40 89ad500e $20 : 000005c8 fc3a2b58 0000000e 8c3a4e40 $24 : 00000006 07eede40 $28 : 8f830000 8f831d60 000005dc d2b5b6e0 Hi : 00000000 Lo : 00266800 epc : 802c561c Tainted: P ra : d2b5b6e0 Status: 1000dc05 KERNEL ERL IE Cause : 80800000 PrId : 000c0b04

35杭州迪普科技有限公司

异常信息分析方法 Process <NULL> (pid: 0, threadinfo=813dc000, task=00000000) Stack : 00010050 00000000 80000000 8e3b8060 0000010c 00000000 8fffff68 000005c8 00000014 000005c8 03c5ceb8 00000000 00000022 d30ab564 8c3a4e40 00000003 fffffffa 8f831ed8 d30a1bc4 d30e28bc 8c3a317c 00000010 000000f4 d2b5b994 1000dc03 00000000 0026db38 00000003 8f831ed8 d2b548f8 0000010c 0000adc3 8cf73060 00000003 1000dc00 00000003 8014be58 8014be58 8f831ea0 0000008b ... Code: ac890004 ac8a0008 ac8b000c <14d8fff1> 24840010 10c00018 30d80003 1306000a 00000000 (sizeof(unsigned long) * 128) bytes trace back from stack: 512 字节堆栈信息 00010050 00000000 80000000 8e3b8060 0000010c 00000000 8fffff68 000005c8 00000014 000005c8 03c5ceb8 00000000 00000022 d30ab564 8c3a4e40 00000003 fffffffa 8f831ed8 d30a1bc4 d30e28bc 8c3a317c 00000010 000000f4 d2b5b994 1000dc03 00000000 0026db38 00000003 8f831ed8 d2b548f8 0000010c 0000adc3 8cf73060 00000003 1000dc00 00000003 8014be58 8014be58 8f831ea0 0000008b d2b4eef4 00000003 bfc5f600 0dfb2882 bc400000 0dfb2860 00000000 80447678 81359a88 8135c018 00000000 5000dc00 00002668 8ca3366f 00000000 00000110 00000003 00000617 00000000 00000070 90461700 8ca33082 000005f6 00000000 8f831e1d 00000000 8f831ed8 d30e28bc fffffffa 0000000e 00000003 d2b54bbc d2be8dac 00000003 81384b30 0d10d860 8f831ed8 d2b566b4 8f830000 8f831ec0 0000008b d2b4d9cc 8c3a4e40 00000003 fffffffa 0000000e 8c3a3060 d2b4b9a8 00000000 81384b30 00000000 00000000 1000dc01 8d2c1060 00000000 00000064 8f831e1d fffffffa 8c3a3060 8c3a308e 8c3a3060 00000073 8c3a308e 8c3a309c d2be8dac 00000020 00004b30 0d09b860 0600011b d2e40000 d2b4eef4 d2defd3c 00000000 d2b848a4 00000072 00000003 d2be8dac 00000003 d2e40000 d2b4d9a0 ffffffff 00000010 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

64

24

A.sp

Drv_ff_tab_add.sp

Spin_lock.sp

栈顶

38杭州迪普科技有限公司

设备模块加载顺序Linux_base

内核核心:进程管理,内存管理等

Conplat_net

driver

Conplat_dp

用户态

协议栈

驱动模块,驱动模块内部的加载顺序

请看rmi/bcm/ppc_drv_st

art

平台内核态模块,dpi等

平台用户态模块,从initlizing dpi

session开始都是

相关脚本 \release\conplat\mips\root_fs\etc\rc.drc.sysinit 内核态加载脚本rc.conplat 用户态脚本release\conplat\mips\root_fs\etc\startup_order