Domanda

Nel software embedded multi-filettata (scritto in C o C ++), un filo deve essere dato spazio dello stack sufficiente per consentire di completare le sue operazioni senza traboccare. Corretto dimensionamento della pila è critica in alcuni ambienti embedded real-time, perché (almeno in alcuni sistemi con cui ho lavorato), il sistema operativo non rileva questo per voi.

Generalmente, la dimensione dello stack di un nuovo filo (diverso dal thread principale) è designato al momento della creazione quel filo (cioè in un argomento per pthread_create () o simili). Spesso, questi stack sono hardcoded per i valori che sono noti per essere buono al momento il codice è stato scritto in origine o testato.

Tuttavia, le future modifiche al codice spesso si rompono le ipotesi su cui si basano le dimensioni degli stack hard-coded, e un giorno fatidico, il thread entra in uno dei rami più profondi del suo grafico delle chiamate e overflow dello stack - abbattere l'intero sistema o la memoria in silenzio corruttrice.

ho visto personalmente questo problema nel caso in cui codice eseguito nel thread dichiara istanze struct in pila. Quando la struct viene aumentata per contenere dati aggiuntivi, la dimensione dello stack gonfia conseguenza, potenzialmente permettendo overflow dello stack a verificarsi. Immagino che questo potrebbe essere un problema enorme per basi di codice stabiliti in cui gli effetti completi di aggiungere campi a una struttura non può essere conosciuto immediatamente (troppi thread / funzioni per trovare tutti i luoghi in cui viene utilizzato che struct).

Dal momento che la solita risposta a "pila dimensionamento" domande è "non sono portatile", supponiamo che il compilatore, sistema operativo, e il processore sono tutte quantità note per questa indagine. Supponiamo anche la ricorsione non viene utilizzato, quindi non a che fare con la possibilità di uno scenario "infinito ricorsione".

Quali sono alcuni modi affidabili per stimare la dimensione dello stack necessario per un filo? Preferirei metodi che non sono in linea (analisi statica) e automatico, ma tutte le idee sono i benvenuti.

È stato utile?

Soluzione

Runtime-valutazione

Un metodo on-line è quello di dipingere lo stack completo di un certo valore, come 0xAAAA (o 0xAA, qualunque sia la vostra larghezza è). Quindi è possibile controllare quanto grande lo stack è cresciuto al massimo in passato controllando la quantità del dipinto è rimasto intatto.

Dai un'occhiata alla questo collegamento per una spiegazione con l'illustrazione.

Il vantaggio è che è semplice. Uno svantaggio è che non si può essere certi che il vostro stack non finirà per superare l'importo della pila utilizzata durante il test.

Static Valutazione

Ci sono alcune verifiche statiche e penso esiste anche una versione di gcc violato che cerca di fare questo. L'unica cosa che posso dirvi è che il controllo statico è molto difficile da fare nel caso generale.

Anche dare un'occhiata al questa domanda .

Altri suggerimenti

È possibile utilizzare uno strumento di analisi statica come StackAnalyzer , se l'obiettivo si adatta alle esigenze.

Se si vuole spendere soldi significativo è possibile utilizzare uno strumento di analisi statica commerciale come Klocwork. Anche se Klocwork si rivolge principalmente a rilevare i difetti del software e vulnerabilità di sicurezza. Tuttavia, ha anche uno strumento chiamato 'kwstackoverflow' che può essere utilizzato per rilevare overflow dello stack di un compito o thread. Sto usando per il progetto integrato che io lavoro, e ho avuto risultati positivi. Non credo che qualsiasi strumento come questo è perfetto, ma credo che questi strumenti commericial sono molto buone. La maggior parte degli strumenti mi sono imbattuto in lotta con puntatori a funzione. So anche che molti fornitori del compilatore come Green Hills ora costruire una funzionalità simile a destra in loro compilatori. Questa è probabilmente la soluzione migliore perché il compilatore ha una profonda conoscenza di tutti i dettagli necessari per prendere decisioni accurate circa la dimensione dello stack.

Se avete il tempo, sono sicuro che è possibile utilizzare un linguaggio di scripting per rendere il proprio strumento di analisi di overflow dello stack. Lo script avrebbe bisogno di individuare il punto di ingresso del compito o filo, generare un albero completo funzione di chiamata, e quindi calcolare la quantità di spazio di stack che ogni funzione utilizza. Ho il sospetto che ci sono probabilmente disponibili strumenti gratuiti che possono generare un albero completo funzione di chiamata in modo che dovrebbe rendere più facile. Se si conoscono le specifiche della vostra piattaforma che generano lo spazio di stack ogni usi funzione può essere molto facile. Ad esempio, la prima istruzione assemblaggio di una funzione PowerPC spesso è la parola negozio con istruzioni di aggiornamento che regola la stack pointer dalla quantità necessaria per la funzione. Si può prendere la dimensione in byte fin dalla prima istruzione che rende determinare lo spazio totale di stack utilizzato relativamente facile.

Questi tipi di analisi saranno tutti vi darà un'approssimazione del caso peggiore limite superiore per l'utilizzo di stack che è esattamente quello che si vuole sapere. Naturalmente, gli esperti (come quelli con cui lavoro) potrebbero lamentano del fatto che si sta assegnando troppo spazio di stack, ma sono dinosauri che non si preoccupano di buona qualità del software:)

Un altra possibilità, anche se non calcolare l'utilizzo dello stack sarebbe quella di utilizzare l'unità di gestione della memoria (MMU) del processore (se ne ha uno) per rilevare overflow dello stack. Ho fatto questo su VxWorks 5.4 utilizzando un PowerPC. L'idea è semplice, basta mettere una pagina di memoria protetta da scrittura in cima del tuo stack. Se Overflow, un processore execption si verificherà e vi sarà rapidamente avvertito il problema overflow dello stack. Naturalmente, non dice da quanto è necessario aumentare la dimensione dello stack, ma se il vostro bene con l'eccezione di debug / file core si può almeno capire la sequenza di chiamata che traboccava lo stack. È quindi possibile utilizzare queste informazioni per aumentare la dimensione dello stack in modo appropriato.

-djhaus

Non libero, ma Coverity fa l'analisi statica dello stack.

Static (off-line) il controllo dello stack non è così difficile come sembra. Ho implementato per il nostro IDE incorporato ( RapidiTTy ) - attualmente lavora per ARM7 (NXP LPC2xxx ), Cortex-M3 (STM32 e NXP LPC17xx), x86 e la nostra in-house MIPS ISA FPGA compatibile soft-core.

Essenzialmente usiamo un semplice parse del codice eseguibile per determinare l'utilizzo pila di ogni funzione. La maggior parte di allocazione dello stack significativo viene fatto all'inizio di ogni funzione; tanto per essere sicuro di vedere come si altera con diversi livelli di ottimizzazione e, se del caso, ARM set di istruzioni / pollice, ecc Ricordate anche che le attività di solito hanno i loro stack, e ISR spesso (ma non sempre) condividono una zona separata pila!

Una volta che avete l'uso di ogni funzione, è abbastanza facile costruire una call-albero dal parse e calcolare l'utilizzo massimo per ogni funzione. Il nostro IDE genera utilità di pianificazione (RTOS sottili effettivi) per voi, quindi sappiamo esattamente quali funzioni vengono designati come 'compiti' e che sono ISR, in modo che possiamo dire l'utilizzo nel caso peggiore per ogni area di stack.

Naturalmente, queste cifre sono quasi sempre sopra il reale il massimo. Pensate a una funzione come sprintf che può utilizzare un molto di spazio dello stack, ma varia enormemente a seconda della stringa di formato e parametri forniti. Per queste situazioni è anche possibile utilizzare l'analisi dinamica - riempire lo stack con un valore noto in avvio, quindi eseguire nel debugger per un po ', mettere in pausa e vedere quanto di ogni pila è ancora pieno di vostro valore (test di stile high watermark) .

Nessuno dei due approcci è perfetto, ma che unisce entrambi vi darà una discreta immagine di ciò che l'uso del mondo reale sta per essere come.

Come discusso nella risposta questa domanda , una tecnica comune è quella di inizializzare la pila con un valore noto e quindi a eseguire il codice per un po 'e vedere dove si ferma il modello.

Questo non è un metodo non in linea, ma il progetto che sto lavorando su noi abbiamo un comando di debug che legge il livello di piena su tutte le pile attività all'interno dell'applicazione. Ciò genera una tabella dell'uso stack per ciascun compito e la quantità di headroom disponibile. Controllare questi dati dopo una corsa di 24 ore con un sacco di interazione con l'utente ci dà una certa sicurezza che le allocazioni dello stack definiti sono "sicuri".

Questo funziona utilizzando la tecnica ben cercato di riempire le pile con un modello noto e supponendo che l'unico modo che questo può essere riscritta è dalla normale utilizzo pila, anche se è stato scritto da qualsiasi altro mezzo di una pila trabocco è l'ultima delle vostre preoccupazioni!

Abbiamo cercato di risolvere questo problema in un sistema embedded al mio lavoro. Ha ottenuto pazzo, c'è solo troppo codice (entrambi i nostri propri e 3 quadri di partito) per ottenere una risposta affidabile. Per fortuna, il nostro dispositivo è stato basato su Linux quindi abbiamo sceso al comportamento standard di dare ogni 2mb filo e lasciare che il gestore della memoria virtuale ottimizzare l'utilizzo.

Il nostro unico problema con questa soluzione è stato uno degli strumenti 3rd party eseguito un mlock su tutta la spazio di memoria (idealmente per migliorare le prestazioni). Questo ha causato tutto 2mb di stack per ogni thread dei thread (75-150 di esse) da paging in. Abbiamo perso metà del nostro spazio di memoria a fino abbiamo capito e commentata la riga all'origine.

Nota a margine: gestore di memoria virtuale di Linux (VMM) alloca RAM in 4K pezzi. Quando un nuovo filo richiede 2 MB di spazio di indirizzi per la pila, il vmm assegna pagine di memoria fittizie a tutti ma all'inizio più pagina. Quando la pila cresce in pagina fasullo kernel rileva un errore di pagina e scambia pagina falsa con una vera, (che consuma un'altra 4k di RAM effettiva). Così stack di un thread può crescere in ogni dimensione necessita (finché è meno di 2mb) e il vmm garantirà solo una minima quantità di memoria utilizzata.

A parte alcuni dei suggerimenti già fatto Vorrei sottolineare che spesso in sistemi embedded è necessario controllare l'utilizzo dello stack strettamente perché si deve mantenere la dimensione dello stack in una dimensione ragionevole.

In un certo senso, utilizzando lo spazio dello stack è un po 'come l'allocazione della memoria, ma senza un modo (facile) per determinare se la ripartizione è riuscita quindi non controllare l'utilizzo dello stack si tradurrà in una lotta sempre per capire il motivo per cui il sistema è di nuovo crash. Così, per esempio, se il sistema alloca la memoria per le variabili locali dalla pila, sia allocare che la memoria con malloc () o, se non è possibile utilizzare malloc () scrivere il proprio gestore di memoria (che è un compito abbastanza semplice).

No-no:

void func(myMassiveStruct_t par)
{
  myMassiveStruct_t tmpVar;
}

Sì, sì:

void func (myMassiveStruct_t *par)
{
  myMassiveStruct_t *tmpVar;
  tmpVar = (myMassiveStruct_t*) malloc (sizeof(myMassicveStruct_t));
}

sembra abbastanza ovvio, ma spesso non è -. Specialmente quando non è possibile utilizzare malloc ()

Naturalmente si avrà ancora problemi, quindi questo è solo qualcosa per aiutare, ma non risolve il problema. Essa, tuttavia, aiutare a stimare la dimensione dello stack in futuro, dal momento che una volta che hai trovato una buona dimensione per il vostro stack e se poi, dopo alcune modifiche al codice, ancora una volta a corto di spazio di stack è possibile rilevare un numero di bug o di altri problemi (chiamata troppo profondo stack per uno).

sicuro al 100%, ma credo che questo può anche essere fatto. Se si dispone di una porta JTAG esposto, è possibile connettersi a Trace32 e controllare il massimo utilizzo dello stack. Anche se per questo, si dovrà dare una abbastanza grande dimensione iniziale dello stack arbitrario.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top