首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >嵌入式系统上的malloc行为

嵌入式系统上的malloc行为
EN

Stack Overflow用户
提问于 2014-03-15 10:46:33
回答 5查看 28.8K关注 0票数 31

我目前正在从事一个嵌入式项目(STM32F103RB,CooCox CoIDE v.1.7.6和arm-none-eabi-gcc 4.8,2013q4),当内存满时,我正在试图了解malloc()如何在普通C上运行。

我的STM32有20 of = 0x5000Bytes的RAM,0x200用于堆栈。

代码语言:javascript
复制
#include <stdlib.h>
#include "stm32f10x.h"

struct list_el {
   char weight[1024];
};

typedef struct list_el item;

int main(void)
{
    item * curr;

    // allocate until RAM is full
    do {
        curr = (item *)malloc(sizeof(item));
    } while (curr != NULL);

    // I know, free() is missing. Program is supposed to crash

    return 0;
}

当堆太小而无法分配时,我希望malloc()返回NULL

0x5000 (内存)- 0x83C (bss) - 0x200 (堆栈)= 0x45C4 (堆)

因此,当第18次执行malloc()时。其中一项是1024=0x400 Bytes大。

但是uC却在第18次之后调用了HardFault_Handler(void) (甚至MemManager_Handler(void))。

有谁有关于如何预测malloc()失败的建议吗?因为等待NULL返回似乎行不通。

谢谢。

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2014-03-15 11:42:38

看起来malloc根本不做任何检查。您所遇到的错误来自于硬件检测到无效地址的写入,该地址很可能来自malloc本身。

malloc分配内存时,它从内部池中获取一个块,并将其返回给您。但是,它需要存储一些信息,以便free函数能够完成解除分配。通常,这是块的实际长度。为了保存这些信息,malloc从块本身的开头取几个字节,在那里写入信息,并将地址返回到它编写自己信息的位置。

例如,假设您请求一个10字节的块。malloc将获取一个可用的16字节块,例如在0x3200..0x320F地址处,将长度(即16)写入字节1和2,并将0x3202返回给您。现在,您的程序可以使用从0x32020x320B的十个字节。其他四个字节也是可用的--如果调用realloc并请求14个字节,就不会进行重新分配。

关键是当malloc将长度写入即将返回给您的内存块时:它写入的地址必须是有效的。看起来,在第18次迭代之后,下一个块的地址是负值(这将转换为非常大的正数),因此CPU捕获写入,并触发硬故障。

在堆和堆栈相互增长的情况下,在允许您使用内存的最后一个字节时,没有可靠的方法来检测内存不足,这通常是非常理想的事情。malloc无法预测分配后将使用多少堆栈,因此它甚至不尝试。这就是为什么在大多数情况下,字节计数都在您身上的原因。

通常,在嵌入式硬件上,当空间限制在几十千字节时,您可以避免在“任意”位置调用malloc。相反,您使用一些预先计算的限制预先分配所有内存,并将其打包到需要它的结构中,然后永远不再调用malloc

票数 28
EN

Stack Overflow用户

发布于 2014-03-15 12:12:22

您的程序很可能因为非法内存访问而崩溃,这几乎总是合法内存访问的间接(后续)结果,但您不打算执行该结果。

例如(这也是我对您的系统上发生的事情的猜测):

您的堆很可能在堆栈之后立即开始。现在,假设main中有堆栈溢出。然后,您在main中执行的操作之一(对您来说自然是一种合法操作)用一些“垃圾”数据覆盖堆的开头。

因此,下次尝试从堆中分配内存时,指向下一个可用内存块的指针不再有效,最终导致内存访问冲突。

因此,首先,我强烈建议您将堆栈大小从0x200字节增加到0x400字节。这通常是在链接器命令文件中或通过IDE在项目的链接器设置中定义的。

如果您的项目位于IAR上,那么您可以在icf文件中更改它:

代码语言:javascript
复制
define symbol __ICFEDIT_size_cstack__ = 0x400

除此之外,我建议您在HardFault_Handler中添加代码,以便在崩溃之前重建调用堆栈和注册值。这可能允许您跟踪运行时错误,并准确地发现它发生在何处。

在文件‘startup_stm32f03x.s’中,确保有以下代码:

代码语言:javascript
复制
EXTERN  HardFault_Handler_C        ; this declaration is probably missing

__tx_vectors                       ; this declaration is probably there
    DCD     HardFault_Handler

然后,在同一个文件中添加以下中断处理程序(所有其他处理程序都位于其中):

代码语言:javascript
复制
    PUBWEAK HardFault_Handler
    SECTION .text:CODE:REORDER(1)
HardFault_Handler
    TST LR, #4
    ITE EQ
    MRSEQ R0, MSP
    MRSNE R0, PSP
    B HardFault_Handler_C

然后,在文件‘stm32f03x.c’中添加以下ISR:

代码语言:javascript
复制
void HardFault_Handler_C(unsigned int* hardfault_args)
{
    printf("R0    = 0x%.8X\r\n",hardfault_args[0]);         
    printf("R1    = 0x%.8X\r\n",hardfault_args[1]);         
    printf("R2    = 0x%.8X\r\n",hardfault_args[2]);         
    printf("R3    = 0x%.8X\r\n",hardfault_args[3]);         
    printf("R12   = 0x%.8X\r\n",hardfault_args[4]);         
    printf("LR    = 0x%.8X\r\n",hardfault_args[5]);         
    printf("PC    = 0x%.8X\r\n",hardfault_args[6]);         
    printf("PSR   = 0x%.8X\r\n",hardfault_args[7]);         
    printf("BFAR  = 0x%.8X\r\n",*(unsigned int*)0xE000ED38);
    printf("CFSR  = 0x%.8X\r\n",*(unsigned int*)0xE000ED28);
    printf("HFSR  = 0x%.8X\r\n",*(unsigned int*)0xE000ED2C);
    printf("DFSR  = 0x%.8X\r\n",*(unsigned int*)0xE000ED30);
    printf("AFSR  = 0x%.8X\r\n",*(unsigned int*)0xE000ED3C);
    printf("SHCSR = 0x%.8X\r\n",SCB->SHCSR);                
    while (1);
}

如果在执行过程中发生此特定的硬故障中断时无法使用printf,则将所有上述数据保存在全局缓冲区中,以便在到达while (1)后查看该数据。

然后,请参阅http://www.keil.com/appnotes/files/apnt209.pdf的“Cortex-M故障异常和寄存器”部分,以便了解问题,或者在这里发布输出,如果您需要进一步的帮助。

更新:

除上述所有内容外,请确保正确定义了堆的基址。它可能是在项目设置中硬编码的(通常是在数据部分和堆栈之后)。但是,它也可以在运行时,在程序的初始化阶段确定。通常,您需要检查数据部分的基址和程序的堆栈(在构建项目后创建的映射文件中),并确保堆不与其中任何一个重叠。

我曾经遇到过这样一种情况,即堆的基址被设置为一个常量地址,这是一个很好的开始。但后来我逐渐增加了数据部分的大小,向程序中添加了全局变量。堆栈位于数据段之后,并且随着数据段的增大,它“向前移动”,所以它们都没有问题。但最终,堆被分配到堆栈的“顶部”部分。因此,在某个时候,堆操作开始覆盖堆栈上的变量,堆栈操作开始覆盖堆的内容。

票数 5
EN

Stack Overflow用户

发布于 2019-03-17 09:18:35

arm-none-eabi-*工具链发行版包括新库 C库。当将newlib配置为嵌入式系统时,用户程序必须使用函数才能正常工作。

malloc() 完全依赖于 _sbrk()来确定堆内存的起始位置和结束位置。对_sbrk()的第一次调用返回堆的开始,如果没有所需的内存量,则后续的调用应该返回 malloc(),然后malloc()将返回NULL到应用程序。您的_sbrk()看起来坏了,因为它显然允许您分配比可用内存更多的内存。您应该能够修复它,以便在堆与堆栈发生冲突之前返回-1

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/22422733

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档