[STM32F1]

关于内存对齐的疑问

[复制链接]
5193|40
手机看帖
扫描二维码
随时随地手机跟帖
yearnext|  楼主 | 2017-4-12 09:58 | 显示全部楼层 |阅读模式
本帖最后由 yearnext 于 2017-4-12 10:06 编辑

本人最近在调试程序的时候发现一个因为内存对齐的问题导致单片机进入HardFault工作平台:stm32f103vb
代码如下:
#include <stdint.h>
#include <stdlib.h>

struct test_t
{
    void (*func)(void *);
    void *param;
};

static uint8_t buffer[10];
static uint8_t i;

void *p1, *p2;

void inc(void *param)
{
    uint8_t *num = param;

    if(param == NULL)
    {
        return;
    }

    (*num)++;
}

int main(void)
{
    struct test_t *test = (struct test_t *)&buffer[1];

    p1 = (void *)&test->func;
    p2 = (void *)&test->param;

    test->func  = inc;
    test->param = (void *)&i;

    if( test->func != NULL && test->param != NULL )
    {
        test->func(test->param);
    }

    while(1)
    {
    }
}

test指向的地址为奇数,当程序执行 test->func(test->param); 就会进入HardFault



yearnext|  楼主 | 2017-4-12 10:01 | 显示全部楼层
可以判断test->func这个指针的地址时奇数,test->param的地址也为奇数

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 10:03 | 显示全部楼层
如果把test精简一下,改成

struct test_t
{
    void (*func)(void);
};

void inc(void)
{
    uint32_t num;

    num++;
}


在执行test->func(test->param); 的时候不会进入HardFault

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 10:09 | 显示全部楼层
可以从下图看到函数inc的地址是奇数,变量i的地址是偶数

内存地址

内存地址

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 10:14 | 显示全部楼层
如果我将

    if( test->func != NULL && test->param != NULL )
    {
        test->func(test->param);
    }


改成

    if( test->func != NULL && test->param != NULL )
    {
        test->func((void *)&i);
    }

最终也不会进入HardFault

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 10:20 | 显示全部楼层
也就是说在这个程序中导致单片机进入HardFault的原因在于test->func的参数地址是否为奇数
但是后来我在验证的时候发现上面的结论是不正确的

   
static uint8_t buffer2[10];

    if( test->func != NULL && test->param != NULL )
    {
        test->func((void *)&buffer2[1]);
    }


可以从中看出test->func的参数地址是否为奇数无关
R1GYMY~CDLK@2]Z4JFR~Z4F.png

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 10:24 | 显示全部楼层
本帖最后由 yearnext 于 2017-4-12 10:35 编辑

那么问题很有可能是在压栈的时候发生的,追踪汇编代码,发现在执行
LDRD r1,r0,[r4,#0]
时发生了错误最终进入到HardFault

汇编

汇编

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 10:57 | 显示全部楼层
本帖最后由 yearnext 于 2017-4-16 09:40 编辑

于是需要对ldrd指令做一些了解,入下图所示:
LDRD r1,r0,[r4,#0]

可以等效为
此处等效不正确
r1 = r4;
r0 = r5;

由linqing171指出,应等效为:
r1 = r4寄存器存储的地址
r0 = r4寄存器存储的地址 + 4 byte





LDRD指令

LDRD指令

使用特权

评论回复
feelhyq| | 2017-4-12 11:02 | 显示全部楼层
楼主把 struct test_t *test = (struct test_t *)&buffer[1]改成 struct test_t *test = NULL;试试可否

使用特权

评论回复
feelhyq| | 2017-4-12 11:03 | 显示全部楼层
我刚才分析了半天,也弄不清楚,只是感觉 struct test_t *test = (struct test_t *)&buffer[1] 这个赋值非常奇怪。

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 11:16 | 显示全部楼层
通过查看cortex m3的内存地址可以看出触发hardfault的事件可能与BusFault、MemManage或UsageFault有关

hardfault寄存器

hardfault寄存器

hardfault寄存器值

hardfault寄存器值

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 11:18 | 显示全部楼层
本帖最后由 yearnext 于 2017-4-12 11:19 编辑
feelhyq 发表于 2017-4-12 11:03
我刚才分析了半天,也弄不清楚,只是感觉 struct test_t *test = (struct test_t *)&buffer[1] 这个赋值非 ...
struct test_t *test = (struct test_t *)&buffer[1]
这个只是为了让test指向的内存地址为奇数,模拟当时的调试环境

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 11:29 | 显示全部楼层
从下图中可以看到UsageFault状态寄存器的第九位置位了,看手册上说除法运算时除数为0且在DIV_0_TRP置位时才有效
但是从汇编代码来看并没有看到使用了除法指令

MemManage状态寄存器

MemManage状态寄存器

BusFault状态寄存器

BusFault状态寄存器

UsageFault状态寄存器

UsageFault状态寄存器

MemManage状态寄存器值

MemManage状态寄存器值

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 11:35 | 显示全部楼层
通过观察发现DIV_0_TRP并未置位,那么DIVBYZERO置位的原因又是什么?
通过内存的查看只有DIVBYZERO置位才触发了hardfalut

寄存器介绍

寄存器介绍

寄存器值

寄存器值

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 12:36 | 显示全部楼层
yearnext 发表于 2017-4-12 10:57
于是需要对ldrd指令做一些了解,入下图所示:

可以等效为

参考
6Y27D@R4~~8MG685(PUUCRO.png

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 12:42 | 显示全部楼层
有看到介绍是这样的
1.png

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 12:43 | 显示全部楼层
yearnext 发表于 2017-4-12 12:42
有看到介绍是这样的

不知道是否能解释产生此现象的原因

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 12:47 | 显示全部楼层
yearnext 发表于 2017-4-12 12:43
不知道是否能解释产生此现象的原因

test->param的内存地址没有8字节对齐

使用特权

评论回复
yearnext|  楼主 | 2017-4-12 12:54 | 显示全部楼层
yearnext 发表于 2017-4-12 12:43
不知道是否能解释产生此现象的原因

我觉得这样test->func((void *)&i);没出错和编译器编译的结果有关,可以说是运气好吧
汇编代码如下图所示


L0W[_)`LXX$K@`YYH$GW}$U.png

使用特权

评论回复
icecut| | 2017-4-12 13:07 | 显示全部楼层
lz 很有意思.研究的挺深刻....
但是解决办法是写规范的代码让编译器理解,别给你编译错误.
如果编译器不确定你是不是奇数,IAR就会用复杂的方式去避免这种错误.所以有些代码 keil 出错但 iar 不出错.
编译器是有犯傻的时候,iar 也一样.

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:https://github.com/yearnext

3

主题

87

帖子

1

粉丝