War Speicherbeschädigung ein häufiges Problem bei großen Programmen, die in Assemblersprache geschrieben wurden?
Speicherbeschädigungsfehler waren in großen C-Programmen und -Projekten immer ein häufiges Problem. Es war damals ein Problem in 4.3BSD und ist heute noch ein Problem. Unabhängig davon, wie sorgfältig das Programm geschrieben ist, ist es bei ausreichender Größe häufig möglich, einen weiteren nicht gebundenen Lese- oder Schreibfehler im Code zu entdecken.
Es gab jedoch eine Zeit, in der große Programme, einschließlich Betriebssysteme, in Assembly geschrieben wurden, nicht C. Waren Speicherbeschädigungsfehler ein häufiges Problem in großen Assemblyprogrammen? Und wie war der Vergleich zu C-Programmen?
Antworten
Das Codieren in der Montage ist brutal.
Schurkenzeiger
Assemblersprachen stützen sich noch mehr auf Zeiger (über Adressregister), sodass Sie sich nicht einmal auf den Compiler oder statische Analysewerkzeuge verlassen können, um Sie vor solchen Speicherbeschädigungen / Pufferüberläufen zu warnen, im Gegensatz zu C.
In C kann beispielsweise ein guter Compiler dort eine Warnung ausgeben:
char x[10];
x[20] = 'c';
Das ist begrenzt. Sobald das Array in einen Zeiger zerfällt, können solche Überprüfungen nicht durchgeführt werden, aber das ist ein Anfang.
In der Assembly können Sie solche Fehler ohne ordnungsgemäße Laufzeit- oder formale Ausführungs-Binärwerkzeuge nicht erkennen.
Schurkenregister (meistens Adresse)
Ein weiterer erschwerender Faktor für die Montage ist, dass die Konvention zur Aufbewahrung von Registern und zum routinemäßigen Aufruf nicht Standard / Garantie ist.
Wenn eine Routine aufgerufen wird und ein bestimmtes Register nicht versehentlich speichert, kehrt sie mit einem geänderten Register (neben den "Scratch" -Registern, von denen bekannt ist, dass sie beim Beenden verworfen werden) zum Anrufer zurück, und der Anrufer erwartet dies nicht es, was zum Lesen / Schreiben an die falsche Adresse führt. Zum Beispiel in 68k Code:
move.b d0,(a3)+
bsr a_routine
move.b d0,(a3)+ ; memory corruption, a3 has changed unexpectedly
...
a_routine:
movem.l a0-a2,-(a7)
; do stuff
lea some_table(pc),a3 ; change a3 if some condition is met
movem.l (a7)+,a0-a2 ; the routine forgot to save a3 !
rts
Die Verwendung einer Routine, die von einer anderen Person geschrieben wurde, die nicht dieselben Konventionen zum Speichern von Registern verwendet, kann zu demselben Problem führen. Normalerweise speichere ich alle Register, bevor ich die Routine eines anderen verwende.
Auf der anderen Seite verwendet ein Compiler die Stapel- oder Standardregisterparameterübergabe, verarbeitet lokale Variablen mithilfe des Stapels / eines anderen Geräts, behält bei Bedarf die Register bei und alles ist im gesamten Programm kohärent, was vom Compiler garantiert wird (sofern keine Fehler vorliegen) Kurs)
Rogue-Adressierungsmodi
Ich habe viele Speicherverletzungen in alten Amiga-Spielen behoben. Das Ausführen in einer virtuellen Umgebung mit aktivierter MMU löst manchmal Lese- / Schreibfehler in vollständigen Scheinadressen aus. Meistens haben diese Lese- / Schreibvorgänge keine Auswirkung, da die Lesevorgänge 0 zurückgeben und die Schreibvorgänge in den Wald gehen. Abhängig von der Speicherkonfiguration kann dies jedoch schlimme Folgen haben.
Es gab auch Fälle, in denen Fehler behoben wurden. Ich habe Sachen gesehen wie:
move.l $40000,a0
statt sofort
move.l #$40000,a0
In diesem Fall enthält das Adressregister den Inhalt $40000
(wahrscheinlich den Papierkorb) und nicht die $40000
Adresse. Dies führt in einigen Fällen zu einer katastrophalen Speicherbeschädigung. Das Spiel führt normalerweise die Aktion aus, die an keiner anderen Stelle funktioniert hat, ohne dies zu beheben, sodass das Spiel die meiste Zeit ordnungsgemäß funktioniert. Es gibt jedoch Zeiten, in denen die Spiele ordnungsgemäß repariert werden mussten, um das ordnungsgemäße Verhalten wiederherzustellen.
In C führt die Irreführung eines Werts für einen Zeiger zu einer Warnung.
(Wir haben ein Spiel wie "Wicked" aufgegeben, das immer mehr grafische Korruption aufwies, je fortgeschrittener Sie in den Levels waren, aber auch abhängig von der Art und Weise, wie Sie die Levels bestanden haben und ihrer Reihenfolge ...)
Rogue-Datengrößen
In der Montage gibt es keine Typen. Es bedeutet, dass wenn ich es tue
move.w #$4000,d0 ; copy only 16 bits
move.l #1,(a0,d0.l) ; indexed write on d1, long
Das d0
Register erhält nur die Hälfte der geänderten Daten. Vielleicht war es das, was ich wollte, vielleicht auch nicht. Wenn d0
der Code dann auf den höchstwertigen 32-16 Bits Null enthält, macht er das, was erwartet wird, andernfalls fügt er a0
und d0
(voller Bereich) hinzu und der resultierende Schreibvorgang ist "im Wald". Ein Fix ist:
move.l #1,(a0,d0.w) ; indexed write on d1, long
Aber wenn d0
> $7FFF
es auch etwas falsch macht, weil d0
es dann als negativ angesehen wird (nicht der Fall bei d0.l
). Benötigt also d0
Zeichenerweiterung oder Maskierung ...
Diese Größenfehler können in einem C-Code angezeigt werden, beispielsweise beim Zuweisen zu einer short
Variablen (wodurch das Ergebnis abgeschnitten wird), aber selbst dann erhalten Sie die meiste Zeit nur ein falsches Ergebnis, keine schwerwiegenden Probleme wie oben (dh, wenn Sie dies nicht tun) ‚t liegen an den Compiler durch erzwingen falschen Typ Abgüsse)
Assembler haben keine Typen, aber gute Assembler erlauben die Verwendung von Strukturen ( STRUCT
Schlüsselwort), mit denen der Code durch automatische Berechnung von Strukturversätzen ein wenig erhöht werden kann. Ein Lesevorgang mit schlechter Größe kann jedoch katastrophal sein, unabhängig davon, ob Sie Strukturen / definierte Offsets verwenden oder nicht
move.w the_offset(a0),d0
Anstatt von
move.l the_offset(a0),d0
wird nicht überprüft und gibt Ihnen die falschen Daten ein d0
. Stellen Sie sicher, dass Sie beim Codieren genügend Kaffee trinken, oder schreiben Sie stattdessen einfach eine Dokumentation ...
Rogue-Datenausrichtung
Der Assembler warnt normalerweise vor nicht ausgerichtetem Code, jedoch nicht vor nicht ausgerichteten Zeigern (da Zeiger keinen Typ haben), die Busfehler auslösen können.
Hochsprachen verwenden Typen und vermeiden die meisten dieser Fehler durch Ausrichten / Auffüllen (es sei denn, sie werden erneut belogen).
Sie können Assembler-Programme jedoch erfolgreich schreiben. Durch die Verwendung einer strengen Methode zum Übergeben von Parametern / Speichern von Registern und durch den Versuch, 100% Ihres Codes durch Tests und einen Debugger abzudecken (symbolisch oder nicht, ist dies immer noch der Code, den Sie geschrieben haben). Das wird nicht alle potenziellen Fehler beseitigen, insbesondere diejenigen, die durch falsche Eingabedaten verursacht werden, aber es wird helfen.
Ich habe den größten Teil meiner Karriere damit verbracht, Assembler, Solo, kleine Teams und große Teams (Cray, SGI, Sun, Oracle) zu schreiben. Ich habe an eingebetteten Systemen, Betriebssystemen, VMs und Bootstrap-Ladern gearbeitet. Speicherbeschädigung war selten, wenn überhaupt, ein Problem. Wir haben scharfe Leute eingestellt, und diejenigen, die versagt haben, wurden in verschiedene Jobs versetzt, die ihren Fähigkeiten besser entsprechen.
Wir haben auch fanatisch getestet - sowohl auf Geräte- als auch auf Systemebene. Wir hatten automatisierte Tests, die ständig sowohl auf Simulatoren als auch auf realer Hardware ausgeführt wurden.
Gegen Ende meiner Karriere habe ich ein Interview mit einem Unternehmen geführt und gefragt, wie sie ihre automatisierten Tests durchgeführt haben. Ihre Antwort von "Was?!?" war alles was ich hören musste, ich beendete das Interview.
Einfache idiotische Fehler sind bei der Montage im Überfluss vorhanden, egal wie vorsichtig Sie sind. Es stellt sich heraus, dass selbst dumme Compiler für schlecht definierte Hochsprachen (wie C) eine Vielzahl möglicher Fehler als semantisch oder syntaktisch ungültig einschränken. Ein Fehler mit einem einzelnen zusätzlichen oder vergessenen Tastendruck lehnt die Kompilierung mit größerer Wahrscheinlichkeit ab als das Zusammenbauen. Konstrukte, die Sie in Assembly gültig ausdrücken können und die einfach keinen Sinn ergeben, weil Sie alles falsch machen, werden mit geringerer Wahrscheinlichkeit in etwas übersetzt, das als gültiges C akzeptiert wird. Und da Sie auf einer höheren Ebene arbeiten, sind Sie es eher schielen und "huh?" und schreibe das Monster um, das du gerade geschrieben hast.
Das Entwickeln und Debuggen von Assemblys ist in der Tat schmerzlich unversöhnlich. Aber die meisten dieser Fehler brechen die Dinge schwer und würden sich in der Entwicklung und im Debugging zeigen. Ich würde die fundierte Vermutung in Frage stellen, dass das Endprodukt ungefähr so robust sein sollte, wenn die Entwickler dieselbe grundlegende Architektur und dieselben guten Entwicklungspraktiken befolgen. Die Art von Fehlern, die ein Compiler abfängt, kann mit guten Entwicklungspraktiken abgefangen werden, und die Art von Fehlern, die Compiler nicht abfangen, kann mit solchen Praktiken abgefangen werden oder auch nicht. Es wird jedoch viel länger dauern, bis das gleiche Niveau erreicht ist.
Ich habe 1971-72 den ursprünglichen Garbage Collector für MDL geschrieben, eine Lisp-ähnliche Sprache. Das war damals eine ziemliche Herausforderung für mich. Es wurde in MIDAS geschrieben, einem Assembler für den PDP-10, auf dem ITS ausgeführt wird.
Das Vermeiden von Speicherbeschädigung war der Name des Spiels in diesem Projekt. Das gesamte Team hatte Angst vor einer erfolgreichen Demo, die abstürzte und brannte, als der Garbage Collector aufgerufen wurde. Und ich hatte keinen wirklich guten Debugging-Plan für diesen Code. Ich habe mehr Schreibtischprüfungen durchgeführt als jemals zuvor oder seitdem. Sachen wie sicherstellen, dass es keine Zaunpfostenfehler gab. Stellen Sie sicher, dass das Ziel beim Verschieben einer Gruppe von Vektoren keinen Nicht-Müll enthielt. Immer wieder meine Annahmen testen.
Ich habe in diesem Code keine Fehler gefunden, außer solchen, die durch Überprüfung am Schreibtisch gefunden wurden. Nachdem wir live gegangen waren, tauchte während meiner Uhr niemand mehr auf.
Ich bin einfach nicht so schlau wie vor fünfzig Jahren. So etwas konnte ich heute nicht machen. Und Systeme von heute sind tausendmal größer als MDL.
Speicherbeschädigungsfehler waren in großen C-Programmen immer ein häufiges Problem. [...] Es gab jedoch eine Zeit, in der große Programme, einschließlich Betriebssysteme, in Assembly geschrieben wurden, nicht in C.
Sie wissen, dass es andere Sprachen gibt, die schon früh verbreitet waren? Wie COBOL, FORTRAN oder PL / 1?
Waren Speicherbeschädigungsfehler ein häufiges Problem in großen Assemblyprogrammen?
Dies hängt natürlich von mehreren Faktoren ab, wie z
- Der Assembler wird verwendet, da verschiedene Assembler-Programme unterschiedliche Programmierunterstützung bieten.
- Programmstruktur, da besonders große Programme an einer überprüfbaren Struktur festhalten
- Modularisierung und klare Schnittstellen
- die Art des geschriebenen Programms, da nicht jede Aufgabe Zeigerfummeln erfordert
- Best-Practice-Stil
Ein guter Assembler stellt nicht nur sicher, dass die Daten ausgerichtet sind, sondern bietet auch Tools, mit denen komplexe Datentypen, Strukturen und dergleichen abstrakt behandelt werden können, sodass Zeiger nicht mehr manuell berechnet werden müssen.
Ein Assembler, der für jedes ernsthafte Projekt verwendet wird, ist wie immer ein Makro-Assembler (* 1), der in der Lage ist, primitive Operationen in übergeordnete Makrobefehle einzuschließen, wodurch eine anwendungsorientiertere Programmierung ermöglicht wird und viele Fallstricke bei der Zeigerbehandlung vermieden werden (* 2).
Programmtypen sind auch sehr einflussreich. Anwendungen bestehen normalerweise aus verschiedenen Modulen, von denen viele fast oder vollständig ohne (oder nur kontrollierte) Zeigerverwendung geschrieben werden können. Auch hier ist die Verwendung der vom Assembler bereitgestellten Tools der Schlüssel zu weniger fehlerhaftem Code.
Als nächstes wäre die beste Vorgehensweise - die mit vielen der vorherigen Hand in Hand geht. Schreiben Sie einfach keine Programme / Module, die mehrere Basisregister benötigen, die große Speicherblöcke anstelle dedizierter Anforderungsstrukturen usw. übergeben ...
Best Practice beginnt jedoch bereits früh und mit scheinbar einfachen Dingen. Nehmen Sie nur das Beispiel einer primitiven (sorry) CPU wie der 6502 mit möglicherweise einer Reihe von Tabellen, die alle an die Seitenränder angepasst sind, um die Leistung zu gewährleisten. Wenn Sie die Adresse einer dieser Tabellen für den indizierten Zugriff in einen Nullseitenzeiger laden, bedeutet die Verwendung der Tools, die der Assembler verwenden möchte
LDA #<Table
STA Pointer
Einige Programme, die ich gesehen habe, gehen lieber
LDA #0
STA Pointer
(oder schlimmer, wenn auf einem 65C02)
STZ Pointer
Die übliche Argumentation lautet "Aber es ist trotzdem ausgerichtet". Ist es? Kann das für alle zukünftigen Iterationen garantiert werden? Was ist mit einem Tag, an dem der Adressraum knapp wird und sie an nicht ausgerichtete Adressen verschoben werden müssen? Viele große (auch schwer zu findende) Fehler sind zu erwarten.
Best Practice bringt uns also wieder dazu, den Assembler und alle darin enthaltenen Tools zu verwenden.
Versuchen Sie nicht, Assembler anstelle des Assemblers zu spielen - lassen Sie ihn seinen Job für Sie erledigen.
Und dann ist da noch die Laufzeit, die für alle Sprachen gilt, aber oft vergessen wird. Neben Dingen wie der Stapelprüfung oder der Begrenzungsprüfung von Parametern besteht eine der effektivsten Möglichkeiten, Zeigerfehler abzufangen, darin, die erste und letzte Speicherseite gegen Schreiben und Lesen zu sperren (* 3). Es fängt nicht nur den beliebten Nullzeigerfehler ab, sondern auch alle niedrigen positiven oder negativen Zahlen, die häufig darauf zurückzuführen sind, dass eine vorherige Indizierung fehlgeschlagen ist. Sicher, Runtime ist immer der letzte Ausweg, aber dieser ist einfach.
Vor allem ist vielleicht der relevanteste Grund
- die ISA der Maschine
bei der Verringerung der Wahrscheinlichkeit einer Speicherbeschädigung durch Verringerung der Notwendigkeit, überhaupt mit Zeigern umzugehen.
Einige CPU-Strukturen erfordern einfach weniger (direkte) Zeigeroperationen als andere. Es gibt eine große Lücke zwischen Architekturen, die Speicher-zu-Speicher-Operationen enthalten, und solchen, die dies nicht tun, wie z. B. akkumulatorbasierte Lade- / Speicherarchitekturen. Sie erfordern von Natur aus eine Zeigerbehandlung für alles, was größer als ein einzelnes Element (Byte / Wort) ist.
Um beispielsweise ein Feld zu übertragen, beispielsweise einen Kundennamen aus dem Speicher, verwendet a / 360 eine einzelne MVC-Operation mit Adressen und Übertragungslängen, die vom Assembler aus der Datendefinition generiert wurden, während eine Lade- / Speicherarchitektur für jedes Byte ausgelegt ist getrennt, muss Zeiger und Länge in Registern einrichten und um sich bewegende einzelne Elemente schleifen.
Da solche Operationen häufig vorkommen, ist auch das daraus resultierende Fehlerpotential häufig. Oder allgemeiner gesagt:
Programme für CISC-Prozessoren sind normalerweise weniger fehleranfällig als solche, die für RISC-Maschinen geschrieben wurden.
Natürlich und wie immer kann alles durch schlechte Programmierung vermasselt werden.
Und wie war der Vergleich zu C-Programmen?
Ähnlich - oder besser, C ist das HLL-Äquivalent der primitivsten CPU-ISA, sodass alles, was Anweisungen auf höherer Ebene bietet, besser ist.
C ist von Natur aus eine RISCy-Sprache. Die bereitgestellten Vorgänge werden auf ein Minimum reduziert, was mit einer Mindestfähigkeit zur Überprüfung auf unbeabsichtigte Vorgänge einhergeht. Die Verwendung von nicht aktivierten Zeigern ist nicht nur Standard, sondern für viele Vorgänge erforderlich, was viele Möglichkeiten für Speicherbeschädigungen eröffnet.
Nehmen wir im Gegensatz zu einer HLL wie ADA, hier ist es fast unmöglich, Zeiger-Chaos zu verursachen - es sei denn, es ist beabsichtigt und explizit als Option deklariert. Ein großer Teil davon ist (wie zuvor bei der ISA) auf höhere Datentypen und deren typsichere Handhabung zurückzuführen.
Für den Erfahrungsteil habe ich den größten Teil meines Berufslebens (> 30 Jahre) in Montageprojekten verbracht, mit etwa 80% Mainframe (/ 370) 20% Micros (meistens 8080 / x86) - plus viel mehr privat :) Mainframe-Programmierung deckte Projekte ab so groß wie 2+ Millionen LOC (nur Anweisungen), während Mikroprojekte etwa 10-20.000 LOC hielten.
* 1 - Nein, etwas, das das Ersetzen von Textpassagen durch vorgefertigten Text bietet, ist bestenfalls ein Textpräprozessor, aber kein Makro-Assembler. Ein Makro-Assembler ist ein Meta-Tool zum Erstellen der für ein Projekt erforderlichen Sprache. Es bietet Tools zum Tippen auf die Informationen, die der Assembler über die Quelle sammelt (Feldgröße, Feldtyp und vieles mehr), sowie Kontrollstrukturen zum Formulieren der Behandlung, die zum Generieren von geeignetem Code verwendet werden.
* 2 - Es ist leicht zu beklagen, dass C nicht mit ernsthaften Makrofunktionen ausgestattet war, sondern nicht nur viele obskure Konstrukte überflüssig machte, sondern auch viele Fortschritte ermöglichte, indem die Sprache erweitert wurde, ohne dass eine neue geschrieben werden musste.
* 3 - Ich persönlich bevorzuge es, Seite 0 nur schreibgeschützt zu machen und die ersten 256 Bytes mit binärer Null zu füllen. Auf diese Weise führen alle Null- (oder Niedrig-) Zeigerschreibvorgänge immer noch zu einem Maschinenfehler, aber das Lesen von einem Nullzeiger gibt je nach Typ ein Byte / Halbwort / Wort / Doppelwort zurück, das Null enthält - na ja, oder eine Nullzeichenfolge :) Ich weiß, es ist faul, aber es macht das Leben viel einfacher, wenn man einfach den Code anderer Leute inkorporieren muss. Die verbleibende Seite kann auch für praktische konstante Werte wie Zeiger auf verschiedene globale Quellen, ID-Zeichenfolgen, konstanten Feldinhalt und Übersetzungstabellen verwendet werden.
Ich habe OS-Mods in Assembly auf CDC G-21, Univac 1108, DECSystem-10, DECSystem-20, allen 36-Bit-Systemen sowie 2 IBM 1401-Assemblern geschrieben.
"Speicherbeschädigung" existierte hauptsächlich als Eintrag in einer Liste "Dinge, die nicht zu tun sind".
Auf einem Univac 1108 fand ich einen Hardwarefehler, bei dem der erste Halbwortabruf (die Interrupt-Handler-Adresse) nach einem Hardware-Interrupt alle 1s anstelle des Inhalts der Adresse zurückgab. Auf ins Unkraut, mit deaktivierten Interrupts, kein Speicherschutz. Rund und rund geht es, wo es aufhört, weiß niemand.
Sie vergleichen Äpfel und Birnen. Hochsprachen wurden erfunden, weil Programme eine Größe erreichten, die mit Assembler nicht zu handhaben war. Beispiel: "V1 hatte 4.501 Zeilen Assembler-Code für Kernel, Initialisierung und Shell. Davon entfallen 3.976 auf den Kernel und 374 auf die Shell." (Aus dieser Antwort .)
Das. V1. Schale. Hätten. 347. Linien. Von. Code.
Die heutige Bash hat vielleicht 100.000 Codezeilen (ein WC über dem Repo ergibt 170.000), Zentralbibliotheken wie Readline und Lokalisierung werden nicht berücksichtigt. Hochsprachen werden teilweise aus Gründen der Portabilität verwendet, aber auch, weil es praktisch unmöglich ist, Programme der heutigen Größe in Assembler zu schreiben. Es ist nicht nur fehleranfälliger - es ist nahezu unmöglich.
Ich denke nicht, dass Speicherbeschädigung in Assemblersprache im Allgemeinen ein größeres Problem darstellt als in jeder anderen Sprache, die ungeprüfte Array-Subskriptionsoperationen verwendet, wenn Programme verglichen werden, die ähnliche Aufgaben ausführen. Während das Schreiben des richtigen Assembler-Codes möglicherweise die Beachtung von Details erfordert, die über diejenigen hinausgehen, die in einer Sprache wie C relevant wären, sind einige Aspekte der Assemblersprache tatsächlich sicherer als C. In der Assemblersprache wird ein Assembler eine Folge von Lade- und Speichervorgängen ausführen Erstellen Sie Lade- und Lageranweisungen in der angegebenen Reihenfolge, ohne zu hinterfragen, ob sie alle erforderlich sind. In C hingegen, wenn ein cleverer Compiler wie clang mit einer anderen Optimierungseinstellung aufgerufen wird als -O0
und mit etwas wie:
extern char x[],y[];
int test(int index)
{
y[0] = 1;
if (x+2 == y+index)
y[index] = 2;
return y[0];
}
es kann bestimmen, dass der Wert , y[0]
wenn die return
Anweisung ausgeführt werden immer 1 sein wird, und es gibt also keine Notwendigkeit , seinen Wert nach dem Schreiben auf neu zu laden y[index]
, auch wenn der einzigen definierte Umstand, dass der Schreib zu indizieren , wenn wäre auftreten könnte x[]
zwei Bytes sind, y[]
geschieht um ihm sofort zu folgen, und index
ist Null, was bedeutet, dass y[0]
tatsächlich die Nummer 2 übrig bleiben würde.
Assembler erfordert genauere Kenntnisse der von Ihnen verwendeten Hardware als andere Sprachen wie C oder Java. Die Wahrheit ist jedoch, dass Assembler in fast allen Bereichen eingesetzt wurden, von den ersten computergestützten Autos über frühe Videospielsysteme bis in die 90er Jahre bis hin zu den Internet-of-Things-Geräten, die wir heute verwenden.
Während C Typensicherheit bot, bot es dennoch keine anderen Sicherheitsmaßnahmen wie die Überprüfung von ungültigen Zeigern oder begrenzte Arrays (zumindest nicht ohne zusätzlichen Code). Es war ziemlich einfach, ein Programm zu schreiben, das abstürzt und brennt, sowie jedes Assembler-Programm.
Zehntausende von Videospielen wurden in Assembler geschrieben, Kompositionen , um seit Jahrzehnten kleine, aber beeindruckende Demos in nur wenigen Kilobyte Code / Daten zu schreiben. Tausende von Autos verwenden heute noch eine Art Assembler und einige weniger bekannte Betriebssysteme (zB MenuetOS ). Möglicherweise haben Sie Dutzende oder sogar Hunderte von Dingen in Ihrem Haus, die in Assembler programmiert wurden und von denen Sie noch nicht einmal wissen.
Das Hauptproblem bei der Assembly-Programmierung besteht darin, dass Sie energischer planen müssen als in einer Sprache wie C. Es ist durchaus möglich, ein Programm mit sogar 100.000 Codezeilen in Assembler ohne einen einzigen Fehler zu schreiben, und es ist auch möglich, ein Programm zu schreiben Programm mit 20 Codezeilen mit 5 Fehlern.
Es ist nicht das Werkzeug, das das Problem ist, es ist der Programmierer. Ich würde sagen, dass Speicherbeschädigung ein häufiges Problem bei der frühen Programmierung im Allgemeinen war. Dies war nicht nur auf Assembler beschränkt, sondern auch auf C (das für das Verlieren von Speicher und den Zugriff auf ungültige Speicherbereiche berüchtigt war), C ++ und andere Sprachen, in denen Sie direkt auf den Speicher zugreifen konnten, sogar BASIC (das die Fähigkeit hatte, bestimmte E / A zu lesen / schreiben). O Ports an der CPU).
Selbst bei modernen Sprachen mit Sicherheitsvorkehrungen werden Programmierfehler auftreten, die zum Absturz von Spielen führen. Warum? Weil beim Entwerfen der Anwendung nicht genügend Sorgfalt angewendet wird. Die Speicherverwaltung ist nicht verschwunden, sondern befindet sich in einer Ecke, in der es schwieriger ist, sie zu visualisieren, was im modernen Code alle Arten von zufälligem Chaos verursacht.
Praktisch jede Sprache ist bei falscher Verwendung anfällig für verschiedene Arten von Speicherbeschädigungen. Das häufigste Problem sind heute Speicherlecks, die aufgrund von Schließungen und Abstraktionen leichter als je zuvor versehentlich auftreten können.
Es ist unfair zu sagen, dass Assembler von Natur aus mehr oder weniger speicherschädigend war als andere Sprachen. Es hat nur einen schlechten Ruf bekommen, weil es schwierig war, richtigen Code zu schreiben.
Es war ein sehr häufiges Problem. Der FORTRAN-Compiler von IBM für den 1130 hatte einige: Die, an die ich mich erinnere, betrafen Fälle falscher Syntax, die nicht erkannt wurden. Der Wechsel zu maschinennahen höheren Sprachen hat offensichtlich nicht geholfen: Frühe Multics-Systeme, die in PL / I geschrieben wurden, stürzten häufig ab. Ich denke, dass Programmierkultur und -technik mehr mit der Verbesserung dieser Situation zu tun hatten als die Sprache.
Ich habe ein paar Jahre Assembler-Programmierung durchgeführt, gefolgt von Jahrzehnten C. Assembler-Programme schienen keine schlimmeren Zeigerfehler als C zu haben, aber ein wesentlicher Grund dafür war, dass die Assembler-Programmierung vergleichsweise langsam ist.
Die Teams, in denen ich tätig war, wollten ihre Arbeit jedes Mal testen, wenn sie einen Funktionszuwachs geschrieben hatten, der normalerweise alle 10 bis 20 Assembler-Anweisungen bestand. In höheren Sprachen testen Sie normalerweise nach einer ähnlichen Anzahl von Codezeilen, die viel mehr Funktionen bieten. Das geht gegen die Sicherheit einer HLL.
Assembler wurde nicht mehr für umfangreiche Programmieraufgaben verwendet, da dies zu einer geringeren Produktivität führte und normalerweise nicht auf andere Computertypen portierbar war. In den letzten 25 Jahren habe ich ungefähr 8 Zeilen Assembler geschrieben, um Fehlerbedingungen zum Testen eines Fehlerbehandlers zu generieren.
Nicht, als ich damals mit Computern arbeitete. Wir hatten viele Probleme, aber ich bin nie auf Probleme mit der Speicherbeschädigung gestoßen.
Jetzt habe ich an mehreren IBM-Maschinen 7090, 360, 370, s / 3, s / 7 sowie an 8080- und Z80-basierten Mikros gearbeitet. Andere Computer hatten möglicherweise Speicherprobleme.