STM32程序发生异常崩溃时,怎样从串口输出当时的程序调用栈等信息

news/2025/1/15 22:36:30/

当STM32程序发生异常崩溃时,为了从串口输出当时的程序调用栈信息,并使用Keil等工具确定具体的函数信息,你可以按照以下步骤操作:

  1. 启用调试信息输出

    • 在STM32程序中,你需要先确保启用了调试信息的输出。这通常涉及到在编译器设置中开启调试信息(如DWARF格式),以便在程序崩溃时能够输出有用的调试数据。
  2. 捕获异常并输出调用栈

    • 在程序中加入异常处理机制,例如使用C++的异常处理或者嵌入式系统中的硬件异常处理(如使用setjmplongjmp,或者定义硬件异常处理函数)。
    • 当异常发生时,捕获异常并在异常处理函数中获取当前的调用栈信息。你可以使用backtrace库或者自定义的函数来获取调用栈。
    • 将调用栈信息通过串口输出。这可能需要你将调用栈地址转换为具体的函数名或行号,这通常需要在编译时包含调试信息,并在程序运行时解析这些信息。
  3. 串口输出

    • 配置STM32的UART(通用异步收发传输器)以输出调试信息。确保串口初始化正确,波特率等参数与接收设备匹配。
    • 在异常处理函数中,将捕获的调用栈信息通过串口发送出去。
  4. 使用Keil等工具分析

    • 在Keil等IDE中,你可以使用调试器来加载崩溃时的程序镜像。
    • 通过查看串口输出的调用栈信息,你可以在Keil中找到对应的函数地址。
    • 利用Keil的符号表(Symbol Table)或者地图文件(Map File),将地址解析为具体的函数名和行号。
    • 通过分析调用栈,你可以确定是哪个函数调用导致了崩溃,并进一步调试以找到问题的根源。
  5. 注意事项

    • 确保在编译时开启了调试信息的生成,这样你才能将地址映射到具体的函数和行号。
    • 串口输出的调用栈信息可能需要进行后处理才能方便查看,例如转换为可读的函数名和行号。
    • 如果程序崩溃时无法直接输出完整的调用栈,可以考虑在关键位置插入日志输出,以便在崩溃前获取尽可能多的信息。

综上所述,通过捕获异常、输出调用栈信息,并结合Keil等工具的调试功能,你可以有效地定位和解决STM32程序中的崩溃问题。

在STM32微控制器上,捕获异常并输出调用栈信息是一个相对复杂的任务,因为这涉及到操作系统的异常处理机制和调试信息的使用。由于STM32通常运行在裸机环境中,没有操作系统的支持,因此需要手动实现异常处理和调用栈跟踪。

以下是一个简单的示例,展示如何在STM32上捕获异常并尝试输出调用栈信息。请注意,这个示例假设你使用的是STM32F4系列,并且使用HAL库进行开发。这个示例可能需要根据具体的硬件和软件环境进行调整。

1. 配置异常向量表

首先,你需要配置异常向量表,将有一个专门的函数来处理未定义指令或系统错误等异常情况。

在你的启动代码或者向量表文件中,确保有定义一个默认的异常处理函数。

例如,在startup_stm32f4xx.s文件中,找到默认的异常向量,如Default_Handler,并确保它被定义。

2. 实现异常处理函数

接下来,实现一个异常处理函数,当发生异常时,该函数会被调用。

void Default_Handler(void) {// 进入异常处理模式__disable_irq();// 输出错误信息printf("Exception occurred!\r\n");// 尝试获取调用栈信息uint32_t* stack_pointer = (uint32_t*)__get_PSP();for (int i = 0; i < 10; i++) {printf("Stack %d: 0x%08X\r\n", i, stack_pointer[i]);}// 无限循环或重启while (1);
}

在这个函数中,我们禁用了中断,输出错误信息,并尝试读取当前的程序堆栈指针(PSP),然后输出堆栈中的内容。这里假设堆栈是满递减堆栈,并且每个栈帧是32位。

3. 配置串口输出

确保串口已经正确配置并初始化,以便能够输出调试信息。

#include "stm32f4xx_hal.h"UART_HandleTypeDef huart2;void MX_USART2_UART_Init(void) {huart2.Instance = USART2;huart2.Init.BaudRate = 115200;huart2.Init.WordLength = UART_WORDLENGTH_8B;huart2.Init.StopBits = UART_STOPBITS_1;huart2.Init.Parity = UART_PARITY_NONE;huart2.Init.Mode = UART_MODE_TX_RX;huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart2.Init.OverSampling = UART_OVERSAMPLING_16;if (HAL_UART_Init(&huart2) != HAL_OK) {// 初始化错误处理Error_Handler();}
}void Error_Handler(void) {while(1);
}int __io_putchar(int ch) {HAL_UART_Transmit(&huart2, (uint8_t*)&ch, 1, 0xFFFF);return ch;
}

4. 在主函数中调用串口初始化

确保在主函数中调用了串口初始化函数。

int main(void) {HAL_Init();MX_USART2_UART_Init();// 主程序代码while (1) {// 主循环}
}

5. 生成调试信息

在Keil中,确保项目设置中启用了调试信息,例如DWARF格式。这样,你可以将地址映射到函数名和源代码行号。

6. 分析调用栈信息

当程序崩溃并输出调用栈信息时,你可以记录下这些地址,然后在Keil中使用“Address to line”功能,将这些地址转换为具体的函数和行号。

例如,在Keil的命令行中,输入:

addr2line -e <your_project.axf> 0xAddress

这将输出对应的文件名和行号。

注意事项

  • 这个方法仅能输出堆栈中的地址,需要手动映射到函数和行号。
  • 堆栈帧的解析取决于编译器的设置和调用约定,可能需要根据实际情况调整。
  • 在裸机环境中,没有标准的调用栈跟踪机制,因此这只是一个基本的实现,可能不适用于所有情况。

通过以上步骤,你可以在STM32程序崩溃时,通过串口输出调用栈信息,并结合Keil工具进行分析,从而定位问题所在。

在Keil中,当你已经获得了调用栈上各个函数的地址后,可以通过以下步骤利用**符号表(Symbol Table)地图文件(Map File)**来将地址解析为具体的函数名和行号。以下是详细的操作步骤:


1. 生成地图文件(Map File)

在Keil中生成地图文件是第一步。地图文件包含了程序中所有符号(函数、变量、地址等)的详细信息,包括它们的地址、大小、所属模块等。

步骤:
  1. 打开Keil项目。
  2. 点击菜单栏的 Project -> Options for Target
  3. 在弹出的窗口中,选择 Linker 标签页。
  4. 勾选 Create Map File,并选择生成地图文件的格式(通常选择 Plain 或 Extended)。
  5. 点击 OK 保存设置。
  6. 重新编译项目(Build)。

编译完成后,地图文件会生成在项目的输出目录下,通常命名为 项目名.map


2. 使用地图文件解析地址

步骤:
  1. 打开生成的 .map 文件。

  2. 在地图文件中,查找 Image Symbol Table 部分。这里列出了程序中所有符号的地址和名称。

    • 例如:
      Image Symbol Table
      Address        Name
      0x08000340     main
      0x08000500     HAL_Init
      0x08000600     SystemClock_Config
      
  3. 根据你从调用栈中获取的地址,在 Image Symbol Table 中查找对应的函数名。

    • 例如,调用栈地址是 0x08000340,在地图文件中找到对应的名称为 main
  4. 如果需要进一步定位到具体的源代码行号,可以结合调试信息。


3. 使用调试信息解析行号

如果你在编译时启用了调试信息(如DWARF格式),Keil可以直接解析地址到具体的函数和行号。

步骤:
  1. 在Keil中打开调试模式(Debug)。

  2. 在调试窗口中,点击 View -> Command Window

  3. 在命令窗口中输入以下命令,将地址解析为函数名和行号:

    ADR2LINE <地址>
    
    • 例如,如果地址是 0x08000340,输入命令:
      ADR2LINE 0x08000340
      
    • Keil会返回类似以下的结果:
      main (main.c: 10)
      
      这表示地址 0x08000340 对应的是 main 函数,位于 main.c 文件的第 10 行。
  4. 如果你没有在调试模式下,可以使用 addr2line 工具。


4. 使用 addr2line 工具解析地址

如果你在命令行环境下,可以使用 addr2line 工具来解析地址。

步骤:
  1. 确保你有编译生成的 .axf 文件(包含调试信息)。
  2. 在命令行中输入以下命令:
    addr2line -e <你的.axf文件> <地址>
    
    • 例如:
      addr2line -e project.axf 0x08000340
      
    • 输出结果类似:
      main.c:10
      
      这表示地址 0x08000340 对应的是 main.c 文件的第 10 行。

5. 结合符号表(Symbol Table)解析地址

符号表是地图文件的一部分,通常在 .map 文件的 Image Symbol Table 部分。它列出了程序中所有符号的地址、名称和大小。

步骤:
  1. 在 .map 文件中,查找 Image Symbol Table 部分。
  2. 根据调用栈中的地址,在符号表中查找对应的函数名。
  3. 如果需要进一步定位行号,可以结合调试信息和 ADR2LINE 命令或 addr2line 工具。

总结

  • 获取地图文件:确保生成 .map 文件,用于查找符号地址和名称。
  • 解析函数名:通过地图文件的 Image Symbol Table 部分,查找调用栈地址对应的函数名。
  • 解析行号
    • 在Keil调试模式下,使用 ADR2LINE 命令。
    • 在命令行环境下,使用 addr2line 工具。
  • 符号表:符号表是 .map 文件的一部分,用于快速定位函数名。

通过以上方法,你可以将调用栈中的地址解析为具体的函数名和行号,从而快速定位程序崩溃的原因。


http://www.ppmy.cn/news/1563440.html

相关文章

c++ 手写queue循环队列

继承与多态 继承 父子出现同名的成员问题 #include <iostream>using namespace std; //父子类中出现重名成员 //定义一个父类 class Father{ public:string name; protected:int pwd; private:int money; public:Father(){cout<<"Father::构造"<&l…

USB 驱动开发 --- Gadget 驱动框架梳理(一)

本文由 Linux 内核文档翻译与总结而来&#xff0c;个人学习笔记仅供参考。 Gadget 框架 在 USB 协议交互过程中&#xff0c;角色定义&#xff1a; the device driver is the master (or “client driver”) Linux 内核中称为 HCD(Host Controller Driver)&#xff0c;负责与 …

Flask表单处理与验证

Flask是一个轻量级的Python框架&#xff0c;它通过扩展库提供了对表单处理与验证的支持。WTForms是一个流行的Flask扩展库&#xff0c;用于创建和验证Web表单。它提供了一种声明式的方法来定义表单结构和验证逻辑&#xff0c;使得表单处理更为简洁和优雅。下面&#xff0c;我们…

第一章:走入HTML

目录 一、HTML的简介  1.介绍 2.HTML的概念和功能 3.HTML的发展历史 二、准备工作 1.编译器的安装 2.相关插件 &#xff08;1&#xff09;中文插件 &#xff08;2&#xff09;Live Server插件 3.快捷键配置方式 三、HTML的基本结构 1.HTML的基本结构 2.快捷方式 四、总…

4Hive计算引擎

4Hive计算引擎 1 MR计算引擎2 Tez计算引擎3 Spark计算引擎 目前Hive支持MapReduce、Tez和Spark 三种计算引擎。 1 MR计算引擎 MR运行的完整过程&#xff1a; Map在读取数据时&#xff0c;先将数据拆分成若干数据&#xff0c;并读取到Map方法中被处理。数据在输出的时候&#…

6.1 MySQL数字函数和条件函数

以前我们在课程中使用过一些mysql的内置函数&#xff0c;比如说四舍五入的round函数&#xff0c;做日期计算的data, datediff函数等等。那么本次课程咱们就来系统的学习一下mysql的这些内置函数&#xff0c;我们使用编程语言写程序的时候&#xff0c;通常会把某一项业务功能封装…

linux stdout/stderr重定向到文件,>或tee

正常情况下直接使用 >或者tee命令只能把stdout的终端输出重定向到文件中&#xff0c;而stderr的输出是无法写到文件中的。 比如在使用svn up时遇到svn 报错的错误&#xff0c;svn ERROR/WARNING 用下面的语句是不会将ERROR/WARNING行写到svn.log的 svn up | tee svn.log…

好用的php商城源码有哪些?

选择一个优秀的商城工具&#xff0c;能更好地帮助大家建立一个好用的商城系统。目前比较流行的都是开源PHP商城系统&#xff0c;那么现实中都有哪些好用的PHP商城源码值得推荐呢&#xff1f;下面就带大家一起来了解一下。 1.TigShop 【推荐指数】&#xff1a;★★★★★☆ 【推…