找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3620

积分

0

好友

480

主题
发表于 3 小时前 | 查看: 5| 回复: 0

在嵌入式开发中,一旦程序跳转到 HardFault_Handler 并停止响应,通常意味着发生了严重的运行时错误。

HardFault 本质

启动文件中可以看到:

HardFault_Handler\
                PROC
                EXPORT  HardFault_Handler          [WEAK]
                B       .
                ENDP

B . 是死循环指令,程序执行到这里就会一直停住。

常见原因

数组越界

这是最常见的错误类型。

典型场景:

uint8_t buffer[10];

void process_data(uint8_t index)
{
    buffer[index] = 0x55;  // 如果 index >= 10,就异常了
}

实际项目中更常见的情况:

void uart_rx_handler(void)
{
    static uint8_t rx_buffer[64];
    static uint8_t rx_index = 0;

    rx_buffer[rx_index++] = UART_DR;  // 没做边界检查!

    if (rx_index >= 64) {
        // 下次写入就越界了
        process_packet(rx_buffer);
        rx_index = 0;
    }
}

解决方法:

  1. 在数组访问前后加边界检查的断言
  2. 用调试器看 PC 指针停在哪,反汇编找到对应的 C 代码
  3. 检查数组索引变量的值,是否超出合理范围

防御性写法:

#define ARRAY_SIZE(arr)  (sizeof(arr) / sizeof(arr[0]))

void safe_write(uint8_t *buf, uint32_t size, uint32_t index, uint8_t value)
{
    if (index < size) {
        buf[index] = value;
    } else {
        // 记录错误日志,或者触发断言
        while(1);
    }
}

栈溢出

栈溢出的问题在于它往往不会立即触发 HardFault,而是改写其他内存区域的数据,导致程序在完全不相关的地方崩溃。

典型场景:

void deep_function(void)
{
    char temp_buffer[512];  // 在栈上开大数组
    memset(temp_buffer, 0, sizeof(temp_buffer));
    // ...
}

void another_function(void)
{
    deep_function();  // 栈空间不够用了
}

递归调用过深也会导致栈溢出:

int factorial(int n)
{
    if (n <= 1) return 1;
    return n * factorial(n - 1);  // 如果 n 很大,栈就爆了
}

判断栈溢出的方法:

  1. 查看 Map 文件,确认栈空间分配大小
  2. 在栈底填充特殊值(如 0xDEADBEEF),运行一段时间后检查是否被改写
  3. 使用调试器查看 MSP(主栈指针)的值,是否超出栈空间范围

解决方法:

在启动文件中增大栈空间:

Stack_Size      EQU     0x00001000  ; 4KB 栈空间

添加栈使用监控:

#define STACK_CANARY_VALUE  0xDEADBEEF
extern uint32_t __initial_sp;
extern uint32_t __StackLimit;

void check_stack_usage(void)
{
    uint32_t *stack_bottom = &__StackLimit;
    uint32_t used_words = 0;

    while (*stack_bottom != STACK_CANARY_VALUE) {
        used_words++;
        stack_bottom++;
    }

    uint32_t used_bytes = used_words * 4;
    // 打印栈使用量
}

野指针

野指针是最难排查的问题之一,因为它可能指向任何地址,症状千奇百怪。

未初始化的指针

void bad_function(void)
{
    int *ptr;  // 未初始化,指向随机地址
    *ptr = 100;  // 崩!
}

返回局部变量地址

uint8_t *get_buffer(void)
{
    uint8_t local_buf[10];
    return local_buf;  // 返回局部变量的地址!
}

void caller(void)
{
    uint8_t *buf = get_buffer();
    buf[0] = 0x55;  // local_buf 已经失效了
}

排查方法:

  1. 查看 LR(链接寄存器)的值,找到调用函数
  2. 检查指针的值是否合理(是否为 NULL,或指向有效地址)
  3. 在可疑的指针操作前后加打印,观察指针值

防御性写法:

void safe_ptr_write(int **pptr, int value)
{
    if (pptr == NULL || *pptr == NULL) {
        return;  // 或者触发错误处理
    }
    **pptr = value;
}

// 指针初始化为 NULL,用完也置 NULL
int *ptr = NULL;
// 使用前检查
if (ptr != NULL) {
    *ptr = 100;
}

对齐访问错误

典型场景:

void misaligned_access(void)
{
    uint8_t buffer[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    // 地址不是 4 的倍数!
    uint32_t *ptr = (uint32_t *)(buffer + 1);
    uint32_t value = *ptr;  // 可能触发 HardFault
}

结构体强制转换时也容易出现这个问题:

typedef struct {
    uint8_t  cmd;
    uint32_t data;  // 在内存中可能不是 4 字节对齐
} __attribute__((packed)) Packet_t;

void process_packet(uint8_t *raw_data)
{
    Packet_t *pkt = (Packet_t *)raw_data;
    uint32_t d = pkt->data;  // 如果 raw_data 不是 4 的倍数,可能出问题
}

排查方法:

  1. 查看 CFSR(可配置故障状态寄存器)的 UNALIGNED 位
  2. 检查指针地址是否能被访问类型的大小整除

正确的做法:

uint32_t read_unaligned_u32(void *ptr)
{
    uint8_t *p = (uint8_t *)ptr;
    return ((uint32_t)p[0]) |
           ((uint32_t)p[1] << 8) |
           ((uint32_t)p[2] << 16) |
           ((uint32_t)p[3] << 24);
}

或使用 __packed 关键字(Keil)或 __attribute__((packed))(GCC),但要注意性能影响。

HardFault 处理

与其让程序死循环,不如打印有用的调试信息:

void HardFault_Handler(void)
{
    uint32_t *sp;

    __asm volatile(
        "TST LR, #4\n"
        "ITE EQ\n"
        "MRSEQ %0, MSP\n"
        "MRSNE %0, PSP\n"
        : "=r" (sp)
    );

    printf("\n=== HardFault ===\n");
    printf("PC  = %08X\n", sp[6]);
    printf("LR  = %08X\n", sp[5]);
    printf("PSR = %08X\n", sp[7]);
    printf("CFSR= %08X\n", SCB->CFSR);
    printf("HFSR= %08X\n", SCB->HFSR);

    if (SCB->CFSR & 0x80000000) {
        printf("BFAR= %08X\n", SCB->BFAR);
    }
    if (SCB->CFSR & 0x00800000) {
        printf("MMFAR=%08X\n", SCB->MMFAR);
    }

    while (1);
}

减少 HardFault 的发生

  1. 数组访问做边界检查 — 不要假设输入数据总是合法的
  2. 栈空间开大一些 — RAM 不够时优化其他地方
  3. 指针初始化为 NULL — 使用前检查,使用后置 NULL
  4. 注意内存对齐 — 特别是结构体强制转换和 DMA 操作
  5. 开启编译器警告 — 把 warning 当作 error 处理
  6. 使用静态分析工具 — PC-lint、Coverity 等工具能发现很多隐患



上一篇:网络工程师的灵魂拷问:钱多但累死的工作,该不该干?
下一篇:告别臃肿与卡顿:TablePro,Mac 原生数据库客户端上手指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-5-7 09:09 , Processed in 0.639829 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表