Подтвердить что ты не робот

Поведение malloc во встроенной системе

В настоящее время я работаю над встроенным проектом (STM32F103RB, CooCox CoIDE v.1.7.6 с arm-none-eabi-gcc 4.8 2013q4), и я пытаюсь понять, как malloc() ведет себя на простой C когда ОЗУ заполнено.

Мой STM32 имеет 20kB = 0x5000Bytes ОЗУ, 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 (куча)

Итак, при выполнении malloc() в 18-й раз. Один элемент: 1024 = 0x400 Байты большие.

Но вместо этого uC вызывает HardFault_Handler(void) после 18-го раза (даже не MemManager_Handler(void))

Есть ли у кого-нибудь совет, как прогнозировать сбой malloc(), так как ожидание возврата NULL не работает.

Спасибо.

4b9b3361

Ответ 1

Не похоже, что malloc выполняет какие-либо проверки вообще. Ошибка, которую вы получаете, связана с аппаратным обеспечением, обнаруживающим запись на недопустимый адрес, который, вероятно, поступает от самого malloc.

Когда malloc выделяет память, он берет кусок из своего внутреннего пула и возвращает его вам. Однако для того, чтобы завершить освобождение, необходимо сохранить некоторую информацию для функции free. Обычно это фактическая длина куска. Чтобы сохранить эту информацию, malloc берет несколько байтов с самого начала куска, записывает там информацию и возвращает вам адрес мимо места, где он написал свою собственную информацию.

Например, скажем, вы попросили 10-байтовый кусок. malloc будет захватывать доступный 16-байтовый фрагмент, скажем, по адресам 0x3200..0x320F, записать длину (например, 16) в байты 1 и 2 и вернуть 0x3202 к вам. Теперь ваша программа может использовать десять байт от 0x3202 до 0x320B. Остальные четыре байта также доступны - если вы вызываете realloc и запрашиваете 14 байтов, перераспределения не будет.

Важнейшим моментом является то, что malloc записывает длину в кусок памяти, которую он собирается возвратить вам: адрес, на который он пишет, должен быть действительным. Похоже, что после 18-й итерации адрес следующего фрагмента отрицательный (что приводит к очень большому положительному значению), поэтому процессор ломает запись и вызывает жесткую ошибку.

В ситуациях, когда куча и стек растут друг к другу, нет надежного способа обнаружить нехватку памяти, позволяя вам использовать каждый последний байт памяти, что часто является очень желанной вещью. malloc не может предсказать, сколько стека вы собираетесь использовать после распределения, поэтому он даже не пытается. Вот почему подсчет байтов в большинстве случаев зависит от вас.

В общем, на встроенных аппаратных средствах, когда пространство ограничено несколькими десятками килобайт, вы избегаете вызовов malloc в "произвольных" местах. Вместо этого вы предварительно распределяете всю свою память заранее, используя некоторые предварительно рассчитанные лимиты, и отправляйте ее в структуры, которые в ней нуждаются, и никогда не вызывайте malloc снова.

Ответ 2

Ваша программа, скорее всего, сбой из-за незаконного доступа к памяти, что почти всегда является косвенным (последующим) результатом доступ к законной памяти, но тот, который вы сделали не намерены выполнять.

Например (это также мое предположение о том, что происходит в вашей системе):

Ваша куча, скорее всего, начинается сразу после стека. Теперь предположим, что у вас есть переполнение стека в main. Тогда одна из операций, которые вы выполняете в main, которая, естественно, является юридической операцией, насколько это возможно, переопределяет начало кучи некоторыми "мусорными" данными.

В качестве следующего результата при следующем попытке выделения памяти из кучи указатель на следующий доступный фрагмент памяти больше не действует, что в конечном итоге приводит к нарушению доступа к памяти.

Итак, для начала я настоятельно рекомендую увеличить размер стека от 0x200 байт до 0x400 байт. Обычно это определяется в файле компоновщика ссылок или через IDE в настройках компоновщика проекта.

Если ваш проект включен в IAR, вы можете изменить его в файле icf:

define symbol __ICFEDIT_size_cstack__ = 0x400

Кроме этого, я предлагаю вам добавить код в свой HardFault_Handler, чтобы восстановить стек вызовов и зарегистрировать значения до сбоя. Это может позволить вам отслеживать ошибку времени выполнения и выяснить, где именно произошло.

В файле "startup_stm32f03xx.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

Затем в файле 'stm32f03xx.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).

Затем обратитесь к разделу "Исключения и регистры ошибок Cortex-M" в http://www.keil.com/appnotes/files/apnt209.pdf, чтобы понять проблему или опубликовать выход здесь, если вы хотите получить дополнительную помощь.

UPDATE:

В дополнение ко всему вышесказанному убедитесь, что базовый адрес кучи определен правильно. Возможно, он жестко закодирован в настройках проекта (как правило, сразу после раздела данных и стека). Но это также можно определить во время выполнения на этапе инициализации вашей программы. В общем, вам нужно проверить базовые адреса раздела данных и стек вашей программы (в файле карты, созданной после создания проекта), и убедиться, что куча не перекрывает ни одну из них.

У меня когда-то был случай, когда базовый адрес кучи был установлен на постоянный адрес, что было хорошо для начала. Но затем я постепенно увеличивал размер раздела данных, добавляя в программу глобальные переменные. Стек был расположен сразу после раздела данных, и он "продвигался вперед" по мере того, как секция данных увеличивалась, поэтому никаких проблем ни с одним из них не возникало. Но в итоге куча была выделена "поверх" части стека. Поэтому в какой-то момент кучи-операции начали переопределять переменные в стеке, а операции стека стали переопределять содержимое кучи.

Ответ 3

Используя стандартный c malloc, очень трудно различить, а malloc кажется ошибкой из моего представления. Таким образом, вы можете управлять памятью, выполняя некоторые пользовательские malloc, используя ваш адрес RAM.

Я не уверен, что это поможет вам, но я сделал некоторые пользовательские malloc в моем проекте, связанном с контроллером, следующим образом

#define LENGTH_36_NUM   (44)
#define LENGTH_52_NUM   (26)
#define LENGTH_64_NUM   (4)
#define LENGTH_128_NUM  (5)
#define LENGTH_132_NUM  (8)
#define LENGTH_256_NUM  (8)
#define LENGTH_512_NUM  (18)    
#define LENGTH_640_NUM  (8) 
#define LENGTH_1536_NUM (6) 

#define CUS_MEM_USED        (1)
#define CUS_MEM_NO_USED     (0)

#define CALC_CNT    (0)
#define CALC_MAX    (1)

#define __Ram_Loc__         (0x20000000) ///This is my RAM address
#define __TOP_Ram_Loc__     (0x20000000 + 0x8000 -0x10) //Total 32K RAM and last 16 bytes reserved for some data storage

typedef struct _CUS_MEM_BLOCK_S {
    char used;
    int block_size;
    char *ptr;
    char *next;
} cus_mem_block_s;

static struct _MEM_INFO_TBL_S {
    int block_size;
    int num_max;
    cus_mem_block_s *wm_head;
    int calc[2];
} memInfoTbl[] = {

 {36,  LENGTH_36_NUM  , 0, {0,0} },
 {52,  LENGTH_52_NUM  , 0, {0,0} },
 {64,  LENGTH_64_NUM  , 0, {0,0} },
 {128, LENGTH_128_NUM , 0, {0,0} },
 {132, LENGTH_132_NUM , 0, {0,0} },
 {256, LENGTH_256_NUM , 0, {0,0} },
 {512, LENGTH_512_NUM , 0, {0,0} },
 {640, LENGTH_640_NUM , 0, {0,0} },
 {1536,LENGTH_1536_NUM, 0, {0,0} },
};
#define MEM_TBL_MAX     (sizeof(memInfoTbl)/sizeof(struct _MEM_INFO_TBL_S))

BOOL MemHeapHasBeenInitialised = FALSE;

Этот макрос определяет для адреса ОЗУ и вручную выберет больше номера блока для размера блока, который часто требуется для распределения. Как и 36 байт, мне нужно больше, поэтому я беру на него больше номера.

Это функция init для mem init

void cus_MemInit(void)
{
    int i,j;
    cus_mem_block_s *head=NULL;
    unsigned int addr;

    addr = __Ram_Loc__;

    for(i=0; i<MEM_TBL_MAX; i++) 
    {
        head = (char *)addr;
        memInfoTbl[i].wm_head = head;
        for(j=0;j<memInfoTbl[i].num_max; j++)
        {
            head->used =CUS_MEM_NO_USED;
            head->block_size = memInfoTbl[i].block_size;
            head->ptr = (char *)(addr + sizeof(cus_mem_block_s));
            addr += (memInfoTbl[i].block_size + sizeof(cus_mem_block_s));
            head->next =(char *)addr;
            head = head->next;
            if(head > __TOP_Ram_Loc__) 
            {
                printf("%s:error.\n",__FUNCTION__);
                return;
            }
        }
    }
    head->ptr = 0;
    head->block_size = 0;
    head->next = __Ram_Loc__;

    MemHeapHasBeenInitialised=TRUE;
}

Этот для распределения

void* CUS_Malloc( int wantedSize )
{
    void *pwtReturn = NULL;
    int i;
    cus_mem_block_s *head;

    if(MemHeapHasBeenInitialised == FALSE) 
            goto done_exit;

    for(i=0; i<MEM_TBL_MAX; i++)
    {
        if(wantedSize <= memInfoTbl[i].block_size)
        {
            head = memInfoTbl[i].wm_head;
            while(head->ptr)
            {
                if(head->used == CUS_MEM_NO_USED)
                {
                    head->used = CUS_MEM_USED;
                    pwtReturn = head->ptr;
                    goto done;
                }
                head = head->next;
            }
            goto done;

        }
    }
 done:


    if(pwtReturn)
    {
        for(i=0; i<MEM_TBL_MAX; i++)
        {
            if(memInfoTbl[i].block_size == head->block_size)
            {

                memInfoTbl[i].calc[CALC_CNT]++;
                if(memInfoTbl[i].calc[CALC_CNT] > memInfoTbl[i].calc[CALC_MAX] )
                    memInfoTbl[i].calc[CALC_MAX]=memInfoTbl[i].calc[CALC_CNT];
                break;
            }
        }
    }
  done_exit:
    return pwtReturn;
}

Этот бесплатный

void CUS_Free(void *pm)
{
    cus_mem_block_s *head;
    char fault=0;


    if( (pm == NULL) || (MemHeapHasBeenInitialised == FALSE) )
        goto done;
    if( (pm < __RamAHB32__) && (pm > __TOP_Ram_Loc__) )
    {
        printf("%s:over memory range\n",__FUNCTION__);
        goto done;
    }

    head = pm-sizeof(cus_mem_block_s);


    if(head->used)
        head->used = CUS_MEM_NO_USED;
    else
    {
        printf("%s:free error\n",__FUNCTION__);
        fault=1;
    }


    if(fault)
        goto done;
    int i;
    for(i=0;i<MEM_TBL_MAX;i++)
    {
        if(memInfoTbl[i].block_size == head->block_size)
        {
            memInfoTbl[i].calc[CALC_CNT]--;
            goto done;
        }
    }
 done:;

}

В конце концов вы можете использовать вышеприведенную функцию, например

void *mem=NULL;
mem=CUS_Malloc(wantedsize);

Затем вы также можете смотреть свою используемую память следующим образом

void CUS_MemShow(void)
{
    int i;
    int block_size;
    int block_cnt[MEM_TBL_MAX];
    int usedSize=0, totalSize=0;
    cus_mem_block_s *head;

    if(MemHeapHasBeenInitialised == FALSE)
            return;

    memset(block_cnt, 0, sizeof(block_cnt));

    head = memInfoTbl[0].wm_head;
    i=0;
    block_size = head->block_size;
    vTaskSuspendAll();
    while( head->ptr !=0)
    {
        if(head->used == CUS_MEM_USED )
        {
            block_cnt[i]++;
            usedSize +=head->block_size;
        }
        usedSize += sizeof(cus_mem_block_s);

        totalSize += (head->block_size+ sizeof(cus_mem_block_s));

        /* change next memory block */  
        head = head->next;
        if( block_size != head->block_size)
        {
            block_size = head->block_size;
            i++;
        }
    }
    xTaskResumeAll();

    usedSize += sizeof(cus_mem_block_s);
    totalSize+= sizeof(cus_mem_block_s);

    dprintf("----Memory Information----\n");

    for(i=0; i<MEM_TBL_MAX; i++) {
        printf("block %d used=%d/%d (max %d)\n",
                    memInfoTbl[i].block_size, block_cnt[i], 
                    memInfoTbl[i].num_max,
                    memInfoTbl[i].calc[CALC_MAX]);
    }

    printf("used memory=%d\n",usedSize);
    printf("free memory=%d\n",totalSize-usedSize);
    printf("total memory=%d\n",totalSize);
    printf("--------------------------\n");
}

В общем, предварительно вычисленная память сначала, а затем дайте, как я.

Ответ 4

В дистрибутив arm-none-eabi-* входит библиотека newlib C. Когда newlib настроен для встроенной системы, тогда пользовательская программа должна предоставить функцию _sbrk() чтобы она работала правильно.

malloc() полагается исключительно на _sbrk() чтобы выяснить, где начинается куча памяти и где она заканчивается. Самый первый вызов _sbrk() возвращает начало кучи, а последующие вызовы должны возвращать -1 если требуемый объем памяти недоступен, тогда malloc() в свою очередь возвращает NULL приложению. Ваш _sbrk() выглядит испорченным, потому что он, очевидно, позволяет вам выделить больше памяти, чем доступно. Вы должны иметь возможность исправить это так, чтобы он возвращал -1 до того, как куча, как ожидается, столкнется со стеком.

Ответ 5

Здесь вы можете найти, как я мог "заставить" malloc() возвращать NULL, если куча слишком мала для размещения на основе предыдущего ответа Беренди. Я оценил максимальное количество STACK и на основании этого мог бы рассчитать адрес, с которого стек может начаться в худшем случае.

#define STACK_END_ADDRESS       0x20020000
#define STACK_MAX_SIZE              0x0400
#define STACK_START_ADDRESS   (STACK_END_ADDRESS - STACK_MAX_SIZE)

void * _sbrk_r(
   struct _reent *_s_r,
   ptrdiff_t nbytes)
{
   char  *base;     /*  errno should be set to  ENOMEM on error */

   if (!heap_ptr) { /*  Initialize if first time through.       */
      heap_ptr = end;
   }
   base = heap_ptr; /*  Point to end of heap.           */
   #ifndef STACK_START_ADDRESS
      heap_ptr += nbytes;   /*  Increase heap.              */
      return base;      /*  Return pointer to start of new heap area.   */
   #else
      /* End of heap mustn't exceed beginning of stack! */        
      if (heap_ptr <= (char *) (STACK_START_ADDRESS - nbytes) ) {  
         heap_ptr += nbytes;    /*  Increase heap.              */
         return base;       /*  Return pointer to start of new heap area.   */
      } else {
         return (void *) -1;         /*   Return -1 means that memory run out  */
      }
   #endif // STACK_START_ADDRESS
}