Handling Stack-Überläufe in eingebetteten Systemen

8

Wie gehen Sie bei eingebetteter Software generell mit einem Stack-Überlauf um? Ich stoße auf einen Prozessor, der in Hardware wie die neuesten AMD-Prozessoren schützt. Es gibt einige Techniken auf Wikipedia, aber sind diese wirklich praktischen Ansätze?

Kann jemand einen klaren Vorschlag geben, der auf den heutigen 32-Bit-Embedded-Prozessoren in jedem Fall funktioniert?

    
Ether 27.07.2009, 00:46
quelle

4 Antworten

11

Idealerweise schreiben Sie Ihren Code mit statischer Stack-Nutzung (keine rekursiven Aufrufe). Dann können Sie die maximale Stack-Nutzung mit folgenden Methoden auswerten:

  1. statische Analyse (mit Werkzeugen)
  2. Messung der Stack-Nutzung während der Ausführung Ihres Codes mit vollständiger Code-Abdeckung (oder so hoher Code-Coverage, bis Sie ein vernünftiges Vertrauen haben, dass Sie das Ausmaß der Stack-Nutzung festgelegt haben, solange Ihr selten ausgeführter Code dies nicht tut benutze besonders mehr Stack als die normalen Ausführungspfade)

Aber selbst damit möchten Sie immer noch die Möglichkeit haben, zu erkennen und dann den Stack zu umgehen, falls dies möglich ist, um eine größere Robustheit zu erreichen. Dies kann besonders während der Entwicklungsphase des Projekts hilfreich sein. Einige Methoden zum erkennen Überlauf:

  1. Wenn der Prozessor einen Speicher-Lese- / Schreib-Interrupt unterstützt (d. h. Speicherzugriffs-Unterbrechungspunkt-Interrupt), kann er so konfiguriert werden, dass er auf das weiteste Ausmaß des Stapelbereichs zeigt.
  2. Richten Sie in der Speicherzuordnungskonfiguration einen kleinen (oder großen) RAM-Block ein, der ein "Stapelwächter" -Bereich ist. Fülle sie mit bekannten Werten. In der eingebetteten Software überprüfen Sie regelmäßig (so oft wie möglich) den Inhalt dieses Bereichs. Wenn es sich jemals ändert, nehmen Sie einen Stapelüberlauf an.

Sobald Sie es erkannt haben, müssen Sie es handhaben . Ich kenne nicht viele Möglichkeiten, wie sich Code von einem Stack-Überlauf wiederherstellen lässt, denn wenn es einmal passiert ist, wird Ihre Programmlogik mit ziemlicher Sicherheit ungültig gemacht. Alles, was Sie tun können, ist

  1. protokolliere den Fehler
    1. Das Protokollieren des Fehlers ist sehr nützlich, da sonst die Symptome (unerwartete Neustarts) sehr schwer zu diagnostizieren sind.
    2. Vorbehalt: Die Protokollierungsroutine muss auch in einem beschädigten Stack-Szenario zuverlässig ausgeführt werden können. Die Routine sollte einfach sein. I.e. Mit einem beschädigten Stack können Sie wahrscheinlich nicht versuchen, mit Ihrer Fantasie-EEPROM-Schreib-Hintergrundaufgabe in das EEPROM zu schreiben. Vielleicht protokollieren Sie den Fehler einfach in einer Struktur, die für diesen Zweck reserviert ist, im Nicht-Init-RAM, die dann nach dem Neustart überprüft werden kann.
  2. Neustart (oder vielleicht heruntergefahren, besonders wenn der Fehler wiederholt auftritt)
    1. Mögliche Alternative: Starten Sie nur die bestimmte Aufgabe neu, wenn Sie ein RTOS verwenden, und Ihr System ist so konzipiert, dass die Stapelbeschädigung isoliert ist und alle anderen Aufgaben in der Lage sind, den Neustart dieser Aufgabe zu bewältigen. Dies würde einige ernsthafte Konstruktionsüberlegungen erfordern.
Craig McQueen 27.07.2009 02:40
quelle
2

Während der eingebettete Stapelüberlauf durch rekursive Funktionen verursacht werden kann, die außer Kontrolle geraten, kann dies auch durch eine fehlerhafte Zeigerverwendung verursacht werden (obwohl dies als eine andere Art von Fehler angesehen werden könnte) und als normaler Systembetrieb mit einem zu kleinen Stapel. Mit anderen Worten, wenn Sie Ihre Stack-Nutzung nicht profilieren, kann dies außerhalb einer Fehler- oder Fehlersituation auftreten.

Bevor Sie den Stack-Overflow "behandeln" können, müssen Sie ihn identifizieren. Eine gute Methode dafür ist, den Stapel während der Initialisierung mit einem Muster zu laden und dann zu überwachen, wie viel von dem Muster während der Laufzeit verschwindet. Auf diese Weise können Sie den höchsten Punkt identifizieren, den der Stapel erreicht hat.

Der Musterprüfalgorithmus sollte in der entgegengesetzten Richtung des Stapelwachstums ausgeführt werden. Wenn also der Stack von 0x1000 auf 0x2000 wächst, kann die Musterprüfung bei 0x2000 beginnen, um die Effizienz zu erhöhen. Wenn Ihr Muster 0xAA ist und der Wert bei 0x2000 etwas anderes als 0xAA enthält, wissen Sie, dass Sie wahrscheinlich einen Überlauf haben.

Sie sollten auch in Erwägung ziehen, einen leeren RAM-Puffer unmittelbar nach dem Stapel zu platzieren, sodass Sie das System herunterfahren können, ohne Daten zu verlieren, wenn Sie einen Überlauf feststellen. Wenn auf Ihren Stack sofort Heap- oder SRAM-Daten folgen, bedeutet das Erkennen eines Überlaufs, dass Sie bereits beschädigt sind. Ihr Puffer schützt Sie ein bisschen länger. Auf einem 32-Bit-Mikro sollten Sie genug RAM haben, um mindestens einen kleinen Puffer bereitzustellen.

    
dls 27.07.2009 01:52
quelle
1

Ein Stapelüberlauf tritt auf, wenn der Stapelspeicher durch einen zu großen Aufrufstapel erschöpft ist? z.B. eine rekursive Funktion zu viele Ebenen tief.

Es gibt Techniken, um einen Stapelüberlauf zu erkennen, indem Sie bekannte Daten nach dem Stapel platzieren, damit er erkannt werden kann, wenn der Stapel zu stark wächst und ihn überschreibt.

Es gibt statische Quellcode-Analyse-Tools wie GnatStack, StackAnalyzer von AbsInt und Bound-T, die verwendet werden können, um die maximale Laufzeit-Stack-Größe zu bestimmen oder zu erraten.

    
Sean A.O. Harney 27.07.2009 01:33
quelle
1

Wenn Sie einen Prozessor mit einer Speicherverwaltungseinheit verwenden, kann Ihre Hardware dies mit minimalem Software-Overhead für Sie erledigen. Die meisten modernen 32-Bit-Prozessoren haben sie und mehr und mehr 32-Bit-Mikrocontroller bieten sie auch.

Richten Sie einen Speicherbereich in der MMU ein, der für den Stapel verwendet werden soll. Es sollte von zwei Speicherbereichen begrenzt sein, in denen die MMU keinen Zugriff erlaubt. Wenn Ihre Anwendung läuft, erhalten Sie eine Exception / Interrupt, sobald Sie den Stack überlaufen.

Da Sie eine Ausnahme zum Zeitpunkt des Auftretens des Fehlers erhalten, wissen Sie genau, wo in Ihrer Anwendung der Stapel fehlerhaft war. Sie können sich den Call-Stack ansehen, um genau zu sehen, wie Sie dahin gekommen sind, wo Sie sind. Dies macht es viel einfacher, Ihr Problem zu finden, als zu versuchen, herauszufinden, was falsch ist, indem Sie Ihr Problem lange nach dem Auftreten entdecken.

Ich habe das erfolgreich auf PPC- und AVR32-Prozessoren verwendet. Wenn Sie mit einer MMU anfangen, haben Sie das Gefühl, dass es Zeitverschwendung ist, da Sie viele Jahre ohne es großartig auskamen, aber sobald Sie die Vorteile einer Ausnahme genau dort sehen, wo Ihr Speicherproblem auftritt, werden Sie nie wieder zurückkehren. Eine MMU kann auch Zugriffe auf Nullzeiger erkennen, wenn Sie keinen Speicherzugriff auf den unteren Speicherbereich Ihres Widders zulassen.

Wenn Sie ein RTOS verwenden, schützt Ihre MMU den Speicher und die Stapel anderer Aufgaben, die Fehler in einer Aufgabe nicht beeinflussen sollten. Dies bedeutet, dass Sie Ihre Aufgabe problemlos neu starten können, ohne die anderen Aufgaben zu beeinträchtigen.

Außerdem hat ein Prozessor mit einer MMU normalerweise auch eine Menge RAM. Ihr Programm ist viel weniger wahrscheinlich, um Ihren Stack zu überfluten, und Sie müssen nicht alles genau abstimmen, damit Ihre Anwendung mit einem kleinen Speicher korrekt läuft Fußabdruck.

Eine Alternative dazu wäre, die Debug-Funktionen des Prozessors zu verwenden, um einen Interrupt bei einem Speicherzugriff auf das Ende des Stapels zu verursachen. Dies wird wahrscheinlich sehr prozessorspezifisch sein.

    
Gerhard 27.07.2009 06:24
quelle