专栏名称: 嵌入式微处理器
关注这个时代最火的嵌入式微处理器,你想知道的都在这里。
目录
相关文章推荐
现代快报  ·  王曼昱亚洲杯女单夺冠 ·  8 小时前  
现代快报  ·  王曼昱亚洲杯女单夺冠 ·  8 小时前  
每天学点做饭技巧  ·  45岁导演饺子成都街头被偶遇,穿橙色冲锋衣搭 ... ·  18 小时前  
每天学点做饭技巧  ·  汪小菲马筱梅过情人节被偶遇,西装革履,状态已 ... ·  3 天前  
51好读  ›  专栏  ›  嵌入式微处理器

谈谈嵌入式C语言踩内存问题

嵌入式微处理器  · 公众号  ·  · 2024-11-19 12:00

正文

C 语言内存问题,难在于定位,定位到了就好解决了。 今天, 们就 来聊一 聊踩内存

踩内存,通过字面理解即可。 本来是操作这一块内存,因为设计失误操作到了相邻内存,篡改了相邻内存的数据。

踩内存,轻则导致功能异常,重则导致程序崩溃死机。

内存,粗略地分:

  • 静态存储区
  • 动态存储区

存储于相同存储区的变量才有互踩内存的可能。

一、静态存储区踩内存

下面,分享一个之前在实际项目中遇到的问题。

在Linux中,一个进程默认可以打开的文件数为1024个,fd的范围为0~1023。

项目中使用了串口,串口fd为static全局变量,某次这个fd突然变为一个超范围得值,显然被踩了。

出问题的代码如:

float arr[5];
int count = 8;
for (size_t i = 0; i {
    arr[i] = xxx;
}


操作同属于静态存储区的arr数组出现了数组越界操作,踩了后面几个连续变量,fd也踩了。

实际中,纯靠log打印调试很难定位fd的相邻变量,需要花比较多的时间。

在Linux中,这个问题我们可以通过生成生成map文件来查看,在CMakeLists.txt中生成map文件的代码如:

set(CMAKE_EXE_LINKER_FLAGS "-Wl,-Map=output.map")  # 生成map文件
set(CMAKE_C_FLAGS "-fdata-sections") # 把static变量地址输出到map文件
set(CMAKE_CXX_FLAGS "-fdata-sections")

二、动态存储区踩内存

动态堆内存踩内存典型例子:malloc与strcpy搭配使用不当导致缓冲区溢出。

#include 
#include 
#include 
#include 

int main (void)
{
    char *str = "hello";
    int str_len = strlen(str);

    ///
    printf("str_len = %d\n", str_len);

    ///
    char *ptr = (char*)malloc(str_len);
    if (NULL == ptr)
    {
        printf("malloc error\n");
        exit(EXIT_FAILURE);
    }

    ///
    char *p_a = ptr + 5;
    *p_a = 20;
    printf("*p_a = %d\n", *p_a);

    ///
    strcpy(ptr, str);

    ///
    printf("ptr = %s\n", ptr);
    printf("*p_a = %d\n", *p_a);

    ///
    if (ptr)
    {
        free(ptr);
        ptr = NULL;
    }

    return 0;
}

运行结果:

显然,经过strcpy操作之后,数据a的值被篡改了。

原因:忽略了strcpy操作会把字符串结束符一同拷贝到目的缓冲区。

如果相邻的空间里没有存放其它业务数据,那么踩了也不会出现问题,如果正好存放了重要数据,这时候可能会出现大bug,而且可能是偶现的,不好复现定位。

针对这种情况,我们可以借助一些工具来定位问题,比如:

  • dmalloc
  • valgrind

valgrind的简单使用可阅读往期笔记: 工具 | Valgrind仿真调试工具的使用

当然,我们也可以在我们的代码里进行一些尝试。针对这类问题,分享一个检测思路:

我们在申请内存时,在申请内存的前后增加两块标识区(红区),里面写入固定数据。申请、释放内存的时候去检测这两块标识区有没有被破坏(检测操作堆内存时是否踩到高压红区)。

为了能定位到后面的标识区,在增加一块len区用来存储实际申请的空间的长度。

此处,我们定义:

  • 前红区(before_ red_area):4字节。写入固定数据0x11223344。

  • 后红区(after_ red_area):4字节。写入固定数据0x55667788。

  • 长度区(len_area):4字节。存储数据存储区的长度。

1、自定义申请内存函数

除了数据存储区之外,多申请12个字节。自定义申请内存的函数自然是要兼容malloc的使用方法。malloc原型:

void *malloc(size_t __size);

自定义申请内存的函数:

void *Malloc(size_t




    
 __size);

返回值自然要返回数据存储区的地址。具体实现:

#define BEFORE_RED_AREA_LEN  (4)            ///
#define AFTER_RED_AREA_LEN   (4)            ///
#define LEN_AREA_LEN         (4)            ///

#define BEFORE_RED_AREA_DATA (0x11223344u)  ///
#define AFTER_RED_AREA_DATA  (0x55667788u)  ///

void *Malloc(size_t __size)
{
    ///
    void *ptr = malloc(BEFORE_RED_AREA_LEN + AFTER_RED_AREA_LEN + __size + LEN_AREA_LEN);
    if (NULL == ptr)
    {
        printf("[%s]malloc error\n", __FUNCTION__);
        return NULL;
    }

    ///
    *((unsigned int*)(ptr)) = BEFORE_RED_AREA_DATA;     

    ///
    *((unsigned int*)(ptr + BEFORE_RED_AREA_LEN)) = __size;  

    ///
    *((unsigned int*)(ptr + BEFORE_RED_AREA_LEN + LEN_AREA_LEN + __size)) = AFTER_RED_AREA_DATA;  

    ///
    void *data_area_ptr = (ptr + BEFORE_RED_AREA_LEN + LEN_AREA_LEN);
    return data_area_ptr;
}

2、自定义检测内存函数

申请完内存并往内存里写入数据后,检测本该写入到数据存储区的数据有没有写到红区。这种内存检测方法我们是用在开发调试阶段的,所以检测内存,我们可以使用断言,一旦触发断言,直接终止程序报错。

检测前后红区里的数据有没有被踩:

void CheckMem(void *ptr, size_t __size)
{
    void *data_area_ptr = ptr;

    ///
    printf("[%s]before_red_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(data_area_ptr - LEN_AREA_LEN - BEFORE_RED_AREA_LEN)));
    assert(*((unsigned int*)(data_area_ptr - LEN_AREA_LEN - BEFORE_RED_AREA_LEN)) == BEFORE_RED_AREA_DATA);

    ///
    printf("[%s]len_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(data_area_ptr - LEN_AREA_LEN)));
    assert(*((unsigned int*)(data_area_ptr - LEN_AREA_LEN)) == __size); 

    ///
    printf("[%s]after_red_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(data_area_ptr + __size)));
    assert(*((unsigned int*)(data_area_ptr + __size)) == AFTER_RED_AREA_DATA); 
}

3、自定义释放内存函数

要释放所有前面申请内存。释放前同样要进行检测:

void Free(void *ptr)
{
    void *all_area_ptr = ptr - LEN_AREA_LEN - BEFORE_RED_AREA_LEN;

    ///
    printf("[%s]before_red_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(all_area_ptr)));
    assert(*((unsigned int*)(all_area_ptr)) == BEFORE_RED_AREA_DATA);

    ///
    size_t __size = *((unsigned int*)(all_area_ptr + BEFORE_RED_AREA_LEN));

    ///
    printf("[%s]before_red_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(all_area_ptr + BEFORE_RED_AREA_LEN + LEN_AREA_LEN + __size)));
    assert(*((unsigned int*)(all_area_ptr + BEFORE_RED_AREA_LEN + LEN_AREA_LEN + __size)) == AFTER_RED_AREA_DATA);

    ///
    free(all_area_ptr);
}

我们使用这种方法检测上面的 malloc与strcpy搭配使用不当导致缓冲区溢出 的例子:

可以看到,这个例子踩了后红区,把后红区数据修改为了 0x55667700 ,触发断言程序终止。

测试代码:

// 公众号:嵌入式大杂烩
#include 
#include 
#include 
#include 
#include 

#define BEFORE_RED_AREA_LEN  (4)            ///
#define AFTER_RED_AREA_LEN   (4)            ///
#define LEN_AREA_LEN         (4)            ///

#define BEFORE_RED_AREA_DATA (0x11223344u)  ///
#define AFTER_RED_AREA_DATA  (0x55667788u)  ///

void *Malloc(size_t __size)
{
    ///
    void *ptr = malloc(BEFORE_RED_AREA_LEN + AFTER_RED_AREA_LEN + __size + LEN_AREA_LEN);
    if (NULL == ptr)
    {
        printf("[%s]malloc error\n", __FUNCTION__);
        return NULL;
    }

    ///
    *((unsigned int*)(ptr)) = BEFORE_RED_AREA_DATA;     

    ///
    *((unsigned int*)(ptr + BEFORE_RED_AREA_LEN)) = __size;  

    ///
    *((unsigned int*)(ptr + BEFORE_RED_AREA_LEN + LEN_AREA_LEN + __size)) = AFTER_RED_AREA_DATA;  

    ///
    void *data_area_ptr = (ptr + BEFORE_RED_AREA_LEN + LEN_AREA_LEN);
    return data_area_ptr;
}

void CheckMem(void *ptr, size_t __size)
{
    void *data_area_ptr = ptr;

    ///
    printf("[%s]before_red_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(data_area_ptr - LEN_AREA_LEN - BEFORE_RED_AREA_LEN)));
    assert(*((unsigned int*)(data_area_ptr - LEN_AREA_LEN - BEFORE_RED_AREA_LEN)) == BEFORE_RED_AREA_DATA);

    ///
    printf("[%s]len_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(data_area_ptr - LEN_AREA_LEN)));
    assert(*((unsigned int*)(data_area_ptr - LEN_AREA_LEN)) == __size); 

    ///
    printf("[%s]after_red_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(data_area_ptr + __size)));
    assert(*((unsigned int*)(data_area_ptr + __size)) == AFTER_RED_AREA_DATA); 
}

void Free(void *ptr)
{
    void *all_area_ptr = ptr - LEN_AREA_LEN - BEFORE_RED_AREA_LEN;

    ///
    printf("[%s]before_red_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(all_area_ptr)));
    assert(*((unsigned int*)(all_area_ptr)) == BEFORE_RED_AREA_DATA);

    ///
    size_t __size = *((unsigned int*)(all_area_ptr + BEFORE_RED_AREA_LEN));

    ///
    printf("[%s]before_red_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(all_area_ptr + BEFORE_RED_AREA_LEN + LEN_AREA_LEN + __size)));
    assert(*((unsigned int*)(all_area_ptr + BEFORE_RED_AREA_LEN + LEN_AREA_LEN + __size)) == AFTER_RED_AREA_DATA);

    ///
    free(all_area_ptr);
}

int main (void






请到「今天看啥」查看全文