Wie gefährlich ist es, außerhalb der Grenzen auf ein Array zuzugreifen?

221

Wie gefährlich ist der Zugriff auf ein Array außerhalb seiner Grenzen (in C)? Es kann manchmal vorkommen, dass ich von außerhalb des Arrays lese (ich verstehe jetzt, dass ich dann auf den Speicher zugreife, der von einigen anderen Teilen meines Programms oder sogar darüber hinaus verwendet wird) oder ich versuche, einen Wert auf einen Index außerhalb des Arrays zu setzen. Das Programm stürzt manchmal ab, läuft aber manchmal nur und liefert nur unerwartete Ergebnisse.

Was ich jetzt gerne wissen würde ist, wie gefährlich ist das wirklich? Wenn es mein Programm beschädigt, ist es nicht so schlimm. Wenn es andererseits etwas außerhalb meines Programms kaputt macht, weil ich es irgendwie geschafft habe, auf einen völlig unabhängigen Speicher zuzugreifen, dann ist es sehr schlecht, stelle ich mir vor. Ich habe viel gelesen, dass alles passieren kann. "Segmentierung könnte das am wenigsten schlimme Problem sein" , "Ihre Festplatte könnte rosa werden und Einhörner könnten unter Ihrem Fenster singen", was alles schön ist, aber was ist wirklich die Gefahr?

Meine Fragen:

  1. Kann das Lesen von Werten von außerhalb des Arrays etwas anderes als mein Programm beschädigen? Ich würde mir vorstellen, dass das bloße Betrachten von Dingen nichts ändert, oder würde es beispielsweise das Attribut "Zuletzt geöffnet" einer Datei ändern, die ich zufällig erreicht habe?
  2. Kann das Einstellen von Werten außerhalb des Arrays etwas anderes als mein Programm beschädigen? Aus dieser Frage zum Stapelüberlauf geht hervor, dass es möglich ist, auf jeden Speicherort zuzugreifen, dass es keine Sicherheitsgarantie gibt.
  3. Ich führe jetzt meine kleinen Programme in XCode aus. Bietet dies zusätzlichen Schutz für mein Programm, wenn es nicht außerhalb seines eigenen Speichers greifen kann? Kann es XCode schaden?
  4. Irgendwelche Empfehlungen, wie ich meinen inhärent fehlerhaften Code sicher ausführen kann?

Ich benutze OSX 10.7, Xcode 4.6.

ChrisD
quelle
Im Allgemeinen schützt das Betriebssystem sich selbst und andere Prozesse vor Ihrem Fehlverhalten. Es ist jedoch nicht etwas, auf das Sie sich unbedingt verlassen möchten.
Hot Licks
7
Außerdem werden Sie niemals eine Datei auf Ihrer Festplatte "erreichen", wenn Sie auf einen Array-Index außerhalb der Grenzen (in Ihrem RAM) zugreifen.
DrummerB
1
Ich glaube, Sie fragen nach C-Array, richtig? Das hat also nichts mit ObjC zu tun und bezieht sich nicht wirklich auf eine IDE.
Bryan Chen
17
Hier ist mein Lieblingsbeispiel für seltsame Ergebnisse (es befasst sich mit dem Stapel, aber ich fand es wirklich aufschlussreich ...).
Phipsgabler
11
xkcd.com/371
Dan spielt am

Antworten:

125

In Bezug auf den ISO C-Standard (die offizielle Definition der Sprache) hat der Zugriff auf ein Array außerhalb seiner Grenzen ein " undefiniertes Verhalten ". Die wörtliche Bedeutung davon ist:

Verhalten bei Verwendung eines nicht portierbaren oder fehlerhaften Programmkonstrukts oder fehlerhafter Daten, für die diese Internationale Norm keine Anforderungen stellt

Eine nicht normative Anmerkung erweitert dies:

Mögliches undefiniertes Verhalten reicht vom vollständigen Ignorieren der Situation mit unvorhersehbaren Ergebnissen über das Verhalten während der Übersetzung oder Programmausführung in einer für die Umgebung charakteristischen dokumentierten Weise (mit oder ohne Ausgabe einer Diagnosemeldung) bis zum Beenden einer Übersetzung oder Ausführung (mit der Ausgabe) einer Diagnosemeldung).

Das ist also die Theorie. Was ist die Realität?

Im "besten" Fall greifen Sie auf einen Speicher zu, der entweder Ihrem aktuell ausgeführten Programm gehört (was dazu führen kann, dass sich Ihr Programm schlecht verhält) oder nicht von Ihrem aktuell laufenden Programm (die wahrscheinlich Ursache Ihres Programms im Besitz Absturz mit so etwas wie einem Segmentierungsfehler). Oder Sie versuchen möglicherweise, in den Speicher zu schreiben, den Ihr Programm besitzt, der jedoch als schreibgeschützt markiert ist. Dies wird wahrscheinlich auch dazu führen, dass Ihr Programm abstürzt.

Dies setzt voraus, dass Ihr Programm unter einem Betriebssystem ausgeführt wird, das versucht, gleichzeitig ausgeführte Prozesse voreinander zu schützen. Wenn Ihr Code auf dem "Bare Metal" ausgeführt wird, z. B. wenn er Teil eines Betriebssystemkerns oder eines eingebetteten Systems ist, gibt es keinen solchen Schutz. Ihr schlecht benommener Code sollte diesen Schutz bieten. In diesem Fall sind die Schadensmöglichkeiten erheblich größer, einschließlich in einigen Fällen physischer Schäden an der Hardware (oder an Dingen oder Personen in der Nähe).

Selbst in einer geschützten Betriebssystemumgebung ist der Schutz nicht immer 100%. Es gibt Betriebssystemfehler, die es nicht privilegierten Programmen ermöglichen, beispielsweise Root-Zugriff (Administratorzugriff) zu erhalten. Selbst mit normalen Benutzerrechten kann ein fehlerhaftes Programm übermäßige Ressourcen (CPU, Speicher, Festplatte) verbrauchen und möglicherweise das gesamte System zum Erliegen bringen. Viele Malware (Viren usw.) nutzt Pufferüberläufe aus, um unbefugten Zugriff auf das System zu erhalten.

(Ein historisches Beispiel: Ich habe gehört, dass auf einigen alten Systemen mit Kernspeicher der wiederholte Zugriff auf einen einzelnen Speicherort in einer engen Schleife buchstäblich dazu führen kann, dass dieser Speicherblock schmilzt. Andere Möglichkeiten sind das Zerstören einer CRT-Anzeige und das Verschieben des Lesevorgangs / schreibe den Kopf eines Festplattenlaufwerks mit der harmonischen Frequenz des Laufwerksgehäuses, wodurch es über einen Tisch läuft und auf den Boden fällt.)

Und es gibt immer Skynet, um das man sich Sorgen machen muss.

Unterm Strich ist dies: Wenn Sie ein Programm schreiben könnte etwas Schlechtes zu tun bewusst , ist es zumindest theoretisch möglich , dass ein fehlerhaftes Programm könnte das gleiche tun zufällig .

In der Praxis ist es sehr unwahrscheinlich, dass Ihr fehlerhaftes Programm, das auf einem MacOS X-System ausgeführt wird, etwas Schwerwiegenderes als einen Absturz verursacht. Es ist jedoch nicht möglich, fehlerhaften Code vollständig daran zu hindern, wirklich schlechte Dinge zu tun.

Keith Thompson
quelle
1
danke, ich verstehe das eigentlich voll und ganz. Aber es löst sofort eine Folgefrage aus: Was kann ein beginnender Programmierer tun, um seinen Computer vor seinen eigenen möglicherweise schrecklichen Kreationen zu schützen? Nachdem ich ein Programm gründlich getestet habe, kann ich es in der Welt entfesseln. Der erste Testlauf ist jedoch mit Sicherheit ein falsches Programm. Wie schützt ihr eure Systeme vor euch selbst?
ChrisD
6
@ ChrisD: Wir neigen dazu, Glück zu haben. 8-)} Im Ernst, der Schutz auf Betriebssystemebene ist heutzutage ziemlich gut. Im schlimmsten Fall muss ich möglicherweise neu starten, um eine Wiederherstellung durchzuführen, wenn ich eine versehentliche Gabelbombe schreibe . Aber ein wirklicher Schaden am System ist wahrscheinlich keine Sorge wert, solange Ihr Programm nicht versucht, etwas zu tun, das nicht gefährlich ist. Wenn Sie sich wirklich Sorgen machen, ist es möglicherweise keine schlechte Idee, das Programm auf einer virtuellen Maschine auszuführen.
Keith Thompson
1
Andererseits habe ich auf Computern, die ich verwendet habe, viele seltsame Dinge gesehen (beschädigte Dateien, nicht behebbare Systemfehler usw.), und ich habe keine Ahnung, wie viele davon möglicherweise durch ein C-Programm verursacht wurden das gefürchtete undefinierte Verhalten. (Bisher sind keine wirklichen Dämonen aus meiner Nase geflogen.)
Keith Thompson
1
Danke, dass du mir
Gabelbomben beigebracht
2
Scientificamerican.com/article/… so ist Feuer mit moderner Elektronik immer noch möglich.
Mooing Duck
25

Im Allgemeinen führen Betriebssysteme von heute (die ohnehin beliebten) alle Anwendungen in geschützten Speicherbereichen mit einem virtuellen Speichermanager aus. Es stellt sich heraus, dass es nicht besonders EINFACH ist, einfach zu lesen oder an einen Ort zu schreiben, der sich im REAL-Bereich außerhalb der Region (en) befindet, die Ihrem Prozess zugewiesen / zugewiesen wurden.

Direkte Antworten:

1) Durch das Lesen wird ein anderer Prozess fast nie direkt beschädigt. Es kann jedoch indirekt einen Prozess beschädigen, wenn Sie einen KEY-Wert lesen, der zum Ver-, Entschlüsseln oder Validieren eines Programms / Prozesses verwendet wird. Das Lesen außerhalb der Grenzen kann sich nachteilig / unerwartet auf Ihren Code auswirken, wenn Sie Entscheidungen auf der Grundlage der von Ihnen gelesenen Daten treffen

2) Die einzige Möglichkeit, etwas wirklich zu BESCHÄDIGEN, indem Sie in eine Loaction schreiben, auf die über eine Speicheradresse zugegriffen werden kann, besteht darin, dass diese Speicheradresse, an die Sie schreiben, tatsächlich ein Hardwareregister ist (ein Speicherort, der eigentlich nicht zur Datenspeicherung, sondern zur Steuerung eines Teils dient von Hardware) kein RAM-Speicherort. Tatsächlich beschädigen Sie normalerweise immer noch nichts, es sei denn, Sie schreiben einen einmal programmierbaren Ort, der nicht wiederbeschreibbar ist (oder etwas Ähnliches).

3) Im Allgemeinen wird der Code im Debug-Modus ausgeführt, wenn er im Debugger ausgeführt wird. Das Ausführen im Debug-Modus führt dazu, dass Ihr Code (aber nicht immer) schneller gestoppt wird, wenn Sie etwas getan haben, das als unpraktisch oder geradezu illegal angesehen wird.

4) Verwenden Sie niemals Makros, Datenstrukturen, in die bereits eine Überprüfung der Array-Indexgrenzen integriert ist, usw.

ZUSÄTZLICH Ich sollte hinzufügen, dass die obigen Informationen wirklich nur für Systeme gelten, die ein Betriebssystem mit Speicherschutzfenstern verwenden. Wenn Sie Code für ein eingebettetes System oder sogar ein System schreiben, das ein Betriebssystem (Echtzeit oder ein anderes) verwendet, das keine Speicherschutzfenster (oder virtuell adressierte Fenster) hat, sollten Sie beim Lesen und Schreiben in den Speicher viel mehr Vorsicht walten lassen. Auch in diesen Fällen sollten immer sichere und sichere Codierungspraktiken angewendet werden, um Sicherheitsprobleme zu vermeiden.

Trompeten
quelle
4
Es sollten immer sichere Codierungspraktiken angewendet werden.
Nik Bougalis
3
Ich würde empfehlen, try / catch NICHT für fehlerhaften Code zu verwenden, es sei denn, Sie fangen ganz bestimmte Ausnahmen ab und wissen, wie Sie diese beheben können. Catch (...) ist das Schlimmste, was Sie einem Buggy-Code hinzufügen können.
Eugene
1
@ NikBougalis - Ich stimme vollkommen zu, aber es ist noch wichtiger, wenn das Betriebssystem keinen Speicherschutz / virtuelle Adressräume enthält oder es an Betriebssystem mangelt :-)
Trompetenlicks
@ Eugene - Ich habe nie bemerkt, dass das ein Problem für mich ist, aber ich stimme Ihnen zu, habe ich es herausgeschnitten :-)
Trompetenlicks
1) Du meinst Schaden, weil ich etwas enthüllen würde, das geheim bleiben sollte? 2) Ich bin nicht sicher, ob ich verstehe, was Sie meinen, aber ich schätze, ich greife nur auf RAM zu, wenn ich versuche, auf Orte außerhalb der Array-Grenzen zuzugreifen?
ChrisD
9

Wenn die Grenzen nicht überprüft werden, kann dies zu hässlichen Nebenwirkungen führen, einschließlich Sicherheitslücken. Eine der hässlichen ist die Ausführung von willkürlichem Code . Im klassischen Beispiel: Wenn Sie ein Array mit fester Größe haben und dort strcpy()eine vom Benutzer bereitgestellte Zeichenfolge einfügen, kann der Benutzer Ihnen eine Zeichenfolge geben, die den Puffer überläuft und andere Speicherorte überschreibt, einschließlich der Code-Adresse, an die die CPU bei Ihrer Funktion zurückkehren soll endet.

Dies bedeutet, dass Ihr Benutzer Ihnen eine Zeichenfolge senden kann, die dazu führt, dass Ihr Programm im Wesentlichen aufgerufen exec("/bin/sh")wird. Dadurch wird es in eine Shell umgewandelt und alles ausgeführt, was er auf Ihrem System möchte, einschließlich der Erfassung aller Ihrer Daten und der Umwandlung Ihres Computers in einen Botnetzknoten.

Weitere Informationen dazu finden Sie unter Zerschlagen des Stapels für Spaß und Gewinn .

che
quelle
Ich weiß, dass ich nicht über die Grenzen hinaus auf Array-Elemente zugreifen sollte, danke für die Verstärkung dieses Punktes. Aber die Frage ist, ob ich nicht nur meinem Programm Schaden zufügen kann, sondern auch versehentlich über die Erinnerung an mein Programm hinausgehen kann. Und ich meine unter OSX.
ChrisD
@ChrisD: OS X ist ein modernes Betriebssystem und bietet Ihnen vollen Speicherschutz. Sie sollten sich beispielsweise nicht darauf beschränken, was Ihr Programm tun darf. Dies sollte nicht das Durcheinander mit anderen Prozessen beinhalten (es sei denn, Sie arbeiten unter Root-Rechten).
Che
Ich würde lieber unter Ring 0 Privilegien sagen, nicht Root.
Ruslan
Interessanter ist , dass hypermoderne Compiler , dass sie entscheiden können , wenn Code versucht zu lesen foo[0]durch , foo[len-1]nachdem zuvor eine Überprüfung genutzt hat , vonlen , um gegen die Array - Länge entweder ausführen oder ein Stück Code überspringt, sollte der Compiler frei fühlen , dass die anderen Code auszuführen bedingungslos sogar Wenn die Anwendung den Speicher hinter dem Array besitzt und die Auswirkungen des Lesens harmlos gewesen wären, wäre dies beim Aufrufen des anderen Codes nicht der Fall.
Supercat
8

Du schreibst:

Ich habe viel gelesen: "Alles kann passieren", "Segmentierung könnte das am wenigsten schlimme Problem sein", "Ihre Festplatte könnte rosa werden und Einhörner könnten unter Ihrem Fenster singen", was alles schön ist, aber was ist wirklich die Gefahr?

Sagen wir es so: Laden Sie eine Waffe. Richten Sie es außerhalb des Fensters ohne ein bestimmtes Ziel und Feuer. Was ist die Gefahr?

Das Problem ist, dass Sie nicht wissen. Wenn Ihr Code etwas überschreibt, das Ihr Programm zum Absturz bringt, ist alles in Ordnung, da es es in einen definierten Zustand versetzt. Wenn es jedoch nicht abstürzt, treten die Probleme auf. Welche Ressourcen werden von Ihrem Programm kontrolliert und was könnte es ihnen antun? Welche Ressourcen könnten unter die Kontrolle Ihres Programms geraten und was könnte es ihnen antun? Ich kenne mindestens ein Hauptproblem, das durch einen solchen Überlauf verursacht wurde. Das Problem lag in einer scheinbar bedeutungslosen Statistikfunktion, die eine nicht verwandte Konvertierungstabelle für eine Produktionsdatenbank durcheinander brachte. Das Ergebnis war eine sehr teure Bereinigung danach. Eigentlich wäre es viel billiger und einfacher gewesen, wenn dieses Problem die Festplatten formatiert hätte ... mit anderen Worten: Rosa Einhörner könnten Ihr geringstes Problem sein.

Die Idee, dass Ihr Betriebssystem Sie schützt, ist optimistisch. Vermeiden Sie nach Möglichkeit das Schreiben außerhalb der Grenzen.

Udo Klein
quelle
ok, genau davor hatte ich Angst. Ich werde "versuchen, das Schreiben außerhalb der Grenzen zu vermeiden", aber wenn ich sehe, was ich in den letzten Monaten getan habe, werde ich es sicherlich noch viel tun. Wie seid ihr so ​​gut im Programmieren geworden, ohne eine sichere Art zu üben?
ChrisD
3
Wer hat gesagt, dass irgendetwas jemals sicher war;)
Udo Klein
7

Wenn Sie Ihr Programm nicht als Root oder einen anderen privilegierten Benutzer ausführen, wird Ihr System nicht beschädigt. Daher ist dies im Allgemeinen eine gute Idee.

Wenn Sie Daten an einen zufälligen Speicherort schreiben, "beschädigen" Sie kein anderes Programm, das auf Ihrem Computer ausgeführt wird, direkt, da jeder Prozess in seinem eigenen Speicher ausgeführt wird.

Wenn Sie versuchen, auf Speicher zuzugreifen, der Ihrem Prozess nicht zugeordnet ist, verhindert das Betriebssystem, dass Ihr Programm mit einem Segmentierungsfehler ausgeführt wird.

Es besteht also keine Gefahr, dass Ihr Programm direkt (ohne als Root ausgeführt wird und direkt auf Dateien wie / dev / mem zugreift) andere Programme stört, die auf Ihrem Betriebssystem ausgeführt werden.

Trotzdem - und wahrscheinlich haben Sie in Bezug auf die Gefahr davon gehört - können Sie durch versehentliches blindes Schreiben zufälliger Daten in zufällige Speicherorte sicher alles beschädigen, was Sie beschädigen können.

Beispielsweise möchte Ihr Programm möglicherweise eine bestimmte Datei löschen, die durch einen Dateinamen angegeben wird, der irgendwo in Ihrem Programm gespeichert ist. Wenn Sie versehentlich nur den Speicherort überschreiben, an dem der Dateiname gespeichert ist, können Sie stattdessen eine ganz andere Datei löschen.

Mikyra
quelle
1
Wenn Sie jedoch als Root (oder ein anderer privilegierter Benutzer) ausgeführt werden, achten Sie darauf. Puffer- und Array-Überläufe sind ein häufiger Malware-Exploit.
John Bode
Tatsächlich ist das Konto, das ich für alle meine täglichen Computer verwende, kein Administratorkonto (ich verwende die OSX-Terminologie, da dies mein System ist). Wollen Sie mir sagen, dass ich möglicherweise nichts beschädigen kann, indem ich versuche, einen Speicherort festzulegen? Das sind wirklich gute Nachrichten!
ChrisD
Wie bereits erwähnt, ist der schlimmste Schaden, den Sie versehentlich anrichten können, der schlimmste, den Sie als Benutzer anrichten können. Wenn Sie zu 100% sicher sein möchten, dass Sie keine Ihrer Daten zerstören, möchten Sie möglicherweise Ihrem Computer ein anderes Konto hinzufügen und damit experimentieren.
Mikyra
1
@mikyra: Das stimmt nur, wenn die Schutzmechanismen des Systems zu 100% wirksam sind. Das Vorhandensein von Malware lässt darauf schließen, dass Sie sich nicht immer darauf verlassen können. (Ich möchte nicht vorschlagen, dass dies unbedingt eine Sorge wert ist. Es ist möglich, aber unwahrscheinlich, dass ein Programm versehentlich dieselben Sicherheitslücken ausnutzt, die von Malware ausgenutzt werden.)
Keith Thompson
1
Die Liste hier enthält: Ausführen von Code aus nicht vertrauenswürdigen Quellen. Klicken Sie einfach auf die Schaltfläche OK in einem Popup der Firewall, ohne zu lesen, worum es geht, oder fahren Sie sie vollständig herunter, wenn die gewünschte Netzwerkverbindung nicht hergestellt werden kann. Patchen von Binärdateien mit dem neuesten Hack aus zweifelhaften Quellen. Es ist nicht die Schuld des Tresors, wenn der Besitzer freiwillig einen Einbrecher mit beiden Armen und besonders starken, weit geöffneten, befestigten Türen einlädt.
Mikyra
4

NSArrays in Objective-C wird ein bestimmter Speicherblock zugewiesen. Das Überschreiten der Grenzen des Arrays bedeutet, dass Sie auf Speicher zugreifen, der dem Array nicht zugewiesen ist. Das heisst:

  1. Dieser Speicher kann einen beliebigen Wert haben. Es gibt keine Möglichkeit festzustellen, ob die Daten basierend auf Ihrem Datentyp gültig sind.
  2. Dieser Speicher kann vertrauliche Informationen wie private Schlüssel oder andere Benutzeranmeldeinformationen enthalten.
  3. Die Speicheradresse ist möglicherweise ungültig oder geschützt.
  4. Der Speicher kann einen sich ändernden Wert haben, da auf ihn von einem anderen Programm oder Thread zugegriffen wird.
  5. Andere Dinge verwenden Speicheradressraum, wie z. B. speicherabgebildete Ports.
  6. Das Schreiben von Daten in eine unbekannte Speicheradresse kann Ihr Programm zum Absturz bringen, den Speicherplatz des Betriebssystems überschreiben und im Allgemeinen dazu führen, dass die Sonne implodiert.

Unter dem Aspekt Ihres Programms möchten Sie immer wissen, wann Ihr Code die Grenzen eines Arrays überschreitet. Dies kann dazu führen, dass unbekannte Werte zurückgegeben werden und Ihre Anwendung abstürzt oder ungültige Daten bereitstellt.

Richard Brown
quelle
NSArrayshaben außerhalb der Grenzen Ausnahmen. Und diese Frage scheint sich auf C-Array zu beziehen.
DrummerB
Ich meinte tatsächlich C-Arrays. Ich weiß, dass es NSArray gibt, aber im Moment sind die meisten meiner Übungen in C
ChrisD
4

Möglicherweise möchten Sie versuchen, das memcheckTool in Valgrind zu verwenden wenn Sie Ihren Code testen. Es werden keine Verstöße gegen einzelne Arraygrenzen innerhalb eines Stapelrahmens abgefangen, aber es sollten viele andere Arten von Speicherproblemen auftreten, einschließlich solcher, die subtile, umfassendere Probleme verursachen würden Probleme außerhalb des Bereichs einer einzelnen Funktion.

Aus dem Handbuch:

Memcheck ist ein Speicherfehlerdetektor. Es kann die folgenden Probleme erkennen, die in C- und C ++ - Programmen häufig auftreten.

  • Sie sollten nicht auf Speicher zugreifen, z. B. Heap-Blöcke über- und unterlaufen lassen, den oberen Teil des Stapels überlaufen lassen und auf Speicher zugreifen, nachdem dieser freigegeben wurde.
  • Verwenden von undefinierten Werten, dh Werten, die nicht initialisiert oder von anderen undefinierten Werten abgeleitet wurden.
  • Falsche Freigabe des Heapspeichers, z. B. doppelte Freigabe von Heapblöcken oder nicht übereinstimmende Verwendung von malloc / new / new [] im Vergleich zu free / delete / delete []
  • Überlappende src- und dst-Zeiger in memcpy und verwandten Funktionen.
  • Speicherlecks.

ETA: Obwohl, wie die Antwort von Kaz sagt, es kein Allheilmittel ist und nicht immer die hilfreichste Ausgabe liefert, insbesondere wenn Sie aufregende Zugriffsmuster verwenden.

Aesin
quelle
Ich würde vermuten, dass der Analyzer von XCode das meiste davon finden würde? und meine Frage ist nicht so sehr, wie man diese Fehler findet, aber wenn das Ausführen eines Programms, das diese Fehler noch aufweist, gefährlich für den Speicher ist, der meinem Programm nicht zugewiesen ist. Ich muss das Programm ausführen, um die Fehler zu sehen
ChrisD
3

Wenn Sie jemals auf Systemebene oder eingebettete Systeme programmieren, können sehr schlimme Dinge passieren, wenn Sie in zufällige Speicherorte schreiben. Ältere Systeme und viele Mikrocontroller verwenden speicherabgebildete E / A-Vorgänge, sodass das Schreiben an einen Speicherort, der einem Peripherieregister zugeordnet ist, zu Chaos führen kann, insbesondere wenn dies asynchron erfolgt.

Ein Beispiel ist das Programmieren eines Flash-Speichers. Der Programmiermodus auf den Speicherchips wird aktiviert, indem eine bestimmte Folge von Werten an bestimmte Stellen innerhalb des Adressbereichs des Chips geschrieben wird. Wenn ein anderer Prozess währenddessen an eine andere Stelle im Chip schreiben würde, würde der Programmierzyklus fehlschlagen.

In einigen Fällen wird die Hardware Adressen umschließen (die wichtigsten Adressbits / -bytes werden ignoriert), sodass das Schreiben an eine Adresse jenseits des Endes des physischen Adressraums tatsächlich dazu führt, dass Daten mitten im Geschehen geschrieben werden.

Und schließlich können ältere CPUs wie der MC68000 so weit gesperrt werden, dass sie nur durch einen Hardware-Reset wieder in Betrieb genommen werden können. Ich habe seit ein paar Jahrzehnten nicht mehr daran gearbeitet, aber ich glaube, wenn beim Versuch, eine Ausnahme zu behandeln, ein Busfehler (nicht vorhandener Speicher) aufgetreten ist, wurde er einfach angehalten, bis der Hardware-Reset bestätigt wurde.

Meine größte Empfehlung ist ein offensichtlicher Stecker für ein Produkt, aber ich habe kein persönliches Interesse daran und bin in keiner Weise mit ihnen verbunden - sondern basiert auf ein paar Jahrzehnten C-Programmierung und eingebetteten Systemen, bei denen die Zuverlässigkeit entscheidend war, dem PC von Gimpel Lint erkennt nicht nur solche Fehler, sondern macht Sie auch ständig zu einem besseren C / C ++ - Programmierer über schlechte Gewohnheiten belästigt.

Ich würde auch empfehlen, den MISRA C-Codierungsstandard zu lesen, wenn Sie sich eine Kopie von jemandem schnappen können. Ich habe in letzter Zeit keine gesehen, aber in den alten Tagen haben sie eine gute Erklärung gegeben, warum Sie die Dinge tun sollten / nicht tun sollten, die sie abdecken.

Keine Ahnung von Ihnen, aber beim zweiten oder dritten Mal, wenn ich einen Coredump oder Hangup von einer Anwendung bekomme, sinkt meine Meinung zu der Firma, die sie produziert hat, um die Hälfte. Das 4. oder 5. Mal und was auch immer das Paket ist, wird zu Regalware und ich fahre einen Holzpfahl durch die Mitte des Pakets / der Scheibe, in die es gekommen ist, nur um sicherzustellen, dass es nie zurückkommt, um mich zu verfolgen.

Dan Haynes
quelle
Je nach System außerhalb der Reichweite Lesevorgänge auch ein unvorhersehbares Verhalten auslösen, oder sie können harmlos sein, obwohl ein harmloses Hardwareverhalten bei Lasten außerhalb des Bereichs kein harmloses Compilerverhalten impliziert.
Supercat
2

Ich arbeite mit einem Compiler für einen DSP-Chip, der absichtlich Code generiert, der über das Ende eines Arrays hinaus auf einen C-Code zugreift, der dies nicht tut!

Dies liegt daran, dass die Schleifen so strukturiert sind, dass am Ende einer Iteration einige Daten für die nächste Iteration vorab abgerufen werden. Das am Ende der letzten Iteration vorab abgerufene Datum wird also nie tatsächlich verwendet.

Das Schreiben eines solchen C-Codes ruft undefiniertes Verhalten hervor, aber das ist nur eine Formalität aus einem Standarddokument, das sich mit maximaler Portabilität befasst.

Meistens ist ein Programm, das außerhalb der Grenzen zugreift, nicht geschickt optimiert. Es ist einfach fehlerhaft. Der Code abruft einige Müll - Wert und, im Gegensatz zu den optimierten Schleifen der eingangs genannten Compiler, der Code dann verwendet , um den Wert in nachfolgenden Berechnungen, um dadurch korrumpieren theim.

Es lohnt sich, solche Fehler zu erkennen, und es lohnt sich, das Verhalten auch nur aus diesem Grund undefiniert zu machen: Damit die Laufzeit eine Diagnosemeldung wie "Array-Überlauf in Zeile 42 von main.c" erzeugen kann.

Auf Systemen mit virtuellem Speicher kann ein Array so zugewiesen werden, dass sich die folgende Adresse in einem nicht zugeordneten Bereich des virtuellen Speichers befindet. Der Zugriff bombardiert dann das Programm.

Beachten Sie außerdem, dass wir in C einen Zeiger erstellen dürfen, der hinter dem Ende eines Arrays liegt. Und dieser Zeiger muss mehr als jeder andere Zeiger mit dem Inneren eines Arrays vergleichen. Dies bedeutet, dass eine C-Implementierung ein Array nicht direkt am Ende des Speichers platzieren kann, wo die Plus-Adresse umlaufen und kleiner aussehen würde als andere Adressen im Array.

Trotzdem ist der Zugriff auf nicht initialisierte oder außerhalb der Grenzen liegende Werte manchmal eine gültige Optimierungstechnik, auch wenn sie nicht maximal portabel ist. Dies ist beispielsweise der Grund, warum das Valgrind-Tool keine Zugriffe auf nicht initialisierte Daten meldet, wenn diese Zugriffe stattfinden, sondern nur, wenn der Wert später auf eine Weise verwendet wird, die das Ergebnis des Programms beeinflussen könnte. Sie erhalten eine Diagnose wie "Bedingter Zweig in xxx: nnn hängt vom nicht initialisierten Wert ab" und es kann manchmal schwierig sein, den Ursprung zu ermitteln. Wenn alle derartigen Zugriffe sofort abgefangen würden, würde es viele falsch positive Ergebnisse geben, die sich aus compileroptimiertem Code sowie korrekt handoptimiertem Code ergeben.

Apropos, ich habe mit einem Codec eines Anbieters gearbeitet, der diese Fehler ausgab, wenn er auf Linux portiert und unter Valgrind ausgeführt wurde. Aber der Verkäufer hat mich davon überzeugt, dass nur einige BitsDer verwendete Wert stammte tatsächlich aus dem nicht initialisierten Speicher, und diese Bits wurden von der Logik sorgfältig vermieden. Es wurden nur die guten Bits des Werts verwendet, und Valgrind kann nicht auf das einzelne Bit zurückgreifen. Das nicht initialisierte Material stammt aus dem Lesen eines Wortes nach dem Ende eines Bitstroms codierter Daten, aber der Code weiß, wie viele Bits sich im Stream befinden, und verwendet nicht mehr Bits als tatsächlich vorhanden sind. Da der Zugriff über das Ende des Bitstrom-Arrays hinaus keinen Schaden für die DSP-Architektur verursacht (es gibt keinen virtuellen Speicher nach dem Array, keine speicherabgebildeten Ports und die Adresse wird nicht umbrochen), handelt es sich um eine gültige Optimierungstechnik.

"Undefiniertes Verhalten" bedeutet nicht wirklich viel, denn laut ISO C sind das einfache Einfügen eines Headers, der nicht im C-Standard definiert ist, oder das Aufrufen einer Funktion, die nicht im Programm selbst oder im C-Standard definiert ist, Beispiele für undefiniertes Verhalten Verhalten. Undefiniertes Verhalten bedeutet nicht "von niemandem auf dem Planeten definiert", sondern nur "nicht von der ISO C-Norm definiert". Aber natürlich manchmal wirklich nicht definiertes Verhalten ist absolut nicht von jedermann definiert.

Kaz
quelle
Vorausgesetzt, es gibt mindestens ein Programm, das von einer bestimmten Implementierung korrekt verarbeitet wird, obwohl es nominell alle im Standard angegebenen Implementierungsgrenzen besteuert, könnte sich diese Implementierung willkürlich verhalten, wenn ein anderes Programm gespeist wird, das frei von Einschränkungsverletzungen ist und dennoch " konforme". Folglich stützen sich 99,999% der C-Programme (alles andere als das "eine Programm" einer Plattform) auf Verhaltensweisen, bei denen der Standard keine Anforderungen stellt.
Supercat
1

Abgesehen von Ihrem eigenen Programm glaube ich nicht, dass Sie irgendetwas kaputt machen werden. Im schlimmsten Fall werden Sie versuchen, von einer Speicheradresse zu lesen oder zu schreiben, die einer Seite entspricht, die der Kernel Ihren Prozessen nicht zugewiesen hat, und die richtige Ausnahme erzeugen und getötet zu werden (ich meine, dein Prozess).

jbgs
quelle
3
..Was? Wie wäre es, wenn Sie den Speicher in Ihrem eigenen Prozess überschreiben, der zum Speichern einer später verwendeten Variablen verwendet wird ... was nun auf mysteriöse Weise seinen Wert geändert hat! Ich versichere Ihnen, dass es eine Menge Spaß macht, diese Fehler aufzuspüren. Ein Segfault wäre das beste Ergebnis. -1
Ed S.
2
Ich meine, er wird außer seinem eigenen Programm keine anderen Prozesse "
unterbrechen"
Es ist mir in der Tat egal, ob ich mein eigenes Programm breche. Ich lerne gerade, das Programm ist offensichtlich sowieso falsch, wenn ich auf etwas außerhalb meines Arrays zugreife. Ich
mache
Die Sache ist: Kann ich sicher sein, dass mein Prozess abgebrochen wird, wenn ich versuche, auf den mir nicht zugewiesenen Speicher zuzugreifen? (unter OSX)
ChrisD
3
Vor Jahren war ich ein ungeschickter C-Programmierer. Ich habe hunderte Male auf Arrays außerhalb ihrer Grenzen zugegriffen. Abgesehen davon, dass mein Prozess vom Betriebssystem beendet wurde, ist nie etwas passiert.
jbgs