Es befand sich tatsächlich in einer Drittanbieter-Bildbetrachter-Unterkomponente unserer Anwendung.
Wir stellten fest, dass es 2-3 Benutzer unserer Anwendung gab, die die Bildbetrachterkomponente häufig eine Ausnahme auslösen und fürchterlich sterben ließen. Wir hatten jedoch Dutzende anderer Benutzer, die das Problem nie gesehen haben, obwohl die Anwendung den größten Teil des Arbeitstages für dieselbe Aufgabe verwendet wurde. Auch gab es einen bestimmten Benutzer, der es viel häufiger als der Rest von ihnen bekam.
Wir haben die üblichen Schritte ausprobiert:
(1) Haben sie Computer mit einem anderen Benutzer wechseln lassen, der nie das Problem hatte, den Computer / die Konfiguration auszuschließen. - Das Problem folgte ihnen.
(2) Sie mussten sich bei der Anwendung anmelden und als Benutzer arbeiten, der das Problem nie gesehen hat. - Das Problem ist ihnen NOCH gefolgt.
(3) Der Benutzer meldete, welches Bild angezeigt wurde, und richtete ein Testkabel ein, um die Anzeige dieses Bildes Tausende Male in schneller Folge zu wiederholen. Das Problem trat nicht im Geschirr auf.
(4) Lassen Sie einen Entwickler mit den Benutzern zusammensitzen und sie den ganzen Tag beobachten. Sie sahen die Fehler, bemerkten aber nicht, dass sie etwas Ungewöhnliches taten, um sie zu verursachen.
Wir hatten wochenlang damit zu kämpfen, um herauszufinden, was die "Fehlerbenutzer" gemeinsam hatten, was die anderen Benutzer nicht hatten. Ich habe keine Ahnung, wie, aber der Entwickler in Schritt (4) hatte einen Moment Zeit, um eines Tages Encyclopedia Brown würdig zu arbeiten.
Er erkannte, dass alle "Error User" Linkshänder waren und bestätigte diese Tatsache. Nur Linkshänder erhielten die Fehler, niemals Righties. Aber wie könnte Linkshänder einen Bug verursachen?
Wir ließen ihn sich hinsetzen und den Linkshändern wieder zusehen, wie sie speziell auf alles achteten, was sie möglicherweise anders machten, und so fanden wir es.
Es stellte sich heraus, dass der Fehler nur auftrat, wenn Sie die Maus in der Bildanzeige in die äußerste rechte Pixelspalte bewegten, während ein neues Bild geladen wurde (Überlauffehler, da der Anbieter eine 1-off-Berechnung für das Mouseover-Ereignis hatte).
Offensichtlich haben die Benutzer beim Warten auf das Laden des nächsten Bildes ihre Hand (und damit die Maus) auf natürliche Weise in Richtung Tastatur bewegt.
Die eine Benutzerin, die den Fehler am häufigsten bekam, war eine der ADD-Arten, die ihre Maus ungeduldig herumbewegte, während sie darauf wartete, dass die nächste Seite geladen wurde. Sie bewegte die Maus also viel schneller nach rechts und traf die Timing genau richtig, also tat sie es, als das Lastereignis passierte. Bis wir eine Lösung vom Verkäufer erhalten haben, sagten wir ihr, sie solle die Maus nach dem Klicken loslassen (nächstes Dokument) und sie erst berühren, wenn sie geladen ist.
Es wurde fortan in der Legende des Entwicklerteams als "The Left Handed Bug" bezeichnet.
Das ist lange her (Ende der 1980er Jahre).
Die Firma, für die ich gearbeitet habe, hat ein CAD-Paket (in FORTRAN) geschrieben, das auf verschiedenen Unix-Workstations (HP, Sun, Silcon Graphics usw.) ausgeführt wird. Wir haben unser eigenes Dateiformat verwendet, um die Daten zu speichern, und als das Paket gestartet wurde, war der Speicherplatz knapp, sodass viele Bitverschiebungen zum Speichern mehrerer Flags in Entity-Headern verwendet wurden.
Der Typ der Entität (Linie, Bogen, Text usw.) wurde beim Speichern mit 4096 (glaube ich) multipliziert. Außerdem wurde dieser Wert negiert, um auf ein gelöschtes Element hinzuweisen. Um den Typ zu erhalten, hatten wir folgenden Code:
Auf jeder Maschine außer einer gab dies ± 1 (für eine Linie), ± 2 (für einen Bogen) usw. und wir konnten dann das Zeichen überprüfen, um zu sehen, ob es gelöscht wurde.
Auf einer Maschine (HP, glaube ich) hatten wir ein seltsames Problem, bei dem der Umgang mit gelöschten Elementen durcheinander gebracht wurde.
Dies war in den Tagen vor IDE's und Visual Debuggern so musste ich Trace-Anweisungen und Protokollierung einfügen, um zu versuchen, das Problem aufzuspüren.
Ich entdeckte schließlich, dass es daran lag, dass, während jeder andere Hersteller dies implementierte
MOD
, HP es mathematisch korrekt implementierte, so dass sich das-4096 MOD 4096
Ergebnis ergab .-1
-4096 MOD 4096
-4097
Am Ende musste ich die gesamte Codebasis durchgehen, um das Vorzeichen des Werts zu speichern und positiv zu machen, bevor ich
MOD
das Ergebnis durchführte und dann mit dem Vorzeichen multiplizierte.Dies dauerte mehrere Tage.
quelle
Wow, gute Lektüre hier!
Am härtesten war es vor Jahren, als Turbo Pascal groß war, obwohl es möglicherweise eines der frühen C ++ - IDEs dieser Zeit war. Als einziger Entwickler (und dritter in diesem Startup) hatte ich so etwas wie ein vereinfachtes, vertriebsfreundliches CAD-Programm geschrieben. Es war zu der Zeit großartig, entwickelte aber einen bösen zufälligen Absturz. Es war unmöglich zu reproduzieren, aber es kam häufig genug vor, dass ich mich auf die Suche nach Insekten machte.
Meine beste Strategie war es, einen Schritt im Debugger zu machen. Der Fehler trat nur auf, wenn der Benutzer genug von einer Zeichnung eingegeben hatte und sich möglicherweise in einem bestimmten Modus oder Zoomzustand befinden musste. Daher gab es viele mühsame Einstellungen und das Löschen von Haltepunkten, die normalerweise eine Minute lang ausgeführt wurden, um eine Zeichnung einzugeben, und dann schritt durch ein großes stück code. Besonders hilfreich waren Haltepunkte, bei denen eine einstellbare Anzahl von Unterbrechungen übersprungen wurde. Diese ganze Übung musste mehrmals wiederholt werden.
Irgendwann habe ich es auf eine Stelle eingegrenzt, an der ein Unterprogramm aufgerufen wurde, dem eine 2 gegeben wurde, aber von dort aus sah ich eine Kauderwelschzahl. Ich hätte das früher abfangen können, wäre aber nicht in dieses Unterprogramm eingetreten, vorausgesetzt, es hat bekommen, was es gegeben wurde. Erblindet von der Annahme, dass die einfachsten Dinge in Ordnung waren!
Es stellte sich heraus, dass ein 16-Bit-Int auf dem Stapel gespeichert wurde, die Subroutine jedoch 32-Bit erwartete. Oder etwas ähnliches. Der Compiler hat nicht automatisch alle Werte auf 32 Bit aufgefüllt oder eine ausreichende Typprüfung durchgeführt. Es war trivial zu reparieren, nur ein Teil einer Zeile, kaum ein Gedanke erforderlich. Aber um dorthin zu gelangen, brauchte man drei Tage, um zu jagen und das Offensichtliche zu hinterfragen.
Ich habe also persönliche Erfahrungen mit dieser Anekdote, dass der teure Berater hereinkommt, nach einer Weile irgendwo einen Tipp macht und $ 2000 berechnet. Die leitenden Angestellten fordern eine Aufschlüsselung, und es sind 1 US-Dollar für den Zapfhahn, 1999 US-Dollar für das Wissen, wo zu tippen ist. Außer in meinem Fall war es Zeit, nicht Geld.
Gelernte Lektionen: 1) Verwenden Sie die besten Compiler, wobei "Beste" die Überprüfung auf so viele Probleme umfasst, wie die Informatik zu überprüfen weiß, und 2) stellen Sie die einfachen offensichtlichen Dinge in Frage oder überprüfen Sie zumindest ihre ordnungsgemäße Funktion.
Seitdem waren alle schwierigen Fehler wirklich schwierig, da ich weiß, dass ich die einfachen Dinge gründlicher prüfen kann, als es notwendig erscheint.
Lektion 2 gilt auch für den härtesten Elektronikfehler, den ich jemals behoben habe, auch mit einer geringfügigen Fehlerbehebung, aber mehrere intelligente EEs waren seit Monaten ausgefallen. Aber dies ist kein Elektronik-Forum, deshalb sage ich nichts mehr darüber.
quelle
Die Netzwerk-Datenrennen-Bedingung aus der Hölle
Ich habe einen Netzwerkclient / -server (Windows XP / C #) geschrieben, um mit einer ähnlichen Anwendung auf einer wirklich alten (Encore 32/77) Workstation zu arbeiten, die von einem anderen Entwickler geschrieben wurde.
Die Anwendung hat im Wesentlichen bestimmte Daten auf dem Host freigegeben / bearbeitet, um den Host-Prozess zu steuern, auf dem das System mit unserer ausgefallenen PC-basierten Touchscreen-Benutzeroberfläche mit mehreren Monitoren ausgeführt wird.
Dies geschah mit einer dreischichtigen Struktur. Der Kommunikationsprozess liest / schreibt Daten zum / vom Host, führt alle erforderlichen Formatkonvertierungen durch (Endianness, Gleitkommaformat usw.) und schreibt / liest die Werte in / aus einer Datenbank. Die Datenbank fungierte als Datenvermittler zwischen den Kommunikations- und Touchscreen-Benutzeroberflächen. Die App der Touchscreen-Benutzeroberfläche generierte Touchscreen-Schnittstellen basierend auf der Anzahl der an den PC angeschlossenen Monitore (dies wurde automatisch erkannt).
In dem vorgegebenen Zeitrahmen konnte ein Wertepaket zwischen dem Host und unserem PC nur maximal 128 Werte gleichzeitig über das Kabel mit einer maximalen Latenz von ~ 110 ms pro Umlauf senden (UDP wurde mit einer direkten x-over-Ethernet-Verbindung zwischen verwendet die Computer). Die Anzahl der zulässigen Variablen, basierend auf der Anzahl der angeschlossenen Touchscreens, wurde streng kontrolliert. Außerdem hatte der Host (obwohl er eine ziemlich komplexe Multiprozessor-Architektur mit gemeinsam genutztem Speicherbus hat, der für Echtzeit-Computing verwendet wird) ungefähr 1/100 der Verarbeitungsleistung meines Mobiltelefons, sodass er beauftragt wurde, so wenig wie möglich zu verarbeiten und den Server zu verwenden / client musste in der Assembly geschrieben werden, um dies sicherzustellen (auf dem Host wurde eine vollständige Echtzeitsimulation ausgeführt, die von unserem Programm nicht beeinflusst werden konnte).
Das Problem war. Wenn einige Werte auf dem Touchscreen geändert werden, wird nicht nur der neu eingegebene Wert übernommen, sondern es wird nach dem Zufallsprinzip zwischen diesem Wert und dem vorherigen Wert gewechselt. Das und nur auf einigen bestimmten Werten auf einigen bestimmten Seiten mit einer bestimmten Seitenkombination zeigte sich jemals das Symptom. Wir haben das Problem fast komplett verpasst, bis wir damit begonnen haben, den anfänglichen Kundenakzeptanzprozess zu durchlaufen
Um das Problem einzugrenzen, habe ich einen der oszillierenden Werte ausgewählt:
Dann brach ich Drahthai aus und fing an, die erfassten Pakete manuell zu dekodieren. Ergebnis:
Ich ging hundertmal jedes Detail des Kommunikationscodes durch und fand keinen Fehler.
Schließlich fing ich an, E-Mails an den anderen Entwickler abzusenden und fragte ihn ausführlich, wie sein Ende funktionierte, um festzustellen, ob etwas fehlte. Dann habe ich es gefunden.
Anscheinend hat er beim Senden von Daten das Array von Daten nicht vor der Übertragung geleert, sodass er im Wesentlichen nur den zuletzt verwendeten Puffer überschrieb, wobei die neuen Werte die alten überschrieben, aber die nicht überschriebenen alten Werte noch übertragen wurden.
Wenn sich also ein Wert an Position 80 des Datenfelds befindet und die angeforderte Werteliste auf weniger als 80 geändert wird, derselbe Wert jedoch in der neuen Liste enthalten ist, sind beide Werte zu einem beliebigen Zeitpunkt im Datenpuffer für diesen bestimmten Puffer vorhanden gegebene Zeit.
Der aus der Datenbank gelesene Wert hing von der Zeitscheibe ab, in der die Benutzeroberfläche den Wert anforderte.
Die Lösung war schmerzlich einfach. Lesen Sie die Anzahl der im Datenpuffer eingehenden Elemente ein (sie war tatsächlich als Teil des Paketprotokolls enthalten) und lesen Sie den Puffer nicht über diese Anzahl von Elementen hinaus.
Gewonnene Erkenntnisse:
Nehmen Sie moderne Rechenleistung nicht für selbstverständlich. Es gab eine Zeit, in der Computer kein Ethernet unterstützten und das Leeren eines Arrays als teuer angesehen werden konnte. Wenn Sie wirklich sehen möchten, wie weit wir gekommen sind, stellen Sie sich ein System vor, das praktisch keine Form der dynamischen Speicherzuweisung hat. IE, der ausführende Prozess musste den gesamten Speicher für alle Programme der Reihe nach vorbelegen, und kein Programm konnte über diese Grenze hinauswachsen. Wenn Sie einem Programm mehr Speicher zuweisen, ohne das gesamte System neu zu kompilieren, kann dies zu einem massiven Absturz führen. Ich frage mich, ob die Leute eines Tages im selben Licht über die Tage vor der Müllabfuhr sprechen werden.
Stellen Sie beim Netzwerkbetrieb mit benutzerdefinierten Protokollen (oder beim Umgang mit der Darstellung von Binärdaten im Allgemeinen) sicher, dass Sie die Spezifikation lesen, bis Sie alle Funktionen aller über die Pipe gesendeten Werte verstanden haben. Ich meine, lies es, bis deine Augen weh tun. Menschen, die mit Daten umgehen, indem sie einzelne Bits oder Bytes manipulieren, haben sehr clevere und effiziente Möglichkeiten, Dinge zu tun. Das Fehlen kleinster Details kann das System beschädigen.
Die Gesamtzeit für die Reparatur betrug 2-3 Tage, wobei die meiste Zeit damit verbracht wurde, an anderen Dingen zu arbeiten, als ich frustriert wurde.
SideNote: Der betreffende Host-Computer hat Ethernet standardmäßig nicht unterstützt. Die Karte, mit der das Laufwerk betrieben werden soll, wurde speziell angefertigt und nachgerüstet, und der Protokollstapel war praktisch nicht vorhanden. Der Entwickler, mit dem ich zusammengearbeitet habe, war ein verdammt guter Programmierer. Er hat nicht nur eine abgespeckte Version von UDP und einen minimalen falschen Ethernet-Stack (der Prozessor war nicht leistungsfähig genug, um einen vollständigen Ethernet-Stack zu handhaben) auf dem System für dieses Projekt implementiert aber er tat es in weniger als einer Woche. Er war auch einer der ursprünglichen Projektteamleiter gewesen, der das Betriebssystem überhaupt entworfen und programmiert hatte. Sagen wir einfach, alles, was er jemals über Computer / Programmierung / Architektur zu erzählen hatte, egal wie lange es dauert oder wie viel ich bereits neu bin, würde ich jedem Wort zuhören.
quelle
Der Hintergrund
Der Käfer
Wie ich es gefunden habe
Anfangs war ich mir sicher, dass dies ein normales Leistungsproblem ist, also erstelle ich eine aufwändige Protokollierung. Die überprüfte Leistung bei jedem Anruf, der mit den Datenbankmitarbeitern über die Auslastung gesprochen wurde, überprüfte die Server auf Probleme. 1 Woche
Dann war ich mir sicher, dass ich ein Problem mit Thread-Konflikten hatte. Ich habe meine Deadlocks überprüft und versucht, die Situation zu erstellen. Erstellen Sie Tools, um zu versuchen, die Situation beim Debug zu erstellen. Mit wachsender Management-Frustration wandte ich mich an Gleichgesinnte, die vorschlugen, das Projekt von Grund auf neu zu starten und den Server auf einen Thread zu beschränken. 1,5 Wochen
Dann schaute ich mir Tess Ferrandez Blog an, erstellte eine Benutzer-Dump-Datei und annalisierte sie mit Windebug, wenn der Server das nächste Mal einen Dump machte. Es wurde festgestellt, dass alle meine Threads in der dictionary.add-Funktion stecken geblieben sind.
Das kurze und lange kleine Wörterbuch, das gerade feststellte, in welches Protokoll x Threads geschrieben werden sollen, wurde nicht synchronisiert.
quelle
Wir hatten eine Anwendung, die mit einem Hardwaregerät sprach, das in einigen Fällen nicht richtig funktionierte, wenn das Gerät physisch vom Stromnetz getrennt wurde, bis es zweimal wieder eingesteckt und zurückgesetzt wurde.
Das Problem stellte sich heraus, dass eine Anwendung, die beim Start ausgeführt wurde, gelegentlich einen Segfehler aufwies, als sie versuchte, aus einem Dateisystem zu lesen, das noch nicht bereitgestellt war (zum Beispiel, wenn ein Benutzer das Lesen von einem NFS-Volume konfiguriert hatte). Beim Start sendet die Anwendung einige Ioctls an den Treiber, um das Gerät zu initialisieren. Anschließend werden die Konfigurationseinstellungen gelesen und weitere Ioctls gesendet, um das Gerät in den richtigen Zustand zu versetzen.
Ein Fehler im Treiber verursachte, dass ein ungültiger Wert zum Zeitpunkt des Initialisierungsaufrufs auf das Gerät geschrieben wurde. Der Wert wurde jedoch mit gültigen Daten überschrieben, als die Aufrufe erfolgten, um das Gerät in einen bestimmten Status zu versetzen.
Das Gerät selbst hatte eine Batterie und erkannte, ob es vom Motherboard Strom verloren hatte, und schrieb ein Flag in den flüchtigen Speicher, das angab, dass es Strom verloren hatte. Beim nächsten Einschalten ging es dann in einen bestimmten Zustand über Anweisung musste gesendet werden, um die Flagge zu löschen.
Das Problem bestand darin, dass nach dem Abschalten der Stromversorgung die ioctls gesendet wurden, um das Gerät zu initialisieren (und den ungültigen Wert auf das Gerät zu schreiben), aber bevor gültige Daten gesendet werden konnten. Beim erneuten Einschalten des Geräts wurde das gesetzte Flag angezeigt, und es wurde versucht, die ungültigen Daten zu lesen, die aufgrund der unvollständigen Initalisierung vom Treiber gesendet wurden. Dies würde das Gerät in einen ungültigen Zustand versetzen, in dem das Ausschaltflag gelöscht worden war, das Gerät jedoch keine weiteren Anweisungen erhielt, bis es vom Treiber erneut initialisiert worden war. Das zweite Zurücksetzen würde bedeuten, dass das Gerät nicht versucht hat, die ungültigen Daten zu lesen, die darauf gespeichert waren, und korrekte Konfigurationsanweisungen erhalten würde, sodass es in den richtigen Zustand versetzt werden kann (vorausgesetzt, die Anwendung, die die Ioctls sendet, hat keinen Segfault ausgeführt ).
Am Ende dauerte es ungefähr zwei Wochen, um die genauen Umstände herauszufinden, die das Problem verursachten.
quelle
Für ein Universitätsprojekt haben wir ein verteiltes P2P-Knotensystem geschrieben, das Dateien gemeinsam nutzt. Dieses unterstützte Multicasting, um sich gegenseitig zu erkennen, mehrere Knotenringe und einen Nameserver, sodass einem Client ein Knoten zugewiesen wird.
Geschrieben in C ++ haben wir dafür POCO verwendet, da es eine nette IO-, Socket- und Thread-Programmierung ermöglicht.
Es sind zwei Bugs aufgetaucht, die uns geärgert und viel Zeit verloren haben, eine sehr logische:
Zufällig teilte ein Computer seine Localhost-IP-Adresse anstelle der Remote-IP-Adresse.
Dies führte dazu, dass Clients eine Verbindung mit dem Knoten auf demselben PC herstellten oder dass Knoten eine Verbindung mit sich selbst herstellten.
Wie haben wir das identifiziert? Als wir die Ausgabe auf dem Nameserver verbesserten, stellten wir zu einem späteren Zeitpunkt beim Neustart der Computer fest, dass unser Skript zur Ermittlung der IP-Adresse falsch war. Zufällig wurde das lo-Gerät zuerst anstelle des eth0-Geräts aufgelistet ... Wirklich dumm. Also haben wir es jetzt hart codiert, um es von eth0 anzufordern, da es von allen Universitätscomputern gemeinsam genutzt wird ...
Und jetzt eine nervigere:
Zufällig würde der Paketfluss zufällig pausieren.
Wenn der nächste Client eine Verbindung herstellt, wird der Vorgang fortgesetzt.
Dies geschah wirklich zufällig und da mehr als ein Computer beteiligt ist, wurde es ärgerlicher, dieses Problem zu debuggen. Die Universitätscomputer erlauben es uns nicht, Wireshark auf diesen Computern auszuführen, so dass wir raten müssen, ob das Problem auf der sendenden Seite oder auf der empfangenden Seite lag Seite.
Angesichts der vielen Ausgaben im Code gingen wir einfach davon aus, dass das Senden der Befehle in Ordnung ist.
Daher fragten wir uns, wo das eigentliche Problem liegt. Anscheinend ist die Art und Weise, wie POCO-Abfragen durchgeführt werden, falsch und wir sollten stattdessen nach verfügbaren Zeichen suchen an der eingehenden steckdose.
Wir gingen davon aus, dass dies bei einfacheren Tests in einem Prototyp mit weniger Paketen nicht zu diesem Problem führte. Daher gingen wir einfach davon aus, dass die Umfrageanweisung funktioniert hat, aber ... war es nicht. :-(
Gewonnene Erkenntnisse:
Machen Sie keine dummen Annahmen wie die Reihenfolge der Netzwerkgeräte.
Frameworks machen ihre Arbeit (entweder Implementierung oder Dokumentation) nicht immer richtig.
Geben Sie im Code genügend Daten aus. Wenn dies nicht zulässig ist, müssen Sie erweiterte Details in einer Datei protokollieren.
Wenn Code nicht Unit-getestet wurde (weil es zu schwierig ist), gehen Sie nicht davon aus, dass etwas funktioniert.
quelle
Ich bin immer noch auf meiner schwierigsten Jagd nach Insekten. Es ist eines davon, manchmal ist es da und manchmal sind es keine Bugs. Deshalb bin ich am nächsten Tag um 6:10 Uhr hier.
Hintergrund:
Die Jagd.
Das Töten.
Postmortem.
quelle
Ich musste beim letzten Semseter einige verwirrende Nebenläufigkeiten beheben, aber der Fehler, der mir immer noch am meisten auffiel, war in einem textbasierten Spiel, das ich in der PDP-11-Baugruppe für eine Hausaufgabe schrieb. Es basierte auf Conways Spiel des Lebens und aus irgendeinem seltsamen Grund wurde ein großer Teil der Informationen neben dem Raster ständig mit Informationen überschrieben, die nicht dort hätten sein dürfen. Die Logik war auch ziemlich einfach, also sehr verwirrend. Nachdem ich einige Male darüber nachgedacht hatte, um herauszufinden, dass alle Logik korrekt ist, bemerkte ich plötzlich, was das Problem war. Dieses Ding:
.
In PDP-11 bildet dieser kleine Punkt neben einer Zahl die Basis 10 anstelle von 8. Er befand sich neben einer Zahl, die eine Schleife begrenzte, die auf das Gitter beschränkt sein sollte, dessen Größe mit den gleichen Zahlen, jedoch in der Basis definiert wurde 8.
Es fällt mir immer noch auf, wie groß der Schaden ist, den solch ein winziger 4-Pixel-Aufschlag angerichtet hat. Was ist die Schlussfolgerung? Codieren Sie nicht in der PDP-11-Assembly.
quelle
Main-Frame-Programm funktioniert nicht mehr, ohne Grund
Ich habe dies gerade auf eine andere Frage gestellt. Siehe Beitrag hier
Es ist passiert, weil sie eine neuere Version des Compilers auf dem Main-Frame installiert haben.
Update 11.06.13: (Die ursprüngliche Antwort wurde von OP gelöscht.)
Ich habe diese Mainframe-Anwendung übernommen. Eines Tages hörte es aus heiterem Himmel auf zu funktionieren. Das war's ... puh, es hat einfach aufgehört.
Meine Aufgabe war es, es so schnell wie möglich zum Laufen zu bringen. Der Quellcode war seit zwei Jahren nicht mehr verändert worden, aber plötzlich hörte er einfach auf. Ich habe versucht, den Code zu kompilieren, und er ist in Zeile XX kaputt gegangen. Ich habe mir Zeile XX angesehen und konnte nicht sagen, was dazu führen würde, dass Zeile XX abbricht. Ich fragte nach den detaillierten Spezifikationen für diese Anwendung und es gab keine. Zeile XX war nicht der Täter.
Ich druckte den Code aus und begann ihn von oben nach unten zu überprüfen. Ich fing an, ein Flussdiagramm zu erstellen, was vor sich ging. Der Code war so verworren, dass ich kaum einen Sinn daraus ziehen konnte. Ich habe es aufgegeben, ein Flussdiagramm zu erstellen. Ich hatte Angst, Änderungen vorzunehmen, ohne zu wissen, wie sich diese Änderungen auf den Rest des Prozesses auswirken würden, zumal ich keine Einzelheiten über die Funktionsweise der Anwendung hatte.
Also habe ich beschlossen, am Anfang des Quellcodes zu beginnen und Leerzeichen und Zeilenbremsen hinzuzufügen, um den Code besser lesbar zu machen. In einigen Fällen stellte ich fest, dass es Bedingungen gab, bei denen ANDs und ORs kombiniert wurden und nicht klar unterschieden werden konnte, welche Daten AND- und welche Daten OR-verknüpft wurden. Also habe ich begonnen, die UND- und ODER-Bedingungen in Klammern zu setzen, um sie besser lesbar zu machen.
Da ich langsam nach unten ging und es aufräumte, speicherte ich meine Arbeit regelmäßig. Irgendwann habe ich versucht, den Code zu kompilieren und es passierte etwas Seltsames. Der Fehler hatte die ursprüngliche Codezeile übersprungen und war nun weiter unten. Also fuhr ich fort und trennte die AND- und OR-Bedingungen mit Parens. Als ich fertig war, klappte es. Stelle dir das vor.
Ich beschloss dann, den Operations Shop zu besuchen und sie zu fragen, ob sie kürzlich neue Komponenten auf dem Hauptrahmen installiert hatten. Sie sagten ja, wir haben kürzlich den Compiler aktualisiert. Hmmmm.
Es stellt sich heraus, dass der alte Compiler den Ausdruck unabhängig davon von links nach rechts ausgewertet hat. Die neue Version des Compilers wertete auch Ausdrücke von links nach rechts aus, aber mehrdeutiger Code, was bedeutet, dass eine unklare Kombination von ANDs und ORs nicht gelöst werden konnte.
Daraus habe ich gelernt ... IMMER, IMMER, IMMER benutze ich Parens, um AND-Bedingungen und OR-Bedingungen zu trennen, wenn sie in Verbindung miteinander verwendet werden.
quelle
Hintergrund:
Die Jagd.
Das Töten.
Postmortem.
gdb
+ Überwachung! Wir haben uns nur die Zeit genommen, die Festplatte zu verdächtigen und dann die Ursache für die Aktivitätsspitzen im Überwachungsdiagramm zu identifizieren ...quelle
Der schwerste wurde nie getötet, weil er nie anders reproduziert werden konnte als in der vollen Produktionsumgebung, wenn die Fabrik in Betrieb war.
Der verrückteste, den ich getötet habe:
Die Zeichnungen drucken Kauderwelsch!
Ich schaue auf den Code und kann nichts sehen. Ich ziehe einen Auftrag aus der Druckerwarteschlange und überprüfe ihn. Er sieht gut aus. (Dies war in der damaligen Zeit PCL5 mit eingebettetem HPGl / 2 - eigentlich sehr gut zum Plotten von Zeichnungen und ohne Probleme beim Erstellen eines Rasterbilds in begrenztem Speicher.) Ich leite es an einen anderen Drucker, der es verstehen sollte, es druckt einwandfrei .
Roll den Code zurück, das Problem ist immer noch da.
Schließlich erstelle ich manuell eine einfache Datei und sende sie an den Drucker - Kauderwelsch. Es stellte sich heraus, dass es überhaupt nicht mein Fehler war, sondern der Drucker selbst. Die Wartungsfirma hatte es auf die neueste Version geflasht, als sie etwas anderes reparierten und diese neueste Version hatte einen Fehler. Es war schwieriger, sie dazu zu bringen, zu verstehen, dass sie wichtige Funktionen entfernt und auf eine frühere Version zurückgesetzt hatten, als den Fehler selbst zu finden.
Eine, die noch ärgerlicher war, aber da sie nur auf meiner Box war, würde ich nicht an erster Stelle stehen:
Borland Pascal, DPMI-Code für einige nicht unterstützte APIs. Führen Sie es aus, gelegentlich funktionierte es, in der Regel boomte es und versuchte, mit einem ungültigen Zeiger umzugehen. Es hat jedoch nie zu einem falschen Ergebnis geführt, wie Sie es von einem Zeigertritt erwarten würden.
Debuggen - wenn ich den Code in einem Schritt durchlaufe, funktioniert er immer richtig, ansonsten ist er genauso instabil wie zuvor. Die Inspektion ergab immer die richtigen Werte.
Der Täter: Es waren zwei.
1) Borlands Bibliothekscode hatte einen großen Fehler: Zeiger im Real-Modus wurden im geschützten Modus in Zeigervariablen gespeichert. Das Problem ist, dass die meisten Zeiger im Real-Modus im geschützten Modus ungültige Segmentadressen haben. Wenn Sie versuchen, den Zeiger zu kopieren, wird er in ein Registerpaar geladen und dann gespeichert.
2) Der Debugger würde niemals etwas über solch eine ungültige Last im Einzelschrittmodus sagen. Ich weiß nicht, was es intern tat, aber was dem Benutzer präsentiert wurde, sah völlig korrekt aus. Ich vermute, dass die Anweisung nicht tatsächlich ausgeführt wurde, sondern stattdessen simuliert wurde.
quelle
Dies ist nur ein sehr einfacher Fehler, der mich irgendwie in einen Albtraum verwandelt hat.
Hintergrund: Ich habe an meinem eigenen Betriebssystem gearbeitet. Das Debuggen ist sehr schwierig (Trace-Anweisungen sind alles, was Sie haben können, und manchmal auch nicht)
Bug: Anstatt zwei Threadwechsel im Usermode durchzuführen, würde es stattdessen eine allgemeine Schutzverletzung geben.
Die Fehlersuche: Ich habe wahrscheinlich ein oder zwei Wochen damit verbracht, dieses Problem zu beheben. Überall Trace-Anweisungen einfügen. Überprüfung des generierten Assembler-Codes (von GCC). Ich drucke jeden Wert aus, den ich konnte.
Das Problem: Irgendwann zu Beginn der Fehlersuche hatte ich eine
hlt
Anweisung in die CRT0 gestellt. Das crt0 ist im Grunde das, was ein Benutzerprogramm für die Verwendung in einem Betriebssystem bootstrappt. Diesehlt
Anweisung verursacht eine GPF, wenn sie im Benutzermodus ausgeführt wird. Ich habe es dort abgelegt und es im Grunde vergessen. (ursprünglich war das Problem ein Pufferüberlauf oder ein Speicherzuordnungsfehler)Das Update: Entfernen Sie die
hlt
Anweisung :) Nach dem Entfernen hat alles reibungslos funktioniert.Was ich gelernt habe: Wenn Sie versuchen, ein Problem zu beheben, verlieren Sie nicht den Überblick über die Korrekturen, die Sie versuchen. Vergleichen Sie regelmäßig die neueste stabile Version der Quellcodeverwaltung und sehen Sie, was Sie in letzter Zeit geändert haben, wenn nichts anderes mehr funktioniert
quelle