web-dev-qa-db-fra.com

comportement malloc sur un système embarqué

Je travaille actuellement sur un projet intégré (STM32F103RB, CooCox CoIDE v.1.7.6 avec arm-none-eabi-gcc 4.8 2013q4) et j'essaie de comprendre comment malloc() se comporte en clair C lorsque le RAM est plein.

Mon STM32 a 20 Ko = 0x5000 octets de RAM, 0x200 sont utilisés pour la pile.

#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;
}

Je m'attendrais à ce que malloc() retourne NULL dès que le tas est trop petit pour allouer:

0x5000 (RAM) - 0x83C (Bss) - 0x200 (Pile) = 0x45C4 (Tas)

Ainsi, lors de l'exécution de la malloc() pour la 18e fois. Un élément fait 1024 = 0x400 Octets de grande taille.

Mais à la place, l'UC appelle la HardFault_Handler(void) après la 18e fois (pas même la MemManager_Handler(void))

Quelqu'un a-t-il un conseil pour prévoir un échec de malloc() - car attendre un retour de NULL ne semble pas fonctionner.

Je vous remercie.

28
Boern

Il ne semble pas que malloc effectue des vérifications. La faute que vous obtenez vient du matériel détectant une écriture dans une adresse non valide, qui provient probablement de malloc lui-même.

Lorsque malloc alloue de la mémoire, il prend un morceau de son pool interne et vous le renvoie. Cependant, il doit stocker des informations pour que la fonction free puisse terminer la désallocation. Habituellement, c'est la longueur réelle du morceau. Afin d'enregistrer ces informations, malloc prend quelques octets depuis le début du bloc lui-même, y écrit les informations et vous renvoie l'adresse après l'endroit où il a écrit ses propres informations.

Par exemple, supposons que vous ayez demandé un bloc de 10 octets. malloc récupérerait un bloc de 16 octets disponible, par exemple, aux adresses 0x3200..0x320F, écrivez la longueur (c'est-à-dire 16) dans les octets 1 et 2, et retournez 0x3202 de retour à vous. Maintenant, votre programme peut utiliser dix octets de 0x3202 à 0x320B. Les quatre autres octets sont également disponibles - si vous appelez realloc et demandez 14 octets, il n'y aura pas de réaffectation.

Le point crucial survient lorsque malloc écrit la longueur dans le bloc de mémoire qu'il est sur le point de vous renvoyer: l'adresse à laquelle il écrit doit être valide. Il semble qu'après la 18e itération, l'adresse du bloc suivant soit négative (ce qui se traduit par un très grand positif), donc le CPU intercepte l'écriture et déclenche la panne matérielle.

Dans les situations où le tas et la pile se rapprochent, il n'existe aucun moyen fiable de détecter une mémoire insuffisante tout en vous permettant d'utiliser chaque dernier octet de mémoire, ce qui est souvent très souhaitable. malloc ne peut pas prédire la quantité de pile que vous allez utiliser après l'allocation, donc il n'essaie même pas. C'est pourquoi le comptage d'octets dans la plupart des cas est sur vous.

En général, sur du matériel embarqué lorsque l'espace est limité à quelques dizaines de kilo-octets, vous évitez les appels malloc dans des emplacements "arbitraires". Au lieu de cela, vous pré-allouez toute votre mémoire à l'avance à l'aide de certaines limites pré-calculées, vous la répartissez dans les structures qui en ont besoin et vous n'appelez plus jamais malloc.

21
dasblinkenlight

Votre programme se bloque très probablement à cause d'un accès illégal à la mémoire , qui est presque toujours le résultat indirect (ultérieur) d'un accès légal à la mémoire , mais que vous n'aviez pas l'intention d'effectuer.

Par exemple (ce qui est également ma supposition quant à ce qui se passe sur votre système):

Votre tas commence très probablement juste après la pile. Supposons maintenant que vous ayez un débordement de pile dans main. Ensuite, l'une des opérations que vous effectuez dans main, qui est naturellement une opération légale en ce qui vous concerne, remplace le début du tas par des données "indésirables".

Par conséquent, la prochaine fois que vous tentez d'allouer de la mémoire à partir du segment de mémoire, le pointeur vers le prochain bloc de mémoire disponible n'est plus valide, ce qui conduit éventuellement à une violation d'accès à la mémoire.

Donc, pour commencer, je vous recommande fortement d'augmenter la taille de la pile de 0x200 octets à 0x400 octets. Ceci est généralement défini dans le fichier de commande de l'éditeur de liens, ou via l'EDI, dans les paramètres de l'éditeur de liens du projet.

Si votre projet est sur IAR, vous pouvez le modifier dans le fichier icf:

define symbol __ICFEDIT_size_cstack__ = 0x400

En dehors de cela, je vous suggère d'ajouter du code dans votre HardFault_Handler, Afin de reconstruire la pile d'appels et d'enregistrer les valeurs avant le crash. Cela peut vous permettre de retracer l'erreur d'exécution et de savoir exactement où elle s'est produite.

Dans le fichier 'startup_stm32f03xx.s', assurez-vous que vous disposez du code suivant:

EXTERN  HardFault_Handler_C        ; this declaration is probably missing

__tx_vectors                       ; this declaration is probably there
    DCD     HardFault_Handler

Ensuite, dans le même fichier, ajoutez le gestionnaire d'interruption suivant (où se trouvent tous les autres gestionnaires):

    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

Ensuite, dans le fichier 'stm32f03xx.c', ajoutez l'ISR suivant:

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);
}

Si vous ne pouvez pas utiliser printf au moment de l'exécution lorsque cette interruption Hard-Fault spécifique se produit, enregistrez toutes les données ci-dessus dans un tampon global à la place, afin de pouvoir les afficher après avoir atteint la fonction while (1).

Ensuite, reportez-vous à la section "Exceptions et registres de pannes Cortex-M" sur http://www.keil.com/appnotes/files/apnt209.pdf afin de comprendre le problème, ou publiez le sortie ici si vous souhaitez plus d'aide.

MISE À JOUR:

En plus de tout ce qui précède, assurez-vous que l'adresse de base du tas est définie correctement. Il est peut-être codé en dur dans les paramètres du projet (généralement juste après la section de données et la pile). Mais il peut également être déterminé lors de l'exécution, lors de la phase d'initialisation de votre programme. En général, vous devez vérifier les adresses de base de la section de données et la pile de votre programme (dans le fichier de mappage créé après la construction du projet), et assurez-vous que le tas ne chevauche ni l'un ni l'autre.

J'ai eu une fois un cas où l'adresse de base du tas était définie sur une adresse constante, ce qui était bien pour commencer. Mais ensuite, j'ai progressivement augmenté la taille de la section de données, en ajoutant des variables globales au programme. La pile était située juste après la section de données, et elle "avançait" à mesure que la section de données grossissait, il n'y avait donc aucun problème avec l'une ou l'autre. Mais finalement, le tas a été alloué "au-dessus" d'une partie de la pile. Ainsi, à un moment donné, les opérations de segment ont commencé à remplacer les variables de la pile et les opérations de segment ont commencé à remplacer le contenu du segment.

4
barak manos

La distribution arm-none-eabi-* toolchain inclut la bibliothèque newlib C . Lorsque newlib est configuré pour un système embarqué, le programme utilisateur doit fournir une fonction _sbrk() pour le faire fonctionner correctement.

malloc() repose uniquement sur _sbrk() pour déterminer où la mémoire de tas commence et où elle se termine. Le tout premier appel à _sbrk() renvoie le début du segment de mémoire et les appels suivants doivent renvoyer -1 si le la quantité de mémoire requise n'est pas disponible, alors malloc() retournerait à son tour NULL à l'application. Votre _sbrk() semble cassée, car elle vous permet apparemment d'allouer plus de mémoire qu'il n'y en a de disponible. Vous devriez pouvoir le réparer pour qu'il renvoie -1 avant le tas devrait entrer en collision avec la pile.

3
berendi

Utilisation de la norme c malloc c'est très difficile à distinguer et malloc semble être bogué de mon point de vue. Vous pouvez donc gérer la mémoire en implémentant un malloc personnalisé à l'aide de votre adresse RAM.

Je ne suis pas sûr que cela puisse vous aider, mais j'ai fait quelques malloc personnalisés dans mon projet lié au contrôleur, c'est comme suit

#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;

Cette macro définit essentiellement pour RAM et ont choisi manuellement plus de numéros de bloc pour la taille de bloc qui nécessitent fréquemment d'allouer, comme 36 octets me demandaient plus, donc je prends plus de nombre pour cela.

C'est la fonction init pour 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;
}

Celui-ci pour l'allocation

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;
}

Celui-ci gratuitement

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:;

}

Après tout, vous pouvez utiliser la fonction ci-dessus comme

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

Ensuite, vous pouvez également regarder votre mémoire utilisée comme suit

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");
}

En général, vous avez d'abord calculé la mémoire, puis donnez ce que j'ai.

3
Jayesh Bhoi

Ici vous pouvez trouver comment je pourrais "forcer" malloc () à retourner NULL, si le tas est trop petit pour être alloué en fonction de la réponse précédente de berendi. J'ai estimé la quantité maximale de STACK et sur cette base, j'ai pu calculer l'adresse où la pile peut commencer dans le pire des cas.

#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
}
0
K. Simon