Come monitorare in continuo l’analisi dello stack durante lo sviluppo

Con le loro migliaia o addirittura milioni di righe di codice, i software embedded stanno diventando sempre più sofisticati, ma l’obiettivo generale di ottenere software robusti, precisi e veloci rimane invariato. Per essere rapido, un software deve poter gestire in modo ottimale le risorse di memorie e CPU disponibili, il che può rappresentare una sfida nei sistemi embedded con capacità di memoria, soprattutto in termini di RAM, limitata. È importante analizzare l’utilizzo della RAM tramite l’analisi di heap e stack. La stima manuale del carico di heap e stack da parte degli sviluppatori è sempre più difficile, anche per i programmi di piccole dimensioni. Stime errate possono portare a casi di stack overflow e a comportamenti non definiti. Ecco perché alcuni standard di codifica attuano delle best practice sull’uso dell’allocazione della memoria per evitare overhead inutili. Tuttavia, lo stack continua a essere un componente necessario della RAM e deve pertanto essere utilizzato in modo ottimale.

Perché serve effettuare l’analisi dello stack per i sistemi embedded?

Lo stack overflow si verifica quando lo stack disponibile è più piccolo di quanto richiesto dal codice. Tuttavia, se l’ambiente viene configurato con uno stack più grande del necessario, si spreca memoria. È essenziale che gli sviluppatori stimino lo scenario peggiore di uso dello stack in applicazioni critiche per la sicurezza con continuità e coerenza per evitare che la RAM del software si esaurisca.

Rischio di una stima errata dello stack.

Rischio di una stima errata dello stack.

Come fare stima in base all’analisi dello stack?

Stima manuale dello stack

Sebbene in alcuni casi stimare manualmente l’analisi dello stack potrebbe essere utile, per i sistemi più complessi risulta complicato. Per farlo, occorre comprendere a fondo vari aspetti, tra cui la profondità delle chiamate a funzione e i dettagli relativi a tutte le variabili locali e alla dimensione dei frame di interrupt che si verificano in qualsiasi momento durante l’esecuzione. Questa procedura richiede molto tempo ed è soggetta a errori. Gli strumenti di analisi statica del codice possono rapidamente calcolare questi valori e non sarà necessario farlo manualmente.

Uso di analizzatori statici del codice

Gli sviluppatori possono prevedere l’uso dello stack con un analizzatore statico di codice. Gli strumenti di analisi sono in grado di analizzare la profondità delle chiamate a funzione, le stime degli stack sulle variabili locali e i parametri di return, gli interrupt annidati e la dimensione degli interrupt che si verificano durante l’esecuzione. Il vantaggio di usare un analizzatore statico di codice consiste nel fatto che tale strumento si occupa delle violazioni alle regole di codifica, dei difetti di runtime e della complessità della codifica, insieme alla stima dell’analisi dello stack. L’operazione richiede pochi minuti, per cui lo sviluppatore potrà risparmiare tempo rispetto al calcolo manuale del consumo dello stack.

Verifica e misurazioni su target

Gli analizzatori statici sono in grado di stimare il consumo dello stack durante lo sviluppo. Tuttavia, la cosa migliore è ottenere risultati sul consumo effettivo dello stack su hardware reali. Molti ambienti di sviluppo sono dotati di funzionalità di emulazione dell’hardware e offrono la possibilità di svolgere l’analisi dello stack in tempo reale. È importante svolgere l’analisi dello stack su hardware reali e creare scenari di overflow per testare routine fail-safe. A questo punto la domanda è: quando svolgere l’analisi dello stack con strumenti di analisi statica e quando su un target reale?

Quando svolgere l’analisi dello stack?

L’analisi dello stack è un processo continuo che viene svolto durante l’intero ciclo di sviluppo del software. Stimare l’uso dello stack solo alla fine del ciclo di sviluppo del software a opera di un team separato di valutazione della qualità potrebbe rappresentare un rischio per l’intera procedura di sviluppo. In più, risolvere i problemi nelle fasi avanzate del ciclo di sviluppo potrebbe essere sbagliato e dispendioso in termini di tempo, oltre a causare confusione nel determinare se affrontare le modifiche di progettazione su hardware o software. I momenti più importanti in cui svolgere l’analisi dello stack sono i seguenti:

  • Quando si aggiungono nuove funzionalità

    Ogni nuova funzionalità aggiunta al software determina un aumento dell’uso dello stack. Gli sviluppatori devono tenere sotto controllo l’uso dello stack da parte della nuova funzionalità.
    1. Esecuzione dell’analisi dello stack, del debug e correzione di codice complesso: dopo l’implementazione delle funzionalità più importanti, gli sviluppatori possono impiegare un analizzatore statico su componenti o moduli software specifici a livello locale per valutare l’aumento del consumo dello stack tra software base e software implementato.
    2. Monitoraggio dell’analisi dello stack nel corso dell’intero processo di sviluppo: il team di QA e i proprietari dei prodotti possono utilizzare un analizzatore statico per fare stime dello stack sulla pipeline di integrazione continua (CI), al fine di visualizzare i risultati su dashboard. Questa procedura aiuta a monitorare l’analisi dello stack durante il ciclo di sviluppo del software.
    3. Attuazione di buone pratiche per mantenere l’uso dello stack al livello minimo: i Quality Gate possono aiutare a evitare violazioni alle linee guida di codifica AUTOSAR e MISRA™ che attuano l’uso condizionato dell’allocazione della memoria dinamica.
  • Prima della release del software

    Le stime dello stack degli analizzatori statici forniscono prove concrete del fatto che il consumo dello stack è sotto controllo. Prima della release dei software, esegui l’analisi dello stack su target reali in condizioni di carico operativo standard, carico minimo e carico massimo in modo tale da avere una visione globale dell’uso dello stack. È fondamentale anche verificare le procedure fail-safe per gli eventi di overflow e underflow dello stack.

Cosa fa Polyspace per la stima dello stack?

Polyspace Code Prover™ svolge stime conservative e ottimistiche su dimensioni maggiori e minori di variabili locali in ciascuna funzione in modo tale da individuare il valore massimo e minimo di uso dello stack sia a livello funzionale che a livello di programma. L’analisi considera la dimensione dei valori di return della funzione, la dimensione dei parametri della funzione, la dimensione delle variabili locali e il padding aggiuntivo introdotto per l’allineamento della memoria.

Metriche del codice dell’analisi dello stack su Polyspace desktop.

Metriche del codice dell’analisi dello stack su Polyspace desktop.

Per comprendere e correggere l’utilizzo dello stack in overshooting, gli sviluppatori possono eseguire Polyspace® in locale e analizzare le profondità delle chiamate a funzione per identificare la causa esatta dell’overshoot dello stack e abbassare l’uso dello stack mediante un utilizzo ottimale delle risorse disponibili.

Albero della chiamata e stima dello stack più alta per la funzione table_loop().

Albero della chiamata e stima dello stack più alta per la funzione table_loop().

Monitoraggio dell’analisi dello stack nel corso dell’intero processo di sviluppo

Polyspace Access™ è un server di database di risultati che restituisce un’interfaccia utente grafica su web browser. Il processo CI può attivare l’analisi dello stack su Polyspace Server™ in modo tale da generare una stima dell’uso dello stack. Questo risultato può essere caricato nel database di risultati. I team di QA e i proprietari di prodotti possono visualizzare in continuo l’uso dello stack sul front-end grafico e prendere le dovute misure in caso di uso eccessivo delle risorse di stack disponibili.

Stima dello stack a livello di progetto su Polyspace Access.

Stima dello stack a livello di progetto su Polyspace Access.

Come fase successiva, analizza le funzioni con un uso dello stack più alto e assegna la funzione specifica agli sviluppatori affinché svolgano ulteriori indagini e il debugging. Polyspace consente di assegnare uno stato, la gravità e dei commenti ai risultati dell’analisi prima dell’assegnazione agli sviluppatori grazie a strumenti di monitoraggio dei bug come Jira. 

Stima dello stack a livello di funzione e dashboard per l’analisi dei risultati su Polyspace Access.

Stima dello stack a livello di funzione e dashboard per l’analisi dei risultati su Polyspace Access.

Attuazione di buone pratiche per mantenere l’uso dello stack al livello minimo

Per quanto riguarda il codice di produzione, è obbligatorio che le violazioni agli standard di codifica quali MISRA C™, MISRA C++, AUTOSAR C++ e molti altri siano pari a zero. Tali standard di codifica vietano l’allocazione della memoria dinamica e consigliano casi d’uso specifici per ottimizzare l’allocazione della memoria statica. Polyspace Bug Finder™ è in grado di riconoscere le violazioni alle best practice, che gli sviluppatori possono monitorare in locale e i proprietari dei prodotti tramite Polyspace Access. Le regole di codifica riportate di seguito specificano le best practice per l’allocazione della memoria statica da analizzare con Polyspace Bug Finder.

Linee guida di codifica

Regola

Descrizione

MISRA C: 2004

20.4

Non usare l’allocazione dinamica dell’heap.

MISRA C: 2012

21.3

Non usare le funzioni di allocazione e deallocazione della memoria <stdlib.h>.

MISRA C++: 2008

18-4-1

Non usare l’allocazione dinamica dell’heap.

AUTOSAR C++14

A18-5-1

Non usare le funzioni malloc, calloc, realloc e free.

AUTOSAR C++14

A18-5-2

Non usare le espressioni non-placement new o delete.

AUTOSAR C++14

A18-5-3

La forma dell’espressione delete deve corrispondere alla forma della nuova espressione usata per allocare la memoria.

AUTOSAR C++14

A18-5-4

Se un progetto ha una versione dimensionata o non dimensionata dell’operatore “delete” definita a livello globale, definire sia la versione dimensionata che la versione non dimensionata.

AUTOSAR C++14

A18-5-5

Le funzioni di gestione della memoria devono garantire quanto segue: (a) comportamento deterministico con conseguente esistenza del tempo di esecuzione peggiore, (b) evitare la frammentazione della memoria, (c) evitare di esaurire la memoria, (d) evitare allocazioni e deallocazioni non corrispondenti, e (e) assenza di dipendenza da chiamate non deterministiche al kernel.

AUTOSAR C++14

A18-5-7

Se nel progetto è utilizzata un’implementazione non in tempo reale delle funzioni di gestione della memoria dinamica, la memoria deve essere allocata e deallocata solo durante le fasi del programma non in tempo reale.

AUTOSAR C++14

A18-5-8

Gli oggetti che non hanno durata superiore a una funzione devono avere una durata di archiviazione automatica.

AUTOSAR C++14

A18-5-9

Le implementazioni personalizzate delle funzioni di allocazione e deallocazione della memoria dinamica devono rispettare i requisiti semantici specificati nella clausola corrispondente “Comportamento richiesto” dello Standard C++.

AUTOSAR C++14

A18-5-10

Usare placement new solo con puntatori allineati in modo corretto a una capacità di archiviazione sufficiente.

AUTOSAR C++14

A18-5-11

L’“operatore new” e l’“operatore delete” devono essere definiti insieme.

L’uso dello stack aumenta con l’aumentare della complessità ciclomatica del codice, del numero di chiamate a funzione annidate, del numero di variabili nella funzione e di altri fattori. Polyspace permette di controllare numerose variabili che influiscono sull’uso dello stack e consente di impostare una soglia di complessità del codice.

Soglie impostate per la complessità del codice.

Soglie impostate per la complessità del codice.

Polyspace Bug Finder permette di svolgere numerosi controlli di runtime per l’allocazione della memoria statica e dinamica. Risolvere tutti i difetti di priorità alta, media bassa aiuta a ridurre i rischi correlati all’allocazione della memoria.

Controlli di runtime della memoria statica e dinamica.

Controlli di runtime della memoria statica e dinamica.

Indipendentemente dal metodo impiegato per calcolare l’uso dello stack, si consiglia di sovradimensionare leggermente lo stack. Questo approccio aiuta a evitare vulnerabilità del sistema dovute allo stack overflow che potrebbe non essere stato rilevato durante i test.

Lo stack overflow è uno dei principali motivi per cui molte applicazioni embedded hanno un comportamento indefinibile sul campo. Usare lo strumento giusto al momento giusto e attenersi alle best practice può aumentare la sicurezza nel fatto che il software non subirà stack overflow.