反汇编快速定位内核OOPS

前言

简单的说,出现内核奔溃,我们可以通过串口日志提供的信息;使用gdb反汇编出问题函数所在的文件,然后使用《disassemble /m 函数名》查看具体函数的源码和汇编码;这样能直观的看到源代码对应的汇编码干了什么;再根据串口日志的寄存器信息,查看当时寄存器的值,就能对上当时的参数,和变量的值

示例1

内核oops信息

这是个wifi驱动的空指针问题,问题出在memcmp函数

<1> [101063.001918] Unable to handle kernel NULL pointer dereference at virtual address 0000012a
<1> [101063.010245] pgd = c0004000
<1> [101063.013081] [0000012a] *pgd=00000000
<0> [101063.016863] Internal error: Oops: 17 [#1] PREEMPT THUMB2
<4> [101063.140418] CPU: 0 PID: 146 Comm: ssv6xxx_hci_rx_ Not tainted 3.10.33 #1
<4> [101063.147250] task: c16f9500 ti: c16f0000 task.ti: c16f0000
<4> [101063.152771] PC is at memcmp+0xa/0x1a
<4> [101063.156553] LR is at ssv_start_xmit+0x1dc/0x410 [ssv6x5x]
<4> [101063.162073] pc : [<c00fb848>]    lr : [<bf815309>]    psr: 80000133
sp : c16f1be8  ip : 00000088  fp : 00000000
<4> [101063.173755] r10: bf8a5498  r9 : c10a4440  r8 : c126c8a4
<4> [101063.179123] r7 : c05f4258  r6 : 0000012a  r5 : 00000100  r4 : c16d0f00
<4> [101063.185741] r3 : 00000000  r2 : 00000006  r1 : c05f4258  r0 : 0000012a
<4> [101063.192390] Flags: Nzcv  IRQs on  FIQs on  Mode SVC_32  ISA Thumb  Segment kernel
<4> [101063.199984] Control: 50c53c7d  Table: 01eb4059  DAC: 00000015

反汇编

根据“LR is at ssv_start_xmit+0x1dc/0x410 [ssv6x5x]”,反汇编该函数所在的.o文件看下

./owtoolchain/linux64/bin/arm-openwrt-linux-uclibcgnueabi-gdb ./build_dir/target-arm_cortex-a7+neon-vfpv4_uClibc-1.0.25_eabi/sv6318pre/src/ssv6x5x.o

可以根据0x1dc定位到保存的返回地址

根据“PC is at memcmp+0xa/0x1a”,反汇编该函数所在的.o文件看下

./owtoolchain/linux64/bin/arm-openwrt-linux-uclibcgnueabi-gdb ./marvell/linux/arch/arm/boot/compressed/string.o

就是取出第一参数R3(根据上图,就是cur->mac_addr)的每个字符做复制到第二份参数;但是r3为0 ,是空指针,所以触发异常

(gdb) disassemble /m memcmp
Dump of assembler code for function memcmp:
69      {
   0x000000e4 <+2>:     mov     r3, r0    //将第一参数r0,保存在r3中
   0x000000e6 <+4>:     push    {r4, lr}

70              const unsigned char *su1 = cs, *su2 = ct, *end = su1 + count;
   0x000000e2 <+0>:     add     r2, r0

71              int res = 0;
72
73              while (su1 < end) {
   0x000000e8 <+6>:     cmp     r3, r2
   0x000000ea <+8>:     bcs.n   0xfa <memcmp+24>

74                      res = *su1++ - *su2++;
   0x000000ec <+10>:    ldrb.w  r4, [r3], #1     //取出r3的数据放到R4中,r3地址加1
   0x000000f0 <+14>:    ldrb.w  r0, [r1], #1

75                      if (res)
   0x000000f4 <+18>:    subs    r0, r4, r0
   0x000000f6 <+20>:    beq.n   0xe8 <memcmp+6>
   0x000000f8 <+22>:    b.n     0xfc <memcmp+26>
   0x000000fa <+24>:    movs    r0, #0

76                              break;
77              }
78              return res;
79      }
   0x000000fc <+26>:    pop     {r4, pc}

End of assembler dump.

示例2

内核oops信息

提示:7265705f这个r1寄存器的地址的页表是空的

<1> [36592.660565] Unable to handle kernel paging request at virtual address 7265705f
<1> [36592.668068] pgd = c0004000
<1> [36592.671362] [7265705f] *pgd=00000000
<0> [36592.680085] Internal error: Oops: 5 [#1] PREEMPT THUMB2
<4> [36592.685301] Modules linked in: mfp iptable_nat nf_nat_ipv4 nf_conntrack_netlink nf_conntrack_ipv4 ebtable_nat ebtable_filter ebtable_broute xt_time xt_tcpudp xt_tcpmss xt_statistic xt_state xt_quota xt_pkttype xt_physdev xt_owner xt_nat xt_multiport xt_mark xt_mac xt_limit xt_length xt_id xt_http xt_hl xt_ecn xt_dscp xt_conntrack xt_comment xt_addrtype xt_TCPMSS xt_REDIRECT xt_LOG xt_HL xt_DSCP xt_CT xt_CLASSIFY nfnetlink nf_nat_tftp nf_nat_irc nf_nat_ftp nf_defrag_ipv4 nf_conntrack_tftp nf_conntrack_irc nf_conntrack_ftp iptable_raw iptable_mangle iptable_filter ipt_REJECT ipt_MASQUERADE ipt_ECN ip_tables ebtables ebt_vlan ebt_stp ebt_snat ebt_redirect ebt_pkttype ebt_mark_m ebt_mark ebt_limit ebt_ip6 ebt_ip ebt_dnat ebt_arpreply ebt_arp ebt_among ebt_802_3 crc_ccitt ip6t_NPT ip6t_MASQUERADE ip6table_nat
<4> [36592.756335]  nf_nat_ipv6 nf_nat ip6t_rt ip6t_frag ip6t_hbh ip6t_eui64 ip6t_mh ip6t_ah ip6t_ipv6header ip6t_REJECT ip6table_raw ip6table_mangle ip6table_filter ip6_tables x_tables nf_conntrack_ipv6 nf_conntrack nf_defrag_ipv6 ipcomp6 xfrm6_tunnel xfrm6_mode_tunnel xfrm6_mode_transport xfrm6_mode_beet esp6 ah6 ipcomp xfrm4_tunnel xfrm4_mode_tunnel xfrm4_mode_transport xfrm4_mode_beet esp4 ah4 ipip ip6_tunnel tunnel6 tunnel4 ip_tunnel af_key xfrm_user xfrm_ipcomp xfrm_algo zram zsmalloc lz4_decompress lz4_compress ssv6x5x button_hotplug
<4> [36592.802268] CPU: 0 PID: 143 Comm: ssv6xxx_hci_rx_ Not tainted 3.10.33 #1
<4> [36592.808947] task: c15e5500 ti: c17d4000 task.ti: c17d4000
<4> [36592.814346] PC is at strcmp+0x4/0x20
<4> [36592.817975] LR is at ssv_rx_handle_msg+0x32/0x88 [ssv6x5x]
<4> [36592.823465] pc : [<c00fb676>]    lr : [<bf812e37>]    psr: 20000133
sp : c17d5e88  ip : 00000000  fp : 00000005
<4> [36592.834933] r10: c16b8000  r9 : c108f446  r8 : c1758440
<4> [36592.840149] r7 : 00000000  r6 : c108f4b2  r5 : c1d54000  r4 : 00000156
<4> [36592.846676] r3 : 00000075  r2 : 00000156  r1 : 7265705f  r0 : bf861f2a
<4> [36592.853202] Flags: nzCv  IRQs on  FIQs on  Mode SVC_32  ISA Thumb  Segment kernel
<4> [36592.860675] Control: 50c53c7d  Table: 01228059  DAC: 00000015

反汇编

PC is at strcmp+0x4/0x20,可以看到就是第二个参数r1的页表是0;第四个字节做的事就是取出r1的数据(第二个参数)放到R0中,r1地址加1,遍历比较两个字符串的各字符

(gdb) disassemble /m strcmp
Dump of assembler code for function strcmp:
82      {
   0x000000fe <+0>:     mov     r2, r0

83              unsigned char c1, c2;
84              int res = 0;
85
86              do {
87                      c1 = *cs++;
   0x00000100 <+2>:     ldrb.w  r3, [r2], #1

88                      c2 = *ct++;
89                      res = c1 - c2;
   0x00000104 <+6>:     ldrb.w  r0, [r1], #1   //取出r1的数据(第二个参数)放到R0中,r1地址加1

90                      if (res)
   0x00000108 <+10>:    subs    r0, r3, r0
   0x0000010a <+12>:    bne.n   0x110 <strcmp+18>

91                              break;
92              } while (c1);
   0x0000010c <+14>:    cmp     r3, #0
   0x0000010e <+16>:    bne.n   0x100 <strcmp+2>

93              return res;
94      }
   0x00000110 <+18>:    bx      lr

End of assembler dump.

LR is at ssv_rx_handle_msg+0x32/0x88 [ssv6x5x],可以看到strcmp的第二参数就是SSV_ID2STR(msg->id);证明没对这个参数做判断,直接使用而导致的异常

(gdb) disassemble /m ssv_rx_handle_msg
Dump of assembler code for function ssv_rx_handle_msg:
1050        if (strcmp("unknown", SSV_ID2STR(msg->id)) == 0)
   0x0001052e <+6>:     ldrh    r4, [r1, #0]
   0x00010532 <+10>:    lsrs    r7, r4, #10
   0x00010534 <+12>:    cmp     r7, #12
   0x00010536 <+14>:    bhi.n   0x10552 <ssv_rx_handle_msg+42>
   0x00010538 <+16>:    ldr     r3, [pc, #96]   ; (0x1059c <ssv_rx_handle_msg+116>)
   0x0001053a <+18>:    ldr.w   r3, [r3, r7, lsl #2]
   0x0001053e <+22>:    cbz     r3, 0x10552 <ssv_rx_handle_msg+42>
   0x00010540 <+24>:    ubfx    r2, r4, #0, #10
   0x00010544 <+28>:    ldr.w   r1, [r3, r2, lsl #2]
   0x00010548 <+32>:    ldr     r3, [pc, #84]   ; (0x105a0 <ssv_rx_handle_msg+120>)
   0x0001054a <+34>:    cmp     r1, #0
   0x0001054c <+36>:    it      eq
   0x0001054e <+38>:    moveq   r1, r3
   0x00010550 <+40>:    b.n     0x10554 <ssv_rx_handle_msg+44>
   0x00010552 <+42>:    ldr     r1, [pc, #76]   ; (0x105a0 <ssv_rx_handle_msg+120>)
   0x00010554 <+44>:    ldr     r0, [pc, #72]   ; (0x105a0 <ssv_rx_handle_msg+120>)
   0x00010556 <+46>:    bl      0x10556 <ssv_rx_handle_msg+46>
   0x0001055a <+50>:    mov     r5, r0
   0x0001055c <+52>:    cbnz    r0, 0x10570 <ssv_rx_handle_msg+72>

<think>好的,用户之前问过kgdb的使用方法,现在想了解Linux内核调试手段。我需要先回忆一下之前提供的kgdb相关内容,确保这次回答不重复,同时覆盖其他常用方法。用户可能是开发者或系统管理员,可能在进行内核开发或故障排查,需要全面的调试方法,包括工具、步骤和适用场景。 首先,得确定用户的基础,可能已经了解kgdb,所以需要补充其他方法。常见的调试手段包括printk、动态调试、kprobes、systemtap、ftrace、kdump、gdb/qemu调试等。每个方法的优缺点和适用场景需要明确区分,帮助用户根据情况选择。 接下来,考虑结构。用户可能需要一个清晰的分类,比如日志类、动态追踪类、崩溃分析、交互式调试、模拟调试等。每个类别下列举工具,说明使用步骤和示例,这样用户容易查找和参考。 还要注意用户可能的痛点,比如生产环境下的使用限制,需要非侵入式方法,或者需要实时追踪性能问题。例如,printk虽然简单但可能影响性能,而ftrace和perf更适合生产环境。kdump用于崩溃后的分析,而kgdb用于实时调试。 需要检查是否有遗漏的重要工具,比如drgn这个新兴的内核调试工具,或者用户可能遇到的常见问题,比如oops信息分析。此外,提醒用户注意内核配置选项,比如CONFIG_DEBUG_INFO等,这些是使用某些工具的前提。 最后,确保语言简洁,步骤明确,示例代码正确,避免使用用户可能不熟悉的术语,必要时提供命令示例和配置建议。同时,注意用户可能的应用场景,比如调试驱动模块、排查死锁或内存泄漏,针对不同场景推荐合适的工具组合。</think>#### 一、Linux内核调试核心方法概览 ```text [调试工具分类] │ ├── 日志分析类(printk/dmesg、动态调试) ├── 动态追踪类(kprobes、ftrace、perf、eBPF) ├── 崩溃分析类(kdump/crash、Oops解析) ├── 交互调试类(KGDB、QEMU+GDB) └── 模拟调试类(User-Mode Linux、QEMU虚拟化) ``` --- #### 二、关键调试手段详解 ##### 1. **基础日志调试(printk/dmesg)** ```c // 内核代码中添加(支持优先级): printk(KERN_ERR "Error at %s:%d\n", __FILE__, __LINE__); // 用户空间查看(带时间戳): $ dmesg -T --level=err,warn ``` **适用场景**:快速定位代码执行路径,需重新编译内核 ##### 2. **动态调试(Dynamic Debug)** ```bash # 启用特定文件的调试信息 echo &#39;file drivers/usb/* +p&#39; > /sys/kernel/debug/dynamic_debug/control # 查看当前激活的调试点 grep " [y] " /sys/kernel/debug/dynamic_debug/control ``` **优势**:无需重新编译内核,动态启用日志 ##### 3. **kprobes 动态插桩** ```bash # 跟踪open系统调用入口(需要CONFIG_KPROBES) echo &#39;p:myprobe do_sys_open filename=+0(%di):string&#39; > /sys/kernel/debug/tracing/kprobe_events echo 1 > /sys/kernel/debug/tracing/events/kprobes/myprobe/enable ``` **输出分析**: ```text bash-1287 [000] ...1 3144.248783: myprobe: (do_sys_open+0x0/0x210) filename="/etc/profile" ``` ##### 4. **ftrace 函数追踪** ```bash # 记录schedule()函数的调用栈(需CONFIG_FUNCTION_TRACER) echo function > /sys/kernel/debug/tracing/current_tracer echo schedule > /sys/kernel/debug/tracing/set_ftrace_filter echo 1 > /sys/kernel/debug/tracing/options/func_stack_trace ``` **输出示例**: ```text bash-1297 [000] d..2 3145.123456: schedule <-do_syscall_64 bash-1297 [000] d..2 3145.123457: <stack trace> => schedule => do_syscall_64 => entry_SYSCALL_64_after_hwframe ``` ##### 5. **崩溃分析(kdump/crash)** ```bash # 配置kdump(需预留内存) $ kexec -p /boot/vmlinuz-$(uname -r) --initrd=/boot/initrd.img-$(uname -r) --append="crashkernel=256M" # 分析vmcore $ crash /usr/lib/debug/boot/vmlinux-$(uname -r) vmcore crash> bt -a # 查看所有CPU的堆栈 crash> kmem -i # 显示内存使用统计 ``` ##### 6. **QEMU+GDB 内核调试** ```bash # 启动QEMU虚拟机(带调试端口) qemu-system-x86_64 -kernel bzImage -append "nokaslr kgdboc=ttyS0,115200" -s -S -nographic # GDB连接调试 (gdb) target remote :1234 (gdb) hbreak start_kernel (gdb) continue ``` --- #### 三、调试技术对比表 | **方法** | 实时性 | 侵入性 | 学习曲线 | 适用阶段 | |----------------|--------|--------|----------|----------------| | printk | 低 | 高 | ★☆☆ | 早期开发 | | KGDB | 高 | 中 | ★★★ | 深层次逻辑错误 | | ftrace | 中 | 低 | ★★☆ | 性能分析 | | kprobes | 高 | 低 | ★★☆ | 动态追踪 | | kdump | 事后分析| 低 | ★★☆ | 生产环境崩溃 | --- #### 四、典型调试场景指南 ##### 场景1内核Oops分析 ```text [ 3263.456789] BUG: unable to handle kernel NULL pointer dereference at 0000000000000050 [ 3263.456792] IP: my_module_write+0x25/0x50 [faulty] ``` **操作步骤**: 1. 通过`dmesg`获取Oops信息 2. 使用`addr2line -e vmlinux 0xffffffffa0000025`转换地址 3. 结合`objdump -dS faulty.ko`反汇编模块 ##### 场景2:死锁检测 ```bash # 使用lockdep检测(需CONFIG_DEBUG_LOCKDEP) echo 1 > /proc/sys/kernel/lockdep # 触发死锁后查看: dmesg | grep -i &#39;possible recursive locking&#39; ``` ##### 场景3:内存泄漏追踪 ```bash # 启用kmemleak(需CONFIG_DEBUG_KMEMLEAK) echo scan > /sys/kernel/debug/kmemleak # 查看泄漏报告 cat /sys/kernel/debug/kmemleak ``` --- #### 五、关键内核配置选项 ```text CONFIG_DEBUG_KERNEL=y # 开启调试主开关 CONFIG_DEBUG_INFO=y # 包含调试符号 CONFIG_MAGIC_SYSRQ=y # 启用SysRq组合键 CONFIG_KPROBES=y # 动态插桩支持 CONFIG_KALLSYMS=y # 显示所有符号信息 CONFIG_DEBUG_SLAB=y # 内存分配跟踪 ``` --- #### 六、高级调试技巧 1. **利用SysRq组合键**: ```text Alt+SysRq+t - 显示所有任务堆栈 Alt+SysRq+w - 显示阻塞任务 Alt+SysRq+m - 输出内存统计 ``` 2. **使用drgn替代crash**: ```python # 现代Python调试工具(需安装drgn) $ drgn -c vmcore >>> task = find_task(pid) >>> print(task.comm) ``` 3. **eBPF高级追踪**: ```bash # 使用bpftrace统计系统调用次数 bpftrace -e &#39;tracepoint:syscalls:sys_enter_* { @[probe] = count(); }&#39; ``` --- > **重要提示**: > 1. 生产环境优先使用低侵入性工具(如ftrace、eBPF) > 2. 调试前备份关键数据,部分调试操作可能导致系统不稳定 > 3. ARM架构需注意交叉调试环境配置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值