debug对于开发工程师很重要

news2024/11/24 15:28:51

在日常开发中,总会遇到一些出人意料的bug,程序跑飞,上电就挂,程序没有按预期执行诸如此类的问题,没有好的调试方法,真的很难定位问题,更别说解决了。在这里分享我用过的一些调试方法,抛砖引玉。

MCU调试

学生时代大家都知道在线调试,这个可以实时查看外设寄存器、cpu通用寄存器数据以及堆栈,可以打上软断点(有数量限制)。这种调试属于侵入式调试,有缺点。

咱们以stm32为例,使用 J-Link 进行调试时,涉及的调试技术和接口主要是 JTAGSWD(Serial Wire Debug),这两种接口允许调试器控制目标微控制器的状态,执行读取内存、设置断点等操作。这种调试方式属于侵入式调试,因为它会对系统的正常运行产生干扰。接下来,我将详细解释这种调试的具体实现及其原理。

调试接口

STM32 微控制器通常支持以下两种调试接口:

  • JTAG(Joint Test Action Group):这是早期常见的调试协议,使用多个引脚进行调试,通常需要 5-6 根信号线(如 TCK、TMS、TDI、TDO、TRST 等)。这种接口提供了比较丰富的调试和测试功能。
  • SWD(Serial Wire Debug):这是 ARM Cortex-M 微控制器系列的标准调试接口,它是 JTAG 的简化版,只需要 2 根信号线(SWDIO 和 SWCLK)就能进行全功能的调试和编程,因此更节省引脚资源,尤其适用于嵌入式系统。

侵入式调试原理

侵入式调试的核心是在调试器与 MCU 之间建立一种交互式控制,通过调试接口向 MCU 发出命令,读取 MCU 状态或改变其执行流程。调试器会通过调试接口连接 MCU 的内部调试单元(例如 ARM Cortex-M 核心中的 CoreSight 调试架构)。具体操作如下:

步骤:

  1. 连接调试接口:J-Link 通过 JTAG 或 SWD 连接到 STM32 芯片,调试器可以通过这些接口访问 MCU 的内部寄存器、内存和其他调试资源。
  2. 调试器控制 MCU
    • 单步执行(Single Stepping):调试器能够让 MCU 一次执行一条指令,方便用户逐行查看代码的执行情况。
    • 设置断点(Breakpoint):用户可以在程序的某个位置设置断点,当 MCU 执行到这个位置时,它会自动暂停,让用户检查程序状态。
    • 修改寄存器/内存:调试器可以直接修改 MCU 内部寄存器或存储器的值,允许用户在不重新编译代码的情况下改变程序的行为。
  3. 暂停和恢复运行:在调试过程中,调试器会暂停 MCU 的正常执行,这就是侵入式调试的侵入性部分。暂停后,调试器可以获取当前的寄存器值、内存内容等调试信息。调试结束后,调试器会恢复 MCU 的运行。

调试时的影响:

  • 实时性丢失:由于在调试过程中 MCU 被暂停执行,因此 MCU 不再能够实时响应外部事件。这在某些需要实时响应的系统中可能会引发问题。
  • 调试开销:虽然 SWD 是一种较为轻量的调试方式,但依然需要占用部分系统资源,如调试接口的引脚、调试单元的部分 CPU 时钟等。

非侵入式调试对比

相比之下,非侵入式调试不会干扰系统的正常运行。ARM Cortex-M 核心提供了 ETM(Embedded Trace Macrocell) 等调试模块,允许程序运行时记录执行流,并通过外部工具分析,但不改变程序的运行状态。这种方式不需要暂停 MCU,也不会影响其实时性。调试工具有J-trace、劳特巴赫(好用),缺点贵,和canoe有一拼。

J-link调试

J-Link 调试器主要依赖编译生成的 可调试文件 来执行调试操作,而非直接使用 .hex 文件。

通常,调试器需要的是包含符号信息的文件,例如:

  • .elf(Executable and Linkable Format):这是最常用的文件格式,包含完整的调试符号和程序代码。调试器通过读取这个文件,能够将内存地址与源代码中的变量、函数对应起来,从而方便地进行断点设置、单步调试和变量监控。

调试器和调试符号:J-Link 调试器在调试时,主要使用的文件是编译器生成的带有调试符号的文件(如 .elf)。该文件包含:

  • 符号表:映射源代码变量、函数到目标代码的内存地址。
  • 源代码信息:调试器能将二进制指令与源代码行号对应,帮助开发人员调试程序。

调试过程:

在实际调试过程中,J-Link 调试器会通过工具链或 IDE(如 Keil, IAR 等)读取 .elf 文件的调试符号和目标设备的内存状态,允许用户进行断点设置、单步调试、变量监控等操作。

编程和调试的区别:

  • 编程:如果你只是将程序下载到 STM32 设备上运行,可以使用 .bin 文件或 .hex 文件。
  • 调试:进行调试时,需要使用带有调试符号的文件(如 .elf),以便调试器识别出源代码与内存之间的映射关系。

当然,咱们都要考虑周全,你可能会遇到没有调试器的情况,可能还会出现这种情况,在线调试没问题,但是脱离调试器,程序上电就跑飞了…这些小昭都遇到过,直接吐血,有机会好好探讨。

不使用调试器方法

栈回溯(Stack Backtrace) 是一种常见的方法,用于在程序卡死、异常(如 HardFault)或意外重启时,帮助开发工程师查找问题所在的函数调用栈和程序崩溃时的上下文信息。

栈回溯的原理

栈回溯的核心思想是通过分析堆栈帧(Stack Frame)来重建程序执行时的函数调用顺序。当程序执行一个函数时,会在栈中保存返回地址和函数的局部变量。当发生异常或崩溃时,栈中的这些信息仍然保留,可以通过遍历堆栈找到函数调用链。

在 ARM Cortex-M 的异常处理机制下,CPU 会自动保存一些核心寄存器的值(如 PC、LR、SP 等)到堆栈中,这为我们进行栈回溯提供了重要的信息。

如何实现栈回溯

  1. 获取发生异常时的堆栈帧
    当 STM32 进入异常(如 HardFault)时,可以通过异常处理函数获取堆栈指针,并根据堆栈帧中的内容进行回溯分析。
    • 异常处理函数(HardFault_Handler) 会将出错时的 CPU 状态保存到栈中。
    • Cortex-M 处理器在发生硬件异常时,通常会将以下寄存器内容压入堆栈:R0-R3、R12、LR(Link Register)、PC(Program Counter)和 xPSR。
  2. 读取堆栈指针
    可以在 HardFault_Handler 中通过堆栈指针(SP)来访问这些寄存器的值。
  3. 回溯函数调用栈
    通过逐步读取栈中的内容(包括返回地址),并结合符号表(如 .elf 文件),你可以找到程序在崩溃时的调用链条。

实现步骤

1. 捕获异常

首先,需要在异常处理程序中捕获异常,常见的异常是 HardFault。可以使用如下代码捕获发生 HardFault 时的堆栈帧信息:

c
复制代码
void HardFault_Handler(void) {
    __asm volatile
    (
        "TST lr, #4 \n"              // 检查异常的堆栈是否在主堆栈(MSP)还是进程堆栈(PSP)
        "ITE EQ \n"
        "MRSEQ r0, MSP \n"           // 如果是 MSP, 将 MSP 的地址赋值给 r0
        "MRSNE r0, PSP \n"           // 如果是 PSP, 将 PSP 的地址赋值给 r0
        "B hard_fault_handler_c \n"  // 跳转到 C 语言处理函数
    );
}

2. 提取堆栈信息

hard_fault_handler_c 函数将根据堆栈帧提取重要的寄存器值,如 PC 和 LR。

c
复制代码
void hard_fault_handler_c(unsigned int *hardfault_args) {
    unsigned int stacked_r0 = hardfault_args[0];
    unsigned int stacked_r1 = hardfault_args[1];
    unsigned int stacked_r2 = hardfault_args[2];
    unsigned int stacked_r3 = hardfault_args[3];
    unsigned int stacked_r12 = hardfault_args[4];
    unsigned int stacked_lr = hardfault_args[5];  // Link register
    unsigned int stacked_pc = hardfault_args[6];  // Program counter
    unsigned int stacked_psr = hardfault_args[7]; // Program status register

    // 现在你可以通过 UART 或者其他方式将这些值输出,用于分析
    printf("Hard fault:\n");
    printf("R0 = %08X\n", stacked_r0);
    printf("R1 = %08X\n", stacked_r1);
    printf("R2 = %08X\n", stacked_r2);
    printf("R3 = %08X\n", stacked_r3);
    printf("R12 = %08X\n", stacked_r12);
    printf("LR = %08X\n", stacked_lr);   // 用于回溯函数调用链
    printf("PC = %08X\n", stacked_pc);   // 指向出错的指令地址
    printf("xPSR = %08X\n", stacked_psr);
}

3. 回溯调用链

通过 LR(链接寄存器),可以回溯调用链。LR 存储的是调用函数的返回地址(即函数返回时需要跳转到的地址)。如果 LR 的最低有效位为 1,表示返回地址是在 Thumb 指令集模式下执行的。

你可以根据 LR 的值继续遍历栈,找到前一个调用的函数地址,并逐步回溯整个调用链。

4. 使用符号表解析地址

堆栈中的 PC 和 LR 是程序的内存地址,通过符号表(如 .elf 文件)可以将这些内存地址映射回具体的源代码位置。你可以使用工具(如 arm-none-eabi-addr2line)来根据地址找出对应的源代码行:

bash
复制代码
arm-none-eabi-addr2line -e firmware.elf 0x08001234

这条命令会告诉你 0x08001234 地址对应的源代码文件和行号。

注意事项

  • 堆栈完整性:栈回溯依赖于函数调用过程中保存的堆栈帧信息,因此,如果程序发生了堆栈溢出或内存被篡改,回溯的结果可能不可靠。
  • 优化级别:在高优化级别下,编译器可能会对函数调用进行优化(如内联函数),导致无法准确回溯调用链。

其他补充方法

  • 软件看门狗结合栈回溯:在结合看门狗定时器重启系统的同时,通过 HardFault_Handler 捕获堆栈信息,可以记录下系统崩溃的关键点。
  • 软件调试器输出:在崩溃发生时输出栈帧信息,或者使用 LED 或 UART 输出执行的关键步骤。

bin文件修改方式调试

通过修改 .bin 文件来定位 程序卡死问题的方法,可能是指插桩(Instrumentation)技术。这种方法是通过在编译生成的二进制文件中手动插入代码,在不重新编译程序的情况下加入一些用于调试的功能,比如记录程序运行状态或函数调用情况。

这种方法可以通过在 .bin 文件中的特定位置手动插入日志输出代码状态报告代码,然后在运行时帮助定位问题,默认情况下,某些 Cortex-M 内核中的 UsageFault 异常可能是禁用的,但可以通过配置寄存器来启用它,从而在非法操作发生时能够捕获并处理这个异常。

Linux调试

在QNX环境,运行的程序遇到崩溃的情况,系统会自动产生coredump文件,这个coredump文件就包含了前面崩溃的调试信息,里面包含了前面程序的符号表,可以定位到是具体是哪个文件以及函数导致破溃的,还能查看对应堆栈数据(GDB调试)。

。。。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2122912.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

欧几里得算法求最大公约数

两个不全为0的非负整数m,n的最大公约数记为gcd(m,n),代表能够整除(即余数为0)m和n的最大正整数。 计算gcd(m,n)的欧几里得算法: 第一步&#xf…

初识爬虫2

requests学习(未更新完): 小技巧,如果你用的也是pycharm,对于控制台输出页面因为数据很长一行,不方便进行查看, 可以让它自动换行: 1.requests文档阅读学习链接:快速上…

linux 脱机

先安装 screen apt-get install screen

【网络】网络通信的传输方式

目录 1.网络通信中的两种基本通信模式 1.1.怎么理解连接 1.2.面向有连接类型 1.3.面向无连接类型 2.实现这两种通信模式的具体交换技术 2.1.电路交换 2.2.分组交换 3.根据接收端数量分类 单播(Unicast) 广播(Broadcast) …

8、值、指针、引用作为参数或返回值

一、作为参数 1、值传递 #include <iostream> using namespace std;void swap(int a, int b) {cout << __FUNCTION__ << "交换前a:" << a << " b:" << b << endl;int tmp a;a b;b tmp;cout << __FUN…

98.游戏的启动与多开-分析与实现多开器

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;易道云信息技术研究院 上一个内容&#xff1a;97.游戏的启动与多开-共享内存多开检测 以 97.游戏的启动与多开-共享内存多开检测 它的…

若依框架开发

若依环境 介绍 ‌若依是一款快速开发平台(低代码)&#xff0c;用于快速构建企业级后台管理系统&#xff0c;它提供了许多常用的功能模块和组件&#xff0c;包括权限管理、代码生成、工作流、消息中心等 官方地址: https://www.ruoyi.vip/ ‌基于Spring Boot和Spring Cloud‌…

vscode配置C/C++环境(保姆级详细教程)

一. 引言 VSCode&#xff0c;全称为Visual Studio Code&#xff0c;是一款由微软开发的免费、开源的轻量级代码编辑器&#xff0c;它支持多种编程语言和平台&#xff0c;并提供丰富的扩展功能&#xff0c;让开发者能够更高效地编写代码。 大家能来搜用如何在VSCode配置C/C环境…

Linxu系统:kill命令

1、命令详解&#xff1a; kill命令是用于向进程发送信号&#xff0c;通常用来终止某个指定PID服务进程&#xff0c;kill命令可以发送不同的信号给目标进程&#xff0c;来实现不同的操作&#xff0c;如果不指定信号&#xff0c;默认会发送 TERM 信号&#xff08;15&#xff09;&…

1. 初识LLM API:环境配置与多轮对话演示

其实AI应用并不是一个什么很高大上的东西&#xff0c;你可以将它当作一个文字的“调库”行为&#xff0c;“调库”只需要知道库名就行了&#xff0c;这里实际也是如此。甚至你只需要知道你想问什么&#xff0c;将你的消息作为输入&#xff0c;就能从大模型得到输出。而这个“库…

CSS学习17--CSS3 过渡、2D变形、3D变形、动画

CSS3 过渡、2D变形、3D变形、动画 一、过渡二、2D变形 transform1.移动 translate2.缩放 scale3. 旋转 rotate4. 倾斜 skew 三、3D变形1. rotateX&#xff08;&#xff09;rotateY&#xff08;&#xff09; rotateZ&#xff08;&#xff09;2. 体会透视 perspective3. translat…

直播电商系统卷轴模式源码开发的发展前景

随着互联网技术的飞速发展和直播电商的日益兴起&#xff0c;直播电商系统已成为数字经济的重要组成部分。其中&#xff0c;卷轴模式作为一种结合了积分奖励和任务兑换机制的商业模式&#xff0c;正逐渐在直播电商领域崭露头角&#xff0c;并展现出广阔的发展前景。本文将从卷轴…

【腾讯云】AI驱动的数据库TDSQL-C如何是从0到1体验电商可视化分析小助手得统计功能,一句话就能输出目标统计图

欢迎来到《小5讲堂》 这是《腾讯云》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 背景效果图流程图创建数据库基本信息数据库配置设置密码控制台开启…

学工控必须知道的变频器字母符号

#变频器##变频器故障##伺服电机##电工##电工#工控人加入PLC工业自动化精英社群 工控人加入PLC工业自动化精英社群

windows软件应该安装在哪里

100%原创动力 文章目录 前言一、安装路径二、数据文件夹1.用户文件夹2.AppData 三、为什么需要管理员权限四、绿色软件 前言 windows软件应该安装在哪里 一、安装路径 windows系统安装软件应该安装在哪里&#xff0c;根据windows规范应该安装在某个盘的 Program Files 目录…

ICP备案办理的流程中股东附件不显示如何解决

ICP备案办理的流程中股东附件不显示如何解决 问题解决方案注意吐槽 问题 问题弹窗&#xff1a; 部分一级股东没有上传证照附件,请检查并上传。 问题内容&#xff1a; 在【股东追溯及其相关证明材料】这一页中无法保存&#xff0c;提示要上传证照附件&#xff0c;但这边找不到任…

dwg2text抽取dwg文件文字不完整分析

libdxfrw项目中dxf2text命令可以抽取dwg文件中的文字部分&#xff0c;编译完工程后对手头上的dwg文件提取时发现抽取不完整&#xff0c;对源码进行分析时发现该命令只处理了text部分&#xff0c;不处理mtext部分 bool dx_iface::printText(const std::string& fileI, dx_da…

Cross-Encoder实现文本匹配(重排序模型)

引言 前面几篇文章都是基于表示型的方法训练BERT进行文本匹配&#xff0c;而本文是以交互型的方法。具体来说&#xff0c;将待匹配的两个句子拼接成一个输入喂给BERT模型&#xff0c;最后让其输出一个相似性得分。 文本匹配系列文章先更新到此&#xff0c;目前为止都是基于监督…

AI大模型行业专题报告:大模型发展迈入爆发期,开启AI新纪元

大规模语言模型&#xff08;Large Language Models&#xff0c;LLM&#xff09;泛指具有超大规模参数或者经过超大规模数据训练所得到的语言模型。与传统语言模型相比&#xff0c;大语言模型的构建过程涉及到更为复杂的训练方法&#xff0c;进而展现出了强大的自然语言理解能力…

网络药理学:1、文章基本思路、推荐参考文献、推荐视频

文章基本思路 选择一味中药或者中药复方&#xff08;常见的都是选择一味中药&#xff0c;如&#xff1a;大黄、银柴胡等&#xff09;&#xff0c;同时选择一个要研究的疾病&#xff08;如食管癌等&#xff09;获得中药的主要化学成分或者说活性成分&#xff08;有时候也以化合…