下面是在实际工作中遇到的一次内核(5.4.110)访问非法内存地址(空指针)导致出错的现场,在这里记录一下简单的分析流程为以后遇到类似的问题作为参考。

[  220.619861] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000023
[ 220.628815] Mem abort info:
[ 220.631737] ESR = 0x96000006
[ 220.634932] EC = 0x25: DABT (current EL), IL = 32 bits
[ 220.640369] SET = 0, FnV = 0
[ 220.643542] EA = 0, S1PTW = 0
[ 220.646788] Data abort info:
[ 220.649783] ISV = 0, ISS = 0x00000006
[ 220.653737] CM = 0, WnR = 0
[ 220.656855] user pgtable: 4k pages, 39-bit VAs, pgdp=000000001149c000
[ 220.663422] [0000000000000023] pgd=00000000080dc003, pud=00000000080dc003, pmd=0000000000000000
[ 220.672360] Internal error: Oops: 96000006 [#1] SMP
[ 220.677359] Modules linked in:
[ 220.680627] CPU: 0 PID: 0 Comm: swapper/0 Not tainted 5.4.110-00126-gad9975d0488d-dirty #1
[ 220.689008] Hardware name: van_xum,tequila (DT)
[ 220.693614] pstate: 20000085 (nzCv daIf -PAN -UAO)
[ 220.698626] pc : dwc_descriptor_complete+0x104/0x140
[ 220.703775] lr : dwc_descriptor_complete+0x48/0x140
[ 220.708772] sp : ffffffc010003d00
[ 220.712203] x29: ffffffc010003d00 x28: 0000000000000006
[ 220.717699] x27: 0000000000000100 x26: 000000000000000a
[ 220.723192] x25: ffffffc010b3363b x24: ffffff80102a3080
[ 220.728684] x23: 0000000000000001 x22: ffffff8010104860
[ 220.734172] x21: 0000000000000000 x20: ffffff80101047f0
[ 220.739664] x19: ffffffc010ed6460 x18: 0000000000000000
[ 220.745152] x17: 0000000000000000 x16: 0000000000000000
[ 220.750635] x15: 0000000000000000 x14: 0000000000000000
[ 220.756117] x13: 0000000000000000 x12: 0000000000000000
[ 220.761603] x11: 0000000000000000 x10: ffffffc010d329d8
[ 220.767093] x9 : 0000000000000005 x8 : ffffffc010d329b8
[ 220.772582] x7 : 0000000000000000 x6 : 0000000000000000
[ 220.778067] x5 : 0000000000000000 x4 : 0000000000000000
[ 220.783555] x3 : ffffffc010ed64a0 x2 : ffffffffffffffdf
[ 220.789048] x1 : ffffffffffffffff x0 : ffffffc010ed6490
[ 220.794528] Call trace:
[ 220.797177] dwc_descriptor_complete+0x104/0x140
[ 220.801978] dwc_scan_descriptors+0x1e4/0x32c
[ 220.806510] dw_dma_tasklet+0x37c/0x380
[ 220.810551] tasklet_action_common.constprop.0+0xb0/0x10c
[ 220.816140] tasklet_action+0x34/0x40
[ 220.819966] __do_softirq+0x1e8/0x2b8
[ 220.823810] irq_exit+0x64/0xb4
[ 220.827134] __handle_domain_irq+0x7c/0xa8
[ 220.831394] gic_handle_irq+0x84/0xc8
[ 220.835213] el1_irq+0xb8/0x140
[ 220.838536] arch_local_irq_enable+0x8/0x10
[ 220.842908] finish_task_switch+0x10c/0x194
[ 220.847250] __schedule+0x3f0/0x530
[ 220.850893] schedule_idle+0x34/0x48
[ 220.854640] do_idle+0x94/0x268
[ 220.857955] cpu_startup_entry+0x2c/0x48
[ 220.862068] rest_init+0xc8/0xd8
[ 220.865489] arch_call_rest_init+0x18/0x20
[ 220.869769] start_kernel+0x448/0x480
[ 220.873658] Code: 97f276b0 a9057fff f90033ff 17ffffe2 (b9404441)
[ 220.879910] ---[ end trace 6df1a29c28ae9694 ]---
[ 220.884668] Kernel panic - not syncing: Fatal exception in interrupt
[ 220.891172] SMP: stopping secondary CPUs
[ 220.895297] Kernel Offset: disabled
[ 220.898920] CPU features: 0x0002,20002004
[ 220.903036] Memory Limit: none
[ 220.906301] ---[ end Kernel panic - not syncing: Fatal exception in interrupt ]---

该问题初步分析是进行 DMA 传输时,可能将函数调用栈信息冲掉所致

1. dump 信息说明

  • pc:(Program Counter)pc 指针,记录当前执行哪一条指令;存储当前 CPU 正在执行指令的地址
  • lr:(Link Register)x30 寄存器,保存函数返回地址
  • sp:(Stack Pointer)栈指针
  • fp:(Frame Pointer)x29 寄存器

2. 根据 dump 出的函数调用定位具体出错的代码

最终出错代码

pc : dwc_descriptor_complete+0x104/0x140

gdb 定位具体代码

$aarch64-none-linux-gnu-gdb vmlinux

(gdb) list *(dwc_descriptor_complete+0x104)
0xffffffc01040f7c8 is in dwc_descriptor_complete (./include/linux/dmaengine.h:1189).
1184 void dma_async_tx_descriptor_init(struct dma_async_tx_descriptor *tx,
1185 struct dma_chan *chan);
1186
1187 static inline void async_tx_ack(struct dma_async_tx_descriptor *tx)
1188 {
1189 tx->flags |= DMA_CTRL_ACK; //在进行flags赋值是出错,也就是tx出现空指针
1190 }
1191
1192 static inline void async_tx_clear_ack(struct dma_async_tx_descriptor *tx)
1193 {
(gdb)

3. 定位具体出错指令

由于出错的接口函数中只是一个普通的赋值操作,因此需要进一步确认出错时,CPU 执行的汇编指令是否存在异常或者特殊性

查看 dwc_descriptor_complete 接口函数的汇编实现

(gdb) disassemble dwc_descriptor_complete
Dump of assembler code for function dwc_descriptor_complete:
0xffffffc01040f6c4 <+0>: stp x29, x30, [sp, #-112]!
0xffffffc01040f6c8 <+4>: mov x29, sp
0xffffffc01040f6cc <+8>: stp x19, x20, [sp, #16]
0xffffffc01040f6d0 <+12>: stp x21, x22, [sp, #32]
0xffffffc01040f6d4 <+16>: str x23, [sp, #48]
0xffffffc01040f6d8 <+20>: xpaclri
0xffffffc01040f6dc <+24>: mov x19, x1 //第二个入参被转存到x19 <------------------ [6]
0xffffffc01040f6e0 <+28>: mov x20, x0
0xffffffc01040f6e4 <+32>: and w23, w2, #0xff
0xffffffc01040f6e8 <+36>: mov x0, x30
0xffffffc01040f6ec <+40>: bl 0xffffffc01009647c <_mcount>
0xffffffc01040f6f0 <+44>: mrs x0, sp_el0
0xffffffc01040f6f4 <+48>: add x22, x20, #0x70
0xffffffc01040f6f8 <+52>: ldr x1, [x0, #1240]
0xffffffc01040f6fc <+56>: str x1, [sp, #104]
0xffffffc01040f700 <+60>: mov x1, #0x0 // #0
0xffffffc01040f704 <+64>: mov x0, x22
0xffffffc01040f708 <+68>: bl 0xffffffc01093b10c <_raw_spin_lock_irqsave>
0xffffffc01040f70c <+72>: mov x21, x0
0xffffffc01040f710 <+76>: ldr w0, [x19, #64] // 第二个入参的第一次使用 ( dma_cookie_complete(txd);) <------- [7]
0xffffffc01040f714 <+80>: add x3, x19, #0x40 //x3=x19+0x40, 0xffffffc010ed6460+0x40=0xffffffc010ed64a0
0xffffffc01040f718 <+84>: cmp w0, #0x0
0xffffffc01040f71c <+88>: b.gt 0xffffffc01040f724 <dwc_descriptor_complete+96> //判断w0大于0时,进行跳转
0xffffffc01040f720 <+92>: brk #0x800
0xffffffc01040f724 <+96>: ldr x1, [x3, #16]
0xffffffc01040f728 <+100>: str w0, [x1, #12]
0xffffffc01040f72c <+104>: str wzr, [x19, #64]
0xffffffc01040f730 <+108>: cbz w23, 0xffffffc01040f7bc <dwc_descriptor_complete+248> //比较w23为0,进行跳转,w23应该是传入的第三个参数
0xffffffc01040f734 <+112>: ldr x0, [x3, #40]
0xffffffc01040f738 <+116>: str x0, [sp, #80]
0xffffffc01040f73c <+120>: ldr x0, [x3, #48]
0xffffffc01040f740 <+124>: str x0, [sp, #88]
0xffffffc01040f744 <+128>: ldr x0, [x3, #56]
0xffffffc01040f748 <+132>: str x0, [sp, #96]
0xffffffc01040f74c <+136>: mov x0, x19 //将x19赋值到x0 <---------------- [5]
0xffffffc01040f750 <+140>: ldr x2, [x0, #48]! //x2=*(x0 + 0x30),读取内存地址的值赋值x2 <-------------- [4]
0xffffffc01040f754 <+144>: sub x2, x2, #0x20 //x2第一次处理,x2减0x20 (&desc->tx_list) <------------- [3]
0xffffffc01040f758 <+148>: add x1, x2, #0x20 //x1是在x2基础上又加了0x20,因此变成了全F
0xffffffc01040f75c <+152>: cmp x1, x0
0xffffffc01040f760 <+156>: b.ne 0xffffffc01040f7c8 <dwc_descriptor_complete+260> // b.any x1不对于x0时,跳转执行 <----------- [2]
0xffffffc01040f764 <+160>: ldr w0, [x3, #4]
0xffffffc01040f768 <+164>: mov x1, x19 ....
0xffffffc01040f7ac <+232>: subs x1, x1, x2
0xffffffc01040f7b0 <+236>: mov x2, #0x0 // #0
0xffffffc01040f7b4 <+240>: b.eq 0xffffffc01040f7f0 <dwc_descriptor_complete+300> // b.none
0xffffffc01040f7b8 <+244>: bl 0xffffffc0100ad278 <__stack_chk_fail>
0xffffffc01040f7bc <+248>: stp xzr, xzr, [sp, #80]
0xffffffc01040f7c0 <+252>: str xzr, [sp, #96]
0xffffffc01040f7c4 <+256>: b 0xffffffc01040f74c <dwc_descriptor_complete+136>
0xffffffc01040f7c8 <+260>: ldr w1, [x2, #68] // 出错指令 w1=*(x2 + 0x44) <----------- [1]
0xffffffc01040f7cc <+264>: orr w1, w1, #0x2
0xffffffc01040f7d0 <+268>: str w1, [x2, #68]
0xffffffc01040f7d4 <+272>: ldr x2, [x2, #32]
....

[1]: 此处为具体出错指令,意思是将寄存器 X2 中的值加上 68 后作为内存地址,并将该内存地址的数据取出,存到 w1 寄存器中。非法内存地址也就是 X2 加 68(0x44)得到的,根据 crash dump 出的寄存器值此时 X2=ffffffffffffffdf,0xffffffffffffffdf+0x44=0x0000000000000023 刚好是非法内存地址,也就是说出错的因为 x2 寄存器的值保存错了。

以上流程中表明 x2 寄存器出现 FFFFFFFFFFFFFFFF 的可能性存在两种:

  1. dwc_descriptor_complete 接口函数传参时,第二个参数是个错误的指针。这样就会使 x0 寄存器错误导致在 4 时,通过内存地址读取数据赋值该 x2 时,出现全 F 的值(一个错误的指针指向了错误的内存区域所致)。

    • 由于在 [7] 处对第二个参数已经使用过(读写),因此可以证明传入的第二个参数指针是正确的。如果错误应该会在 [7] 处直接报错。
  2. dwc_descriptor_complete 接口函数传参时,第二个参数是正确的。但是在 4 时,通过内存地址读取数据赋值给 x2 时,原来正确的数据被别的程序覆盖掉了(踩内存)

通过以上流程的分析我认为是在 4 处,读取相关内存地址中的数据时,原有的正确数据被错误数据覆盖

C 源码:

(gdb) list dwc_descriptor_complete
271 /*----------------------------------------------------------------------*/
272
273 static void
274 dwc_descriptor_complete(struct dw_dma_chan *dwc, struct dw_desc *desc,
275 bool callback_required)
276 {
277 struct dma_async_tx_descriptor *txd = &desc->txd;
278 struct dw_desc *child;
279 unsigned long flags;
280 struct dmaengine_desc_callback cb;
(gdb)
281
282 dev_vdbg(chan2dev(&dwc->chan), "descriptor %u complete\n", txd->cookie);
283
284 spin_lock_irqsave(&dwc->lock, flags);
285 dma_cookie_complete(txd);
286 if (callback_required)
287 dmaengine_desc_get_callback(txd, &cb);
288 else
289 memset(&cb, 0, sizeof(cb));
290
(gdb)
291 /* async_tx_ack */
292 list_for_each_entry(child, &desc->tx_list, desc_node)
293 async_tx_ack(&child->txd);
294 async_tx_ack(&desc->txd);
295 dwc_desc_put(dwc, desc);
296 spin_unlock_irqrestore(&dwc->lock, flags);
297
298 dmaengine_desc_callback_invoke(&cb, NULL);
299 }
300

通过对以上汇编代码的分析出错的原因主要是 [4], 读取内存数据(ldr x2, [x0, #48]!)时出错。该指令对应的 C 代码实现主要在 list_for_each_entry(child, &desc->tx_list, desc_node) 接口

这样结合之前分析的出错原因,可能是别的程序写内存时覆盖了 tx_list 链表数据(踩内存);不过还存在一种可能就是 tx_list 的操作出错了,由 dma 驱动代码本身所造成的 bug。

4. MMU 错误信息

[  220.619861] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000023
// 解析ESR_EL1寄存器
[ 220.628815] Mem abort info:
[ 220.631737] ESR = 0x96000006
[ 220.634932] EC = 0x25: DABT (current EL), IL = 32 bits
[ 220.640369] SET = 0, FnV = 0
[ 220.643542] EA = 0, S1PTW = 0
[ 220.646788] Data abort info:
[ 220.649783] ISV = 0, ISS = 0x00000006
[ 220.653737] CM = 0, WnR = 0 [ 220.656855] user pgtable: 4k pages, 39-bit VAs, pgdp=000000001149c000
[ 220.663422] [0000000000000023] pgd=00000000080dc003, pud=00000000080dc003, pmd=0000000000000000
[ 220.672360] Internal error: Oops: 96000006 [#1] SMP

以上信息主要是在内核出现 do_page_fault 时的一些 log 信息

do_page_fault
\->__do_kernel_fault
\->die_kernel_fault
\->mem_abort_decode
\->data_abort_decode
\->show_pte
\->die(Oops)

from: arch/arm64/mm/fault.c

mem_abort_decode 函数主要是解析 ESR_ELx 寄存器,在内核模式下为 ESR_EL1

参考:DDI0487D_a_armv8_arm.pdf —— D12.2.36 ESR_EL1, Exception Syndrome Register (EL1)

EC, bits [31:26], EC = 0x25, DABT (current EL),异常级别未更改的数据中止。用于数据访问产生的 MMU 错误、除堆栈指针未对齐引起的对齐错误和同步外部中止(包括同步奇偶校验或 ECC 错误)之外的对齐错误。

EC == 0b010101
SVC instruction execution in AArch64 state.
See ISS encoding for an exception from HVC or SVC instruction execution on
page D12-2785. EC == 0b100101
Data Abort taken without a change in Exception level.
Used for MMU faults generated by data accesses, alignment faults other than those
caused by Stack Pointer misalignment, and synchronous External aborts, including
synchronous parity or ECC errors. Not used for debug related exceptions.
See ISS encoding for an exception from a Data Abort.

SVC:Supervisor Call instruction

5. gdb 调试 vmlinux 技巧

查看特定位置的代码

(gdb) list * [函数名]或[函数名+函数内偏移]

命令: list 或 l 列出指定的函数或行

格式:

list [FUNCTION/*ADDRESS]
  • LINENUM,在当前文件中围绕该行列出,
  • FILE:LINENUM,列出该文件中的该行,
  • FUNCTION,列出该函数的开头,
  • FILE:FUNCTION,用于区分同名的静态函数。
  • *ADDRESS,在包含该地址的行周围列出

查看特定函数的汇编实现

disassemble [函数名]

命令: disassemble 反汇编内存地址部分(也可以用函数名)

格式:

disassemble[/m|/r|/s] START [, END]
  • /m: 被弃用了推荐使用 /s
  • /r: 包含十六进制的原始指令。
  • /s: 包括源代码行(如果可用)。 在此模式下,输出按 PC 地址顺序显示,并显示所有相关源文件的文件名和内容。

查看特定地址信息

(gdb) x [内存地址]

命令:x 查看内存地址中的值

格式:

x /<n/f/u>  <addr>
  • n 是正整数,表示需要显示的内存单元的个数,即从当前地址向后显示 n 个内存单元的内容,一个内存单元的大小由第三个参数 u 定义。
  • f 表示 addr 指向的内存内容的输出格式,s 对应输出字符串,此处需特别注意输出整型数据的格式:
  • x 按十六进制格式显示变量。
  • d 按十进制格式显示变量。
  • u 按十六进制格式显示无符号整型。
  • o 按八进制格式显示变量。
  • t 按二进制格式显示变量。
  • a 按十六进制格式显示变量。
  • c 按字符格式显示变量。
  • f 按浮点数格式显示变量。
  • i instruction 以汇编指令显示。
  • u: 就是指以多少个字节作为一个内存单元 - unit, 默认为 4。当然 u 还可以用被一些字符表示,如 b=1 byte, h=2 bytes,w=4 bytes,g=8 bytes.
  • 表示内存地址。
(gdb) x 0xffffffc01041b1cc  #当前地址数据,表示机器码
0xffffffc01041b1cc <dwc_desc_get+108>: 0xf0ffffe0 (gdb) x /i 0xffffffc01041b1cc #将机器码以汇编代码形式输出
0xffffffc01041b1cc <dwc_desc_get+108>: adrp x0, 0xffffffc01041a000 <dmatest_func+1252>

arm64 下内核 crash—— 非法地址的更多相关文章

  1. Linux内核Crash分析

    转载自:http://linux.cn/article-3475-1.html 在工作中经常会遇到一些内核crash的情况,本文就是根据内核出现crash后的打印信息,对其进行了分析,使用的内核版本为 ...

  2. 一次内核 crash 的排查记录

    一次内核 crash 的排查记录 使用的发行版本是 CentOS,内核版本是 3.10.0,在正常运行的情况下内核发生了崩溃,还好有 vmcore 生成. 准备排查环境 crash 内核调试信息rpm ...

  3. ASM:《X86汇编语言-从实模式到保护模式》第13章:保护模式下内核的加载,程序的动态加载和执行

    ★PART1:32位保护模式下内核简易模型 1. 内核的结构,功能和加载 每个内核的主引导程序都会有所不同,因为内核都会有不同的结构.有时候主引导程序的一些段和内核段是可以共用的(事实上加载完内核以后 ...

  4. bootm命令中地址参数,内核加载地址以及内核入口地址

    bootm命令只能用来引导经过mkimage构建了镜像头的内核镜像文件以及根文件镜像,对于没有用mkimage对内核进行处理的话,那直接把内核下载到连接脚本中指定的加载地址0x30008000再运行就 ...

  5. Linux下逻辑地址、线性地址、物理地址详细总结

    Linux下逻辑地址.线性地址.物理地址详细总结 一.逻辑地址转线性地址      机器语言指令中出现的内存地址,都是逻辑地址,需要转换成线性地址,再经过MMU(CPU中的内存管理单元)转换成物理地址 ...

  6. [转帖]Linux下逻辑地址、线性地址、物理地址详细总结

    Linux下逻辑地址.线性地址.物理地址详细总结 https://www.cnblogs.com/alantu2018/p/9002441.html 总结的挺好的 现在应该是段页式管理 使用MMU和T ...

  7. /proc/sys/ 下内核参数解析

    http://blog.itpub.net/15480802/viewspace-753819/ http://blog.itpub.net/15480802/viewspace-753757/ ht ...

  8. Linux 下修改网卡MAC地址

    Linux下修改网卡MAC地址 by:授客 QQ:1033553122 例子:修改网卡接口eth0的mac地址 #停用网卡接口,比如eth0 # ifconfig eth0 down #编辑对应的网卡 ...

  9. LINUX的DNS怎么设置?linux下如何修改DNS地址

    LINUX的DNS怎么设置?linux下如何修改DNS地址 https://jingyan.baidu.com/article/870c6fc32c028eb03fe4be30.html Linux下 ...

  10. VMware虚拟CentOS 6.5在NAT模式下配置静态IP地址及Xshell远程控制配置

    VMware虚拟CentOS 6.5在NAT模式下配置静态IP地址及Xshell远程控制配置 标签: LinuxXshellCentOS 2016-10-15 04:58 127人阅读 评论(0) 收 ...

随机推荐

  1. C# 通过反射(Reflection)调用不同名泛型方法

    概述 由于工作需要,需要通过数据类型和方法名控制方法走向 用到的数据类型有8种(string,Int16,Int32,Int64,Boolean,Byte,Single,Double) 读取的方法(参 ...

  2. 【Maxwell】02 Kafka配置

    一.快速搭建Kafka环境 基于Docker容器创建(供参考): https://www.cnblogs.com/mindzone/p/15608984.html 这里简要写一下命令: # 拉取zk ...

  3. 【转载】大模型时代的PDF解析工具

    本文来自博客园,作者:叶伟民,转载请注明原文链接:https://www.cnblogs.com/adalovelacer/p/18092208/pdf-tools-for-large-languag ...

  4. Ubuntu 18.04.4 安装docker18.09 (使用阿里云的源)

    由于AI_Station 是使用容器构建环境的,而且只提供镜像上传下载功能,不为容易提供网络功能,因此需要在平台上把镜像拉取到本地,并安装一些必备软件然后再打包成镜像上传回去,因此需要在本地构建doc ...

  5. 元学习:元学习的始祖论文——《On the Optimization of a Synaptic Learning Rule》

    ============================================= 这个论文保持着上世纪人工智能论文的特点,与其说是计算机类论文更不如说是偏生物科学方面的论文,这也可能是因为当 ...

  6. vue之循环遍历v-for

    1.背景 2.遍历数组 <!DOCTYPE html> <html lang="en"> <head> <meta charset=&qu ...

  7. 生成式 AI:机会与风险并存,企业该如何取舍?

    作者 | 李晨 编辑 | Debra Chen Gartner最近对全球2,500名高管进行的一项调查发现,近一半(45%)的人表示,ChatGPT的宣传促使他们增加人工智能(AI)投资.调查报告称, ...

  8. MySQL手动执行rollback,内部实现分析

    -- 测试手动回滚操作 -- 1手动开启事务 START TRANSACTION -- 2执行更新操作语句 UPDATE FraBakNtuAnalysize SET IsDeleted = 0 WH ...

  9. 微服务开发手册之GRPC 荐

    GRPC是一个高性能.通用的开源RPC框架,基于HTTP/2协议标准和Protobuf序列化协议开发,支持众多的开发语言. @[TOC] 1 简介 在GRPC框架中,客户端可以像调用本地对象一样直接调 ...

  10. 在Ubuntu 16.04 LTS服务器上安装FreeRADIUS和Daloradius的方法

    FreeRADIUS 为AAA Radius Linux下开源解决方案,DaloRadius为图形化web管理工具. freeradius一般用来进行账户认证管理,记账管理,常见的电信运营商的宽带账户 ...