Was war Ihre schwierigste Fehlersuche und wie haben Sie sie gefunden und getötet?


31

Dies ist eine Frage zum Teilen des Wissens. Ich bin daran interessiert, aus Ihren Erfolgen und / oder Misserfolgen zu lernen.

Informationen, die hilfreich sein könnten ...

Hintergrund:

  • Kontext: Sprache, Anwendung, Umgebung usw.
  • Wie wurde der Fehler identifiziert?
  • Wer oder was hat den Fehler identifiziert?
  • Wie komplex war die Wiedergabe des Fehlers?

Die Jagd.

  • Was war dein Plan?
  • Auf welche Schwierigkeiten sind Sie gestoßen?
  • Wie wurde der fehlerhafte Code gefunden?

Das Töten.

  • Wie komplex war das Update?
  • Wie haben Sie den Umfang des Fixes ermittelt?
  • Wie viel Code war an der Korrektur beteiligt?

Postmortem.

  • Was war die Grundursache technisch? Pufferüberlauf usw.
  • Was war die Grundursache von 30.000 Fuß?
  • Wie lange hat der Prozess letztendlich gedauert?
  • Gab es Funktionen, die durch die Korrektur beeinträchtigt wurden?
  • Welche Methoden, Werkzeuge, Motivationen fanden Sie besonders hilfreich? ... fürchterlich nutzlos?
  • Wenn Sie alles noch einmal machen könnten? ............

Diese Beispiele sind allgemein, nicht in jeder Situation anwendbar und möglicherweise unbrauchbar. Bitte nach Bedarf würzen.

Antworten:


71

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.


14
Das ist das Böse, von dem ich je gehört habe.
Nathan Taylor

9
Es hat jedoch einen Helden aus dem Typ gemacht, der es gelöst hat.
JohnFx

2
Wow, das ist ein verdammt großer Fehler!
Mitchel Sellers

3
Toller Fund! Gute Geschichte.
Toon Krijthe

11
Als ob wir Linken nicht schon genug wie Bürger zweiter Klasse behandelt würden. Jetzt müssen wir uns auch mit mehr als unserer ganzen Menge an Softwarefehlern auseinandersetzen ... danke! : p
Dan Moulding

11

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:

type = record[1] MOD 4096

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 4096Ergebnis 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 MODdas Ergebnis durchführte und dann mit dem Vorzeichen multiplizierte.

Dies dauerte mehrere Tage.


3
Es hat im Laufe der Jahre wahrscheinlich schwierigere Bugjagden gegeben, aber diese sind mir seit mehr als 20 Jahren in Erinnerung geblieben!
ChrisF

7

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.


Bitte posten Sie den Elektronikfehler an einer anderen Stelle und einen Link hier!
tgkprog

6

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:

  • Ich habe die Touchscreen-App überprüft, sie oszillierte
  • Ich überprüfte die Datenbank und oszillierte
  • Ich habe die Kommunikations-App überprüft und oszilliert

Dann brach ich Drahthai aus und fing an, die erfassten Pakete manuell zu dekodieren. Ergebnis:

  • Nicht oszillierend, aber die Pakete sahen nicht richtig aus, es gab zu viele Daten.

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.


5

Der Hintergrund

  • In einer unternehmenskritischen WCF-Anwendung, die eine Website steuert und eine transaktionale Back-End-Verarbeitung bereitstellt.
  • Large Volume-Anwendung (Hunderte von Anrufen pro Sekunde)
  • Mehrere Server, mehrere Instanzen
  • Hunderte von bestandenen Unit-Tests und unzähligen QA-Attacken

Der Käfer

  • Wenn der Server auf die Produktion umgestellt wurde, lief er für eine zufällige Zeit gut, begann sich dann schnell zu verschlechtern und die CPU der Box auf 100% zu bringen.

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.


3

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.


2

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.


1
Die Lösung von Netzwerkproblemen ohne Wireshark (oder ein ähnliches Tool) ist in / von iteslf heldenhaft.
Evan Plaice

2

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:

  • Kontext: Sprache, Anwendung, Umgebung usw.
    • PHP OS Commerce
  • Wie wurde der Fehler identifiziert?
    • Zufällige Reihenfolge ist Teil der zufälligen Fehler- und Weiterleitungsprobleme
  • Wer oder was hat den Fehler identifiziert?
    • Client, und das Redirect-Problem war offensichtlich
  • Wie komplex war die Wiedergabe des Fehlers?
    • Ich habe nicht reproduzieren können, aber der Kunde hat es geschafft.

Die Jagd.

  • Was war dein Plan?
    • Debug-Code hinzufügen, Auftrag ausfüllen, Daten analysieren, wiederholen
  • Auf welche Schwierigkeiten sind Sie gestoßen?
    • Mangel an wiederholbaren Problemen und schrecklichem Code
  • Wie wurde der fehlerhafte Code gefunden?
    • Viele anstößige Code wurde gefunden .. nur nicht genau das, was ich brauchte.

Das Töten.

  • Wie komplex war das Update?
    • sehr
  • Wie haben Sie den Umfang des Fixes ermittelt?
    • Es gab keinen Spielraum ... es war überall
  • Wie viel Code war an der Korrektur beteiligt?
    • Alles davon? Ich glaube nicht, dass eine Datei unberührt war

Postmortem.

  • Was war die Grundursache technisch? Pufferüberlauf usw.
    • schlechte Codierungspraxis
  • Was war die Grundursache von 30.000 Fuß?
    • Ich wuerde eher nicht behaupten, dass...
  • Wie lange hat der Prozess letztendlich gedauert?
    • für immer und einen Tag
  • Gab es Funktionen, die durch die Korrektur beeinträchtigt wurden?
    • Feature? oder ist es ein Fehler?
  • Welche Methoden, Werkzeuge, Motivationen fanden Sie besonders hilfreich? ... fürchterlich nutzlos?
  • Wenn Sie alles noch einmal machen könnten? ............
    • Strg + a Entf

Wenn der Grund "schlechte Codierungspraxis" war, sollten Sie mit Ihrem Chef besprechen, ob dies ein guter Zeitpunkt ist, um die Codierungspraktiken Ihres Teams zu überarbeiten und möglicherweise Peer Review einzuführen.

2

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.


2

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.


Der Beitrag, auf den Ihr Link verweist, wurde entfernt. Möchten Sie die Antwort aktualisieren?
gnat

1
@gnat - Gefunden auf archive.org :)
Michael Riley - AKA Gunny

1

Hintergrund:

  • Kontext: Webserver (C ++), mit dem Kunden sich selbst einchecken können
  • Fehler: Wenn die Seite angefordert wird, reagiert sie einfach nicht, die gesamte Farm und die Prozesse werden abgebrochen (und neu gestartet), da sie zu lange gedauert haben (nur einige Sekunden sind zulässig), um die Seite zu bedienen
  • Einige Benutzer haben sich beschwert, aber es war extrem sporadisch, so dass es größtenteils unbemerkt blieb (die Leute klicken einfach auf "Aktualisieren", wenn eine Seite nicht geliefert wird). Die Core Dumps sind uns allerdings aufgefallen;)
  • Wir haben es tatsächlich nie geschafft, uns in unseren lokalen Umgebungen zu reproduzieren. Der Fehler trat einige Male in Testsystemen auf, wurde aber bei Leistungstests nie gefunden.

Die Jagd.

  • Plan: Nun, da wir Speicherabbilder und Protokolle hatten, wollten wir sie analysieren. Da dies die gesamte Farm betraf und wir in der Vergangenheit einige Datenbankprobleme hatten, haben wir vermutet, dass es sich um eine einzelne Datenbank für mehrere Server handelt.
  • Schwierigkeiten: Ein vollständiger Server-Dump ist riesig, und daher werden sie häufig gelöscht (um nicht zu wenig Speicherplatz zu haben). Wir mussten also schnell auf einen zugreifen, als er auftrat ... Wir haben darauf bestanden. Die Dumps zeigten verschiedene Stapel (niemals irgendwelche DB-Inhalte, so viel dafür), es schlug fehl, während die Seite selbst vorbereitet wurde (nicht in den vorherigen Berechnungen), und es wurde bestätigt, was die Protokolle zeigten, das Vorbereiten der Seite dauerte manchmal sogar lange obwohl es nur eine grundlegende Template-Engine mit vorberechneten Daten ist (traditionelles MVC)
  • Erste Schritte: Nach einigen weiteren Beispielen und Überlegungen stellten wir fest, dass das Lesen von Daten von der Festplatte (der Seitenvorlage) viel Zeit in Anspruch nahm. Da es sich um die gesamte Farm handelte, suchten wir zuerst nach geplanten Jobs (Crontab, Batches), aber die Zeitpunkte stimmten nie überein. Schließlich fiel mir ein, dass dies immer einige Tage vor der Aktivierung einer neuen Version geschah der Software und ich hatte ein AhAh! Moment ... es wurde durch die Verteilung der Software verursacht! Das Bereitstellen von mehreren Hundert Megabyte (komprimiert) kann die Festplattenleistung ein wenig beeinträchtigen: / Natürlich wird die Verteilung automatisiert und das Archiv auf alle Server gleichzeitig übertragen (Multicast).

Das Töten.

  • Behobene Komplexität: Wechsel zu kompilierten Vorlagen
  • Code betroffen: keine, eine einfache Änderung im Erstellungsprozess

Postmortem.

  • Grundursache: Betriebsstörung oder fehlende Vorausplanung :)
  • Zeitrahmen: Es dauerte Monate, bis das Problem erkannt wurde, ein paar Tage, bis es behoben und getestet wurde, ein paar Wochen, bis die Qualitätssicherung und die Leistung getestet und bereitgestellt wurden sonst ... irgendwie echt pervers!
  • Unerwünschte Nebeneffekte: Es ist nicht möglich, Vorlagen zur Laufzeit zu wechseln, da sie im mitgelieferten Code gebacken sind. Wir haben diese Funktion jedoch nicht viel genutzt, da das Wechseln von Vorlagen im Allgemeinen bedeutet, dass Sie mehr Daten zum Eingeben haben meist ausreichend für "kleine" layoutänderungen.
  • Methoden, Werkzeuge: 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 ...
  • Nächstes Mal: ​​Behandle alle E / A als nachteilig!

1

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.


1

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 hltAnweisung in die CRT0 gestellt. Das crt0 ist im Grunde das, was ein Benutzerprogramm für die Verwendung in einem Betriebssystem bootstrappt. Diese hltAnweisung 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 hltAnweisung :) 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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.