debug对于开发工程师很重要

在日常开发中,总会遇到一些出人意料的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 registerunsigned int stacked_pc = hardfault_args[6];  // Program counterunsigned 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.mzph.cn/bicheng/53717.shtml

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

相关文章

“论剑”智算时代,长沙已经站在计算产业的“华山之巅”

文 | 智能相对论 作者 | 陈泊丞 共赴全新十年之约,长沙又来搞大事情了! 2024互联网岳麓峰会以“AI汇湘江 数智领航未来”为主题,全面聚焦在“AI”时代把握数字化、网络化、智能化发展机遇,积极响应当前人工智能技术迅猛发展的势…

Qt5.4.1连接odbc驱动操作达梦数据库

Qt5.4.1连接odbc驱动操作达梦数据库 1 环境介绍2 Qt5.4.1 安装2.1 图形化界面安装Qt5.4.12.2 配置Qt5.4.1 环境变量2.3 Qt5.4.1 生成 libqsqlodbc.so 并配置2.3.1 生成Makefile2.3.2 查看 libqsqlodbc.so 文件并配置 3 配置Qt测试用例4 达梦数据库学习使用列表 1 环境介绍 CPU…

实现卷积层的前向传播(Pythom版)

在TensorFlow框架中,实现卷积层(2维)的代码是 tf.keras.layers.Conv2D()。它主要接收如下几个参数, filters:卷积核的个数,也就是卷积层输出的通道数(沿axis-1的维度) kernel_size&a…

性能碾压pandas、polars的数据分析神器来了

来源:python大数据分析 费弗里 1 简介 就在几天前,经过六年多的持续开发迭代,著名的开源高性能分析型数据库DuckDB发布了其1.0.0正式版本。 DuckDB具有极强的单机数据分析性能表现,功能丰富,具有诸多拓展插件&#xf…

时空特征融合方向小论文创新点一次性都给你!看到就是赚到

朋友们,今天给大家推荐一个发小论文很不错的方向:时空特征融合。 时空特征融合是一种提高模型性能和准确性的关键技术,通过结合空间和时间维度的信息,它可以显著提高模型的预测精度和泛化能力,给我们提供更全面的数据…

惊!还有这种邮件群发神器!?

邮件群发工具是推广营销的重要利器。这种软件具备强大的功能,能够批量发送邮件,确保所发送的邮件不易被标记为垃圾邮件。同时,它还包括自动地址采集和整理功能,能够快速获取邮箱地址,省去了寻找地址的麻烦。 功能亮点&…

Trm理论 2(Word2Vec)

神经网络模型(NNLM)和Word2Vec NNLM模型是上次说过的模型,其目的是为了预测下一个词。 softmax(w2tanh(w1x b1)b2) 会得到一个副产品词向量 而Word2Vue就是专门求词向量的模型 softmax(w2*(w1*x b1)b2) Word2Vec softmax(w2*(w1*x b1)b…

连续信号的matlab表示

复习信号与系统以及matlab 在matlab中连续信号使用较小的采样间隔来表四 1.单位阶跃信号 阶跃信号:一个理想的单位阶跃信号在时间 t 0 之前值为0,在 t 0 及之后值突然变为常数 A(通常取 A 1) %matlab表示连续信号,是让信号的采样间隔很小…

Python 中的 SHAP 简介

本文中有多篇计划文章,后期会补充相关链接。鉴于公众号内无法后期修改文章,请关注原文链接。 如何创建和解释 SHAP 图:瀑布图、力图、平均 SHAP 图、蜂群图和依赖图 可直接在橱窗里购买,或者到文末领取优惠后购买: SHAP 是用于理解和调试模型的最强大的 Python 包。它可以…

Oceanbase Restore Point实践

官网链接:Restore Point-V3.2.4-OceanBase 数据库文档-分布式数据库使用文档 在很多应用系统中,用户需要查询数据库中的某个时间点,或者特定版本的数据来完成一些数据分析或汇总之类的操作。 OceanBase 数据库在 V2.2.7x 版本中提供了 Restor…

vscode ssh离线远程连接ubuntu调试

遇见问题: 1 ssh连接上无法启动服务器的虚拟环境; 2 ssh连接上启动服务器的虚拟环境后无法打断点; 对于问题需要参考下面连接安装python和debugy的插件拓展,并且配置json文件link。VSCode - 离线安装扩展python插件教程_vscode…

Jupyter Notebook设置代码提示和自动代码补全

算法学习、4对1辅导、论文辅导或核心期刊可以通过公众号滴滴我 文章目录 在使用Jupyter Notebook中,会出现Jupyter不像Pycharm一样,可以 自动补全代码以及 代码方法提示等功能,这时候就需要通过给Jupyter安装插件来进行实现。 执行步骤&#…

EMR Spark-SQL性能极致优化揭秘 Native Codegen Framework

作者:周克勇,花名一锤,阿里巴巴计算平台事业部EMR团队技术专家,大数据领域技术爱好者,对Spark有浓厚兴趣和一定的了解,目前主要专注于EMR产品中开源计算引擎的优化工作。 背景和动机 SparkSQL多年来的性能…

StarRocks Lakehouse 快速入门——Apache Iceberg

导读: StarRocks Lakehouse 快速入门旨在帮助大家快速了解湖仓相关技术,内容涵盖关键特性介绍、独特的优势、使用场景和如何与 StarRocks 快速构建一套解决方案。最后大家也可以通过用户真实的使用场景来了解 StarRocks Lakehouse 的最佳实践&#xff01…

2024国赛数学建模备赛|30种常用的算法模型之最优算法-层次分析法

层次分析法(Analytic Hierarchy Process,简称 AHP)是对一些较为复杂、较为模 糊的问题作出决策的简易方法,它特别适用于那些难于完全定量分析的问题。它是美 国运筹学家 T. L. Saaty 教授于上世纪 70 年代初期提出的一种简便、灵活…

网络安全服务基础Windows--第13节-加密技术

基本保密通信模型 密码学发展 1. 古典密码学(1949年之前) 主要特点:数据的安全基于算法的保密 ● 在古典密码学中,密码算法通常是通过⼿⼯或机械装置实现的。 ● 数据的安全性主要依赖于算法本身的保密性,即“安…

Return arguments from function calling with OpenAI API when streaming?

题意:在使用OpenAI API进行流式传输时,如何返回函数调用的参数? 问题背景: Ive made a simple OpenAI API example with function calling. Im only using function calling to format the response, Im not calling multiple fu…

一个vue前端的例子(六)如何获取table一行的id

比如我们要删除列表一行 vue中template中的scope到底是个什么&#xff1f;_vue template scope-CSDN博客 <el-button click"edit_tool(scope.$index)" type"warning" icon"el-icon-edit">编辑</el-button> 获取列表下标

Brave编译指南2024 Windows篇:Brave简介(一)

1.引言 随着互联网技术的不断发展&#xff0c;用户对隐私保护和安全性的需求日益增加。传统浏览器在这方面存在诸多不足&#xff0c;而Brave浏览器则通过一系列创新技术和功能&#xff0c;致力于为用户提供更好的隐私保护和浏览体验。Brave不仅屏蔽广告和跟踪器&#xff0c;还…

web项目如何部署到服务器上呢?——麻烦的方法

只需关注web项目如何部署到服务器上&#xff0c;因为服务器运行时就可以访问web项目了。 一、麻烦的方法 1、首先启动服务器 &#xff08;1&#xff09;找到bin文件夹 &#xff08;2&#xff09;双击运行startup.bat文件 &#xff08;3&#xff09;运行之后的界面如下&#…