我目前正在从事一个嵌入式项目(STM32F103RB,CooCox CoIDE v.1.7.6和arm-none-eabi-gcc 4.8,2013q4),当内存满时,我正在试图了解malloc()如何在普通C上运行。
我的STM32有20 of = 0x5000Bytes的RAM,0x200用于堆栈。
#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返回似乎行不通。
谢谢。
发布于 2014-03-15 11:42:38
看起来malloc根本不做任何检查。您所遇到的错误来自于硬件检测到无效地址的写入,该地址很可能来自malloc本身。
当malloc分配内存时,它从内部池中获取一个块,并将其返回给您。但是,它需要存储一些信息,以便free函数能够完成解除分配。通常,这是块的实际长度。为了保存这些信息,malloc从块本身的开头取几个字节,在那里写入信息,并将地址返回到它编写自己信息的位置。
例如,假设您请求一个10字节的块。malloc将获取一个可用的16字节块,例如在0x3200..0x320F地址处,将长度(即16)写入字节1和2,并将0x3202返回给您。现在,您的程序可以使用从0x3202到0x320B的十个字节。其他四个字节也是可用的--如果调用realloc并请求14个字节,就不会进行重新分配。
关键是当malloc将长度写入即将返回给您的内存块时:它写入的地址必须是有效的。看起来,在第18次迭代之后,下一个块的地址是负值(这将转换为非常大的正数),因此CPU捕获写入,并触发硬故障。
在堆和堆栈相互增长的情况下,在允许您使用内存的最后一个字节时,没有可靠的方法来检测内存不足,这通常是非常理想的事情。malloc无法预测分配后将使用多少堆栈,因此它甚至不尝试。这就是为什么在大多数情况下,字节计数都在您身上的原因。
通常,在嵌入式硬件上,当空间限制在几十千字节时,您可以避免在“任意”位置调用malloc。相反,您使用一些预先计算的限制预先分配所有内存,并将其打包到需要它的结构中,然后永远不再调用malloc。
发布于 2014-03-15 12:12:22
您的程序很可能因为非法内存访问而崩溃,这几乎总是合法内存访问的间接(后续)结果,但您不打算执行该结果。
例如(这也是我对您的系统上发生的事情的猜测):
您的堆很可能在堆栈之后立即开始。现在,假设main中有堆栈溢出。然后,您在main中执行的操作之一(对您来说自然是一种合法操作)用一些“垃圾”数据覆盖堆的开头。
因此,下次尝试从堆中分配内存时,指向下一个可用内存块的指针不再有效,最终导致内存访问冲突。
因此,首先,我强烈建议您将堆栈大小从0x200字节增加到0x400字节。这通常是在链接器命令文件中或通过IDE在项目的链接器设置中定义的。
如果您的项目位于IAR上,那么您可以在icf文件中更改它:
define symbol __ICFEDIT_size_cstack__ = 0x400除此之外,我建议您在HardFault_Handler中添加代码,以便在崩溃之前重建调用堆栈和注册值。这可能允许您跟踪运行时错误,并准确地发现它发生在何处。
在文件‘startup_stm32f03x.s’中,确保有以下代码:
EXTERN HardFault_Handler_C ; this declaration is probably missing
__tx_vectors ; this declaration is probably there
DCD HardFault_Handler然后,在同一个文件中添加以下中断处理程序(所有其他处理程序都位于其中):
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:
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故障异常和寄存器”部分,以便了解问题,或者在这里发布输出,如果您需要进一步的帮助。
更新:
除上述所有内容外,请确保正确定义了堆的基址。它可能是在项目设置中硬编码的(通常是在数据部分和堆栈之后)。但是,它也可以在运行时,在程序的初始化阶段确定。通常,您需要检查数据部分的基址和程序的堆栈(在构建项目后创建的映射文件中),并确保堆不与其中任何一个重叠。
我曾经遇到过这样一种情况,即堆的基址被设置为一个常量地址,这是一个很好的开始。但后来我逐渐增加了数据部分的大小,向程序中添加了全局变量。堆栈位于数据段之后,并且随着数据段的增大,它“向前移动”,所以它们都没有问题。但最终,堆被分配到堆栈的“顶部”部分。因此,在某个时候,堆操作开始覆盖堆栈上的变量,堆栈操作开始覆盖堆的内容。
发布于 2019-03-17 09:18:35
arm-none-eabi-*工具链发行版包括新库 C库。当将newlib配置为嵌入式系统时,用户程序必须使用函数才能正常工作。
malloc() 完全依赖于 _sbrk()来确定堆内存的起始位置和结束位置。对_sbrk()的第一次调用返回堆的开始,如果没有所需的内存量,则后续的调用应该返回 malloc(),然后malloc()将返回NULL到应用程序。您的_sbrk()看起来坏了,因为它显然允许您分配比可用内存更多的内存。您应该能够修复它,以便在堆与堆栈发生冲突之前返回-1。
https://stackoverflow.com/questions/22422733
复制相似问题