Linux应用 / 驱动程序崩溃调试

devtools/2025/3/21 22:28:34/

文章目录

  • 前言
  • 一、GDB 使用
    • 1. GDB 介绍
    • 2. Debug版本与Release版本
    • 3. 指令演示
      • 3.1 显示行号
      • 3.2 断点设置
      • 3.3 查看断点信息
      • 3.4 删除断点
      • 3.5 开启 / 禁用断点
      • 3.6 运行
      • 3.7 打印 / 追踪变量
    • 4. 最常用指令
  • 二、Linux 应用程序调试
    • 1. codedump 介绍
    • 2. 在 Linux 系统中使用 coredump
      • 2.1 开启 codedump
      • 2.2 配置生成路径
      • 2.3 调试示例
    • 3. 在开发板上调试
      • 3.1 开启 coredump
      • 3.2 调试示例
  • 三、Linux 驱动程序调试
    • 1. ARM开发中特殊的三个寄存器
      • 1.1 堆栈指针R13(SP)
      • 1.2 连接寄存器R14(LR)
      • 1.3 程序计数器R15(PC)
    • 2. 栈回溯
    • 3. 利用工具调试

前言

我们在使用 Linux 操作系统做项目的时候,当项目比较复杂,工程比较多的时候,编译运行程序很多时候会出现 bug。

这个 bug 可能在运行时立马出现,导致段错误,也可能运行时直接导致程序崩溃,也可能在运行一段时间后程序崩溃,有时候遇到这种莫名其妙的错误会导致我们无从下手。

那么我们就需要使用调试工具或者方法来快速定位问题,通过调试,开发者可以快速定位和修复问题,减少开发和测试的时间,提高开发效率。

一、GDB 使用

1. GDB 介绍

GDB 是由 GUN 软件系统社区提供的调试工具,同 GCC 配套组成了一套完整的开发环境,GDB 是 Linux 和许多类 Unix 系统的标准开发环境。

2. Debug版本与Release版本

我们在编写代码后运行一般是使用【DeBug】环境进行运行。因为在企业里写软件项目,将代码写完后程序员自己要做简单的测试,保证代码没有问题。

当程序员自己测试完没有问题之后,就会将这个可执行程序给到测试人员进行测试,而且会给出自己的单元测试报告。对于测试人员来说所处的模式是【Release】,也就是将来客户要使用的这款软件的发布版本。

当测试在测的过程中,一定会发现一些问题。此时测试人员就会把报告再打回研发部。研发部做修改重新生成Release版本的可行性程序给到测试人员继续测试。

最后只有当测试通过了,再将生成的【单元测试报告】与产品经理进行核对之后没有问题,那这个软件才可以真正地面向市场。

因此:Release 版本的内存会比 Debug 版本的内存小,因为添加调试信息意味着软件的体积就会变大,占的内存更多。

3. 指令演示

测试程序:test.c

#include <stdio.h>int AddToTop(int top)
{printf("Enter AddToTop\n");int count = 0;for(int i = 1;i <= top; ++i){count += i;}printf("Quit AddToTop\n");                                                                         return count;
}int main(void)
{int top = 10;int ret = AddToTop(top);printf("ret = %d\n", ret);return 0;
}

查看可执行文件内存大小:

ls -l

image.png

进行 gdb 调试:

gdb debug

3.1 显示行号

直接执行 l ,随机显示 10 行,执行 l 0 或 l 1,表示从第一行开始显示十行,继续显示按 enter 键即可:

l 
l 0
l 1

image.png

3.2 断点设置

  • b + 行号 —— 在那一行打断点
  • b 源文件:函数名 —— 在该函数的第一行打上断点
  • b 源文件:行号 —— 在该源文件中的这行加上一个断点
b 10
b test.c:AddToTop
b test.c:20

image.png

3.3 查看断点信息

执行 info 显示所有调试信息,执行 info b 显示断点信息:

info
info b

image.png

image.png
其中:

  • Num —— 编号
  • Type —— 类型
  • Disp —— 状态
  • Enb —— 是否可用
  • Address —— 地址
  • What —— 在此文件的哪个函数的第几行
  • breakpoint already hit time,表示断点执行次数

3.4 删除断点

  • d + 当前要删除断点的编号(Num)
  • d + breakpoints

image.png

3.5 开启 / 禁用断点

  • disable b(breakpoints) —— 使所有断点无效
  • enable b(breakpoints) —— 使所有断点有效
  • disable b(breakpoint) + 编号 —— 使一个断点无效
  • enable b(breakpoint) + 编号 —— 使一个断点有效

image.png

image.png

3.6 运行

执行 r:

  • 无断点直接运行到程序结束
  • 再加上断点去运行的话就会在打的断点处停下来

image.png

image.png

执行 n 和 s:

  • n(next) —— 逐过程【相当于F10,为了查找是哪个函数出错了】
  • s(step) —— 逐语句【相当于F11,一次走一条代码,可进入函数,同样的库函数也会进入】

image.png

image.png

3.7 打印 / 追踪变量

  • p(print) 变量名 —— 打印变量值
  • display —— 跟踪查看一个变量,每次停下来都显示它的值【变量/结构体…】
  • undisplay + 变量名编号,取消跟踪
  • 我们也可以去追踪一下这两个变量的地址,不过可以看到对于地址来说是不会发生改变的

image.png

image.png

4. 最常用指令

  • until + 行号
  • finish —— 在一个函数内部,执行到当前函数返回,然后停下来等待命令
  • c(continue) —— 从一个断点处,直接运行至下一个断点处
  • bt —— 查看底层函数调用的过程【函数压栈】

二、Linux 应用程序调试

1. codedump 介绍

codedump 文件是指在程序崩溃或异常结束时,操作系统将程序的内存信息、寄存器状态、堆栈信息等保存到文件中以便进行调试和分析的文件。codedump 文件通常包含了程序崩溃时的全部状态信息,可以帮助程序员快速定位程序崩溃的原因并进行修复。

codedump 文件主要包含了用户空间的内存信息,包括用户空间栈、代码段、数据段和堆等。当一个进程因为某种原因(例如,非法内存访问、非法指令等)异常终止时,操作系统可以将进程的内存信息保存到一 codedump 文件中。这个文件可以用于后续调试,以便找出问题的根源。

codedump 文件通常不包含内核空间栈的信息,因为出于安全和隔离的原因,操作系统不会将内核空间的信息暴露给用户态程序。因此,codedump 文件主要用于分析用户空间的程序问题,而不是内核问题。

2. 在 Linux 系统中使用 coredump

当应用程序崩溃时,内核可以生产 coredump 文件:

image.png

我们可以使用 coredump 文件来调试应用程序。

2.1 开启 codedump

使用 ulimit -a 命令检查系统 codedump 配置(默认情况下,codedump是被关闭的)

ulimit -a

若输出结果中的"core file size"为"0",则表示Coredump被关闭。

执行以下命令开启:

ulimit -c unlimited

image.png

2.2 配置生成路径

开启生成 coredump 的 shell 脚本,配置保存路径:
shell.sh

#!/bin/bashDUMP_PATH=`pwd`# 检查当前用户是否具有sudo权限
if [ "$(id -u)" != "0" ]; thenecho "请使用sudo运行此脚本"exit 1
fi# 配置coredump
echo 2 > /proc/sys/fs/suid_dumpable
echo "$DUMP_PATH/coredump" > /proc/sys/kernel/core_pattern# 创建coredump保存目录
mkdir -p $DUMP_PATH
chmod 777 $DUMP_PATH# coredump功能已开启 配置信息
cat /proc/sys/fs/suid_dumpable
cat /proc/sys/kernel/core_pattern

在模板中,可以使用以下占位符:coredump / %e.%p.%t.coredump

  • %e:可执行文件名
  • %p:进程ID
  • %u:当前用户ID
  • %g:当前用户组ID
  • %s:生成Coredump文件时的信号
  • %t:生成Coredump文件时的时间戳
  • %h:主机名

在目录下运行脚本:

vi shell.sh
chmod +x shell.sh
sudo ./shell.sh

image.png

2.3 调试示例

示例空指针 bug 代码:app_bug.c

#include <stdio.h>volatile int g_val = 0x12345678;int CreatBug(int b, int n)
{int ret;volatile int *p = NULL;printf("in CreatBug\n");*p = 1;ret = b / n;printf("leave CreateBug\n");return ret;
}void D(int n, int m)
{printf("in D\n");CreatBug(n, m);printf("leave D\n");
}void C(int n, int m)
{printf("in C\n");D(n, m);printf("leave C\n");
}
void B(int n, int m)
{printf("in B\n");C(n, m);printf("leave B\n");
}void A(int n, int m)
{printf("in A\n");B(g_val * n, m);printf("leave A\n");
}int main()
{printf("to Creat Bug ... \n");A(100, 0);printf("done\n");return 0;
}

编译运行程序,程序出现了段错误,并生成了 coredump 文件(路径为脚本配置的路径):

image.png

根据生成的 coredump 文件进行调试:

image.png

3. 在开发板上调试

开发板运行我们的可执行程序时,前提是内核不崩溃(驱动程序不崩溃),才会产生core文件。

3.1 开启 coredump

在开发板上执行:

ulimit -c unlimited

3.2 调试示例

在开发上运行测试程序:
image.png

崩溃后直接在板子上调试:

image.png

或者将 core 文件移 ubuntu ,在 ubuntu 上进行调试,原理跟上面一样,这样可能 core 文件会出现权限问题:

image.png

手动添加权限:

sudo chmod 644 core

解释:

  • drwxrwxrwx
  • -rw-------
  • -rwxrwxr-x
  • -:表示这是一个普通文件(非目录)。
  • rwx:文件所有者(book)对该文件有读(r)、写(w)和执行(x)权限。
  • rwx:文件所属组(book)的用户也有读、写和执行权限。
  • r-x:其他用户(不属于 book 组的用户)对该文件有读和执行权限,但没有写权限。

然后就可以进行调试:

image.png

三、Linux 驱动程序调试

1. ARM开发中特殊的三个寄存器

在ARM体系中,一般分为四种寄存器:通用目的寄存器、堆栈指针(SP)、连接寄存器(LR) 以及程序计数器(PC), 其中需要着重理解后面三种寄存器。

1.1 堆栈指针R13(SP)

  • 每一种异常模式都有其自己独立的r13,它通常指向异常模式所专用的堆栈,也就是说五种异常模式、非异常模式(用户模式和系统模式),都有各自独立的堆栈,用不同的堆栈指针来索引。
  • 当ARM进入异常模式的时候,程序就可以把一般通用寄存器压入堆栈,返回时再出栈,保证了各种模式下程序的状态的完整性。

1.2 连接寄存器R14(LR)

  • 保存子程序返回地址。使用BL或BLX时,跳转指令自动把返回地址放入r14中;
  • 子程序通过把r14复制到PC来实现返回,通常用下列指令:MOV PC, LR;BX LR;
  • 当异常发生时,异常模式的R14用来保存异常返回地址,将R14如栈可以处理嵌套中断。

1.3 程序计数器R15(PC)

  • PC是有读写限制的;
  • 没有超过读取限制的时候,读取的值是指令的地址加上8个字节,由于ARM指令总是以字对齐的,故bit[1:0]总是00;
  • 在CM3内部使用了指令流水线,读PC时返回的值是当前指令的地址+4;
  • 向PC中写数据,就会引起一次程序的分支(但是不更新LR寄存器),CM3中的指令至少是半字对齐的,所以PC的LSB总是读回0;
  • 在分支时,无论是直接写 PC 的值还是使用分支指令,都必须保证加载到 PC 的数值是奇数(即 LSB=1),用以表明这是在Thumb 状态下执行;
  • 倘若写了 0,则视为企图转入 ARM 模式,CM3 将产生一个 fault 异常。

2. 栈回溯

当驱动崩溃时,内核空间会打印寄存器信息、栈内容:

image.png

根据上述信息,我们只能进行纯手工的栈回溯。

先执行以下命令得到反汇编文件:

arm-buildroot-linux-gnueabihf-objdump -D hello_drv.ko > hello_drv.dis	

image.png

通过内核崩溃打印信息和反汇编文件分析得到出错位置:

image.png

或者得到模块的代码段基地址:

cat /sys/module/hello_drv/sections/.text

image.png

image.png

通过崩溃时 PC 地址 - 代码段基地址 = 1A8,得到具体出错位置:

image.png
分析出错时候栈的地方:

image.png
在栈里面它保存有返回地址,我们只需要去对应函数的栈找到 LR 的值即可知道函数的调用关系:
image.png
通过上图可知LR对应值和调用关系:

  • 1f8:C调用D
  • 23c:B调用C
  • 2c0:A调用B
  • 37c:hello_write调用A

image.png

image.png

image.png

image.png

3. 利用工具调试

驱动崩溃时打印的串口信息,能否转换为core文件,然后使用gdb进行调试?

答案是可以的,在这里要借助百问网工具进行转换:

image.png

添加调试信息:在 Makefile中加以下选项:

KBUILD_CFLAGS   += -g

参考内核源码目录 Makefile 文件:

image.png

修改 Makefile:

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88
KBUILD_CFLAGS   += -gall:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o hello_test hello_test.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f hello_testobj-m	+= hello_drv.o

重新make:

image.png

传入支持文件:

image.png

执行 make 生成 mcu_coredump

image.png

第1步:把串口信息转换为core文件

./mcu_coredump 1.log 1.core

image.png

第2步:使用gdb调试内核

arm-buildroot-linux-gnueabihf-gdb ~/100ask_imx6ull-sdk/Linux-4.9.88/vmlinux 1.core

第3步:导入驱动文件

(gdb) add-symbol-file /home/book/nfs_rootfs/code/gdb/driver/01_hello_drv/hello_drv.ko 0x7f154000

0x7f154000为代码段

image.png

第4步:使用gdb命令查看驱动运行情况

image.png

image.png

可以清楚的看到各个函数的调用关系,快速定位代码出错地方。


http://www.ppmy.cn/devtools/169002.html

相关文章

深度学习【迭代梯度下降法求解线性回归】

梯度下降法 梯度下降法是一种常用迭代方法&#xff0c;其目的是让输入向量找到一个合适的迭代方向&#xff0c;使得输出值能达到局部最小值。在拟合线性回归方程时&#xff0c;我们把损失函数视为以参数向量为输入的函数&#xff0c;找到其梯度下降的方向并进行迭代&#xff0…

Linux的Shell编程

一、什么是Shell 1、为什么要学习Shell Linux运维工程师在进行服务器集群管理时&#xff0c;需要编写Shell程序来进行服务器管理。 对于JavaEE和Python程序员来说&#xff0c;工作的需要。Boss会要求你编写一些Shell脚本进行程序或者是服务器的维护&#xff0c;比如编写一个…

Socket 、WebSocket、Socket.IO详细对比

WebSocket、Socket 和 Socket.IO 是网络通信中常用的技术&#xff0c;它们在功能、使用场景和实现方式上有明显的异同点。以下是它们的详细对比&#xff1a; 1. Socket 定义 Socket 是一个通用的网络编程接口&#xff0c;用于在网络上实现进程间通信&#xff08;IPC&#xff0…

cool-admin-midway 使用腾讯云cos上传图片

说明&#xff1a;在使用cool-admin这个低代码平台时&#xff0c;发现官方的cos上传插件有问题&#xff0c;总是报错 substring&#xff0c;故自己找解决方案&#xff0c;修改本地的upload方法改为云端上传。 解决方案&#xff1a; 安装腾讯云cos的nodeJS SDK pnpm i cos-node…

CMS漏洞-WordPress篇

一.姿势一&#xff1a;后台修改模板拿WebShell 1.使用以下命令开启docker cd /www/wwwroot / vulhub / wordpress / pwnscriptum docker - compose up - d 如果发现不能开启&#xff0c;可以检查版本和端口 2.访问网址登录成功后 外观 &#x1f449;编辑 &#x1f449;404.…

Python第六章04:列表操作练习题

# 列表常用功能练习题 """ 有一个列表&#xff0c;内容是&#xff1a;[21,25,21,23,22,20],记录一批学生的年龄请通过列表的功能&#xff08;方法&#xff09;&#xff0c;对齐进行&#xff1a; 1.定义这个列表&#xff0c;并用变量接收它 2.追加一个数字31&…

【SpringCloud】Eureka、LoadBalancer和Nacos

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【中间件】企业级中间件剖析 一、微服务 单体架构 单体架构是一种传统的软件架构方式&#xff0c;它将一个应用程序的所有功能模块&#xff08;如用户认证、订单处理、数据存储等&#xff09;都打包在…

Web3网络生态中数据保护合规性分析

Web3网络生态中数据保护合规性分析 在这个信息爆炸的时代&#xff0c;Web3网络生态以其独特的去中心化特性&#xff0c;逐渐成为数据交互和价值转移的新平台。Web3&#xff0c;也被称为去中心化互联网&#xff0c;其核心理念是将数据的控制权归还给用户&#xff0c;实现数据的…