Jakie było twoje najtrudniejsze polowanie na robaki i jak je znalazłeś i zabiłeś?

31

To jest pytanie „Podziel się wiedzą”. Jestem zainteresowany uczeniem się na podstawie twoich sukcesów i / lub porażek.

Informacje, które mogą być pomocne ...

Tło:

  • Kontekst: język, aplikacja, środowisko itp.
  • Jak zidentyfikowano błąd?
  • Kto lub co zidentyfikowało błąd?
  • Jak skomplikowane było odtwarzanie błędu?

Polowanie

  • Jaki był twój plan
  • Jakie napotkałeś trudności?
  • Jak w końcu znaleziono obrażający kod?

The Killing.

  • Jak skomplikowana była poprawka?
  • Jak określiłeś zakres poprawki?
  • Ile kodu wymagało poprawki?

Sekcja zwłok.

  • Jaka była podstawowa przyczyna technicznie? przepełnienie bufora itp.
  • Jaka była pierwotna przyczyna od 30 000 stóp?
  • Jak długo trwał ten proces?
  • Czy poprawka wpłynęła negatywnie na jakieś funkcje?
  • Jakie metody, narzędzia i motywacje uważasz za szczególnie pomocne? ... okropnie bezużyteczne?
  • Gdybyś mógł zrobić to wszystko jeszcze raz? ............

Te przykłady są ogólne, nie mają zastosowania w każdej sytuacji i być może są bezużyteczne. Przypraw w razie potrzeby.

Zardzewiały
źródło

Odpowiedzi:

71

Tak naprawdę było to w podskładniku przeglądarki aplikacji innej firmy.

Okazało się, że było 2-3 użytkowników naszej aplikacji, którzy często mieliby wyjątek i okropnie umierali komponent przeglądarki zdjęć. Mieliśmy jednak dziesiątki innych użytkowników, którzy nigdy nie widzieli problemu, pomimo używania aplikacji do tego samego zadania przez większą część dnia pracy. W szczególności był jeden użytkownik, który uzyskiwał go znacznie częściej niż reszta.

Próbowaliśmy zwykłych kroków:

(1) Gdyby zamieniali komputery z innym użytkownikiem, który nigdy nie miał problemu z wykluczeniem komputera / konfiguracji. - Problem nastąpił po nich.

(2) Gdyby zalogowali się do aplikacji i pracowali jako użytkownik, który nigdy nie widział problemu. - Problem STILL za nimi podążał.

(3) Gdyby użytkownik poinformował, który obraz ogląda, i skonfigurował uprząż testową, aby powtórzyć oglądanie tego obrazu tysiące razy w krótkich odstępach czasu. Problem nie pojawił się w uprzęży.

(4) Poprosił programistę, aby siedział z użytkownikami i obserwował ich przez cały dzień. Widzieli błędy, ale nie zauważyli, że robią coś niezwykłego, aby je spowodować.

Zmagaliśmy się z tym od tygodni, próbując dowiedzieć się, co wspólnego mają „użytkownicy błędów”, a inni nie. Nie mam pojęcia, jak to zrobić, ale deweloper w kroku (4) miał moment eureki na drodze do pracy pewnego dnia, wartej Encyklopedii Brown.

Uświadomił sobie, że wszyscy „użytkownicy błędów” pozostali w rękach, i potwierdził ten fakt. Tylko leworęczni użytkownicy otrzymywali błędy, nigdy prawicy. Ale w jaki sposób leworęczność może spowodować błąd?

Kazaliśmy mu usiąść i znów oglądać leworęcznych, zwracając szczególną uwagę na wszystko, co mogą robić inaczej, i tak to znaleźliśmy.

Okazało się, że błąd wystąpił tylko wtedy, gdy przesunąłeś mysz do skrajnej prawej kolumny pikseli w przeglądarce zdjęć podczas ładowania nowego obrazu (błąd przepełnienia, ponieważ sprzedawca miał jednorazowe obliczenie zdarzenia myszy).

Najwyraźniej podczas oczekiwania na załadowanie następnego obrazu wszyscy użytkownicy naturalnie przesunęli rękę (a tym samym mysz) w kierunku klawiatury.

Jedyną osobą, która najczęściej otrzymywała błąd, był jeden z tych typów ADD, które kompulsywnie poruszały myszą niecierpliwie podczas oczekiwania na załadowanie następnej strony, dlatego znacznie szybciej przesuwała mysz w prawo i naciskała czas w sam raz, więc zrobiła to, gdy zdarzenie ładowania się wydarzyło. Dopóki nie otrzymaliśmy poprawki od dostawcy, powiedzieliśmy jej, aby po kliknięciu (następny dokument) puściła mysz i nie dotykała go, dopóki się nie załaduje.

Odtąd był znany w zespole deweloperów jako „The Left Handed Bug”

JohnFx
źródło
14
To najgorsza rzecz, o jakiej kiedykolwiek słyszałem.
Nathan Taylor,
9
Jednak z faceta, który go rozwiązał, stał się bohaterem.
JohnFx
2
Wow, teraz to cholerny błąd!
Mitchel Sellers,
3
Świetne znalezisko! Niezła historia.
Toon Krijthe,
11
Jakby my, lewici, nie byliśmy już wystarczająco traktowani jak obywatele drugiej kategorii. Teraz musimy się też obarczać więcej niż naszym uczciwym udziałem błędów w oprogramowaniu ... rany, dzięki! : p
Dan Molding
11

Jest to dawno temu (koniec lat 80.).

Firma, dla której pracowałem, napisała pakiet CAD (w FORTRAN), który działał na różnych stacjach roboczych Unix (HP, Sun, Silcon Graphics itp.). Użyliśmy własnego formatu pliku do przechowywania danych, a kiedy pakiet został uruchomiony, miejsca na dysku było mało, więc do przechowywania wielu flag w nagłówkach encji było dużo przesunięć.

Typ bytu (linia, łuk, tekst itp.) Został pomnożony przez 4096 (chyba) podczas przechowywania. Ponadto wartość ta została zanegowana, aby wskazać usunięty element. Aby uzyskać typ, który mieliśmy kod:

type = record[1] MOD 4096

Na każdej maszynie, z wyjątkiem jednej, dawało to ± 1 (dla linii), ± 2 (dla łuku) itp. I wtedy mogliśmy sprawdzić znak, aby zobaczyć, czy został usunięty.

Na jednej maszynie (myślę, HP) mieliśmy dziwny problem, w którym obsługa usuniętych elementów była zepsuta.

Było to w czasach poprzedzających IDE i wizualne debuggery, więc musiałem wstawić instrukcje śledzenia i logowanie, aby spróbować wyśledzić problem.

I w końcu odkrył, że to dlatego, że podczas gdy każdy inny producent realizowane MODtak, że -4096 MOD 4096spowodowało -1HP wprowadziły go poprawnie matematycznie tak, że -4096 MOD 4096spowodowało -4097.

Skończyło się na tym, że musiałem przejść przez całą bazę kodu, zapisując znak wartości i nadając mu wartość dodatnią przed wykonaniem, MODa następnie pomnożeniem wyniku przez wartość znaku.

Zajęło to kilka dni.

ChrisF
źródło
3
Prawdopodobnie na przestrzeni lat były trudniejsze polowania na owady, ale ta zapadła mi w pamięć od ponad 20 lat!
ChrisF
7

Wow, dobra lektura tutaj!

Moje najtrudniejsze były lata temu, kiedy Turbo Pascal było duże, choć mogło to być jedno z wczesnych IDE C ++ w tamtych czasach. Jako jedyny programista (i trzeci w tym startupie) napisałem coś w rodzaju uproszczonego programu CAD przyjaznego dla sprzedawców. W tamtym czasie było świetnie, ale rozwinęła się paskudna, losowa awaria. Reprodukcja była niemożliwa, ale zdarzało się tak często, że wyruszałem na polowanie na błędy.

Moją najlepszą strategią było jednoetapowe uruchomienie debuggera. Błąd występował tylko wtedy, gdy użytkownik wprowadził wystarczającą ilość rysunku i być może musiał być w określonym trybie lub stanie powiększenia, więc było wiele żmudnych ustawień i usuwania punktów przerwania, normalnie działało przez minutę, aby wprowadzić rysunek, a następnie przejdź przez dużą część kodu. Szczególnie pomocne były punkty przerwania, które pomijałyby pewną regulowaną liczbę razy, a następnie przerywały. Całe ćwiczenie trzeba było powtórzyć kilka razy.

W końcu zawęziłem go do miejsca, w którym wywoływano podprogram, otrzymując 2, ale z jego wnętrza zobaczyłem jakiś bełkot. Mogłem to złapać wcześniej, ale nie wkroczyłem w ten podprogram, zakładając, że dostał to, co mu dano. Oślepiony, zakładając, że najprostsze rzeczy były w porządku!

Okazało się, że umieszcza 16-bitową liczbę całkowitą na stosie, ale podprogram oczekuje 32-bitowej. Czy jakoś tak. Kompilator nie wypełnił automatycznie wszystkich wartości wartością 32-bitową lub nie wykonał wystarczającego sprawdzania typu. Naprawienie tego było trywialne, tylko część jednej linii, prawie żadna myśl nie była wymagana. Jednak dotarcie na miejsce zajęło trzy dni polowania i przesłuchanie tego, co oczywiste.

Mam więc osobiste doświadczenie z anegdotą na temat drogiego konsultanta, który po chwili robi jedno kliknięcie i pobiera 2000 USD. Kierownictwo żąda załamania, a to kosztuje 1 $ za kran, 1999 $ za wiedzę, gdzie kran. Z wyjątkiem mojego przypadku, to nie był czas, ale pieniądze.

Wyciągnięte wnioski: 1) korzystaj z najlepszych kompilatorów, w których „najlepszy” jest definiowany jako obejmujący sprawdzanie tylu problemów, ile informatycy potrafią sprawdzić, oraz 2) kwestionują proste oczywiste rzeczy lub przynajmniej weryfikują ich prawidłowe funkcjonowanie.

Od tego czasu wszystkie trudne błędy były naprawdę trudne, ponieważ wiem, jak sprawdzać proste rzeczy dokładniej, niż wydaje się to konieczne.

Lekcja 2 dotyczy również najtrudniejszego błędu elektroniki, jaki kiedykolwiek naprawiłem, również z trywialną poprawką, ale kilka inteligentnych EE było zatkanych od miesięcy. Ale to nie forum elektroniczne, więc nie powiem więcej.

DarenW
źródło
Proszę wysłać błąd elektroniki gdzie indziej i link tutaj!
tgkprog,
6

Warunki wyścigu danych sieciowych z piekła rodem

Pisałem klienta / serwer sieciowy (Windows XP / C #) do pracy z podobną aplikacją na naprawdę starej stacji roboczej (Encore 32/77) napisanej przez innego programistę.

Aplikacja zrobiła w zasadzie udostępnianie / manipulowanie niektórymi danymi na hoście, aby kontrolować proces hosta z systemem za pomocą naszego fantazyjnego, wielomonitorowego interfejsu użytkownika z ekranem dotykowym.

Zrobiło to z trójwarstwową strukturą. Proces komunikacji odczytał / zapisał dane do / z hosta, wykonał wszystkie niezbędne konwersje formatu (endianness, format zmiennoprzecinkowy itp.) I zapisał / odczytał wartości do / z bazy danych. Baza danych działała jako pośrednik danych między interfejsem komunikacyjnym a interfejsem dotykowym. Interfejsy ekranu dotykowego generowane przez aplikację interfejsu użytkownika na podstawie liczby monitorów podłączonych do komputera (to automatycznie wykryło).

W podanym przedziale czasowym pakiet wartości między hostem a naszym komputerem mógł przesyłać maksymalnie 128 wartości w poprzek drutu jednocześnie z maksymalnym opóźnieniem ~ 110 ms na podróż w obie strony (UDP zastosowano z bezpośrednim połączeniem ethernetowym x-over między komputery). Tak więc dozwolona liczba zmiennych oparta na zmiennej liczbie dołączonych ekranów dotykowych była pod ścisłą kontrolą. Ponadto host (choć posiadający dość złożoną architekturę wieloprocesorową z magistralą pamięci współużytkowanej wykorzystywaną do obliczeń w czasie rzeczywistym) miał około 1/100 mocy przetwarzania mojego telefonu komórkowego, więc miał za zadanie wykonać tak mało przetwarzania, jak to możliwe, a jego serwer / klient musiał zostać napisany w asemblerze, aby to zapewnić (host prowadził pełną symulację w czasie rzeczywistym, na którą nasz program nie miał wpływu).

Problem polegał na tym. Niektóre wartości po zmianie na ekranie dotykowym nie przyjmowałyby tylko nowo wprowadzonej wartości, ale losowo przełączały się między tą wartością a poprzednią. To i tylko na kilku konkretnych wartościach na kilku konkretnych stronach z pewną kombinacją stron kiedykolwiek wykazywało ten objaw. Prawie przegapiliśmy ten problem, dopóki nie zaczęliśmy go uruchamiać w procesie wstępnej akceptacji klienta


Aby określić problem, wybrałem jedną z oscylujących wartości:

  • Sprawdziłem aplikację Touchscreen, oscylowała
  • Sprawdziłem bazę danych, oscylując
  • Sprawdziłem aplikację komunikacyjną, oscylując

Następnie wybrałem Wireshark i zacząłem ręcznie dekodować przechwytywanie pakietów. Wynik:

  • Nie oscyluje, ale pakiety nie wyglądają dobrze, było za dużo danych.

Przeszedłem każdy szczegół kodu komunikacyjnego sto razy, nie znajdując żadnej wady / błędu.

W końcu zacząłem wysyłać wiadomości e-mail do drugiego dewelopera, pytając szczegółowo, jak działa jego koniec, aby sprawdzić, czy czegoś brakuje. Potem to znalazłem.

Najwyraźniej, kiedy wysyłał dane, nie opróżniał tablicy danych przed transmisją, więc w gruncie rzeczy po prostu nadpisał ostatni użyty bufor nowymi wartościami nadpisując stare, ale stare wartości nie nadpisane wciąż są przesyłane.

Tak więc, jeśli wartość znajdowałaby się w pozycji 80 tablicy danych, a lista żądanych wartości zmieniła się na mniej niż 80, ale ta sama wartość była zawarta na nowej liście, wówczas obie wartości istniałyby w buforze danych dla tego konkretnego bufora w dowolnym dany czas.

Wartość odczytywana z bazy danych zależała od przedziału czasu, w którym interfejs użytkownika żądał wartości.


Poprawka była boleśnie prosta. Wczytaj liczbę elementów przychodzących do bufora danych (faktycznie był zawarty jako część protokołu pakietu) i nie odczytuj bufora powyżej tej liczby.


Zdobyta wiedza:

  • Nie bierz nowoczesnej mocy obliczeniowej za pewnik. Był czas, kiedy komputery nie obsługiwały Ethernetu, a opróżnianie tablicy można było uznać za drogie. Jeśli naprawdę chcesz zobaczyć, jak daleko zaszliśmy, wyobraź sobie system, który praktycznie nie ma formy dynamicznej alokacji pamięci. IE, proces wykonawczy musiał wstępnie przydzielić całą pamięć dla wszystkich programów, aby żaden program nie mógł przekroczyć tej granicy. IE, przydzielenie większej ilości pamięci do programu bez ponownej kompilacji całego systemu może spowodować poważną awarię. Zastanawiam się, czy ludzie będą kiedyś opowiadać o dniach zbierania śmieci w tym samym świetle.

  • Podczas tworzenia sieci z niestandardowymi protokołami (lub ogólnie obsługi reprezentacji danych binarnych) upewnij się, że czytasz specyfikację, dopóki nie zrozumiesz każdej funkcji każdej wartości przesyłanej przez potok. Mam na myśli, czytaj to, dopóki nie bolą cię oczy. Ludzie przetwarzają dane, manipulując poszczególnymi bitami lub bajtami, w bardzo sprytny i wydajny sposób. Brak najmniejszego szczegółu może uszkodzić system.

Ogólny czas na naprawę wynosił 2-3 dni, a większość czasu spędziłem na pracy nad innymi rzeczami, kiedy byłem z tego sfrustrowany.

Uwaga: Komputer, o którym mowa, domyślnie nie obsługuje sieci Ethernet. Karta do napędu została wykonana na zamówienie i zmodernizowana, a stos protokołów praktycznie nie istniał. Deweloper, z którym pracowałem, był cholernie programistą, nie tylko zaimplementował uproszczoną wersję UDP i minimalny fałszywy stos ethernetowy (procesor nie był wystarczająco silny, aby obsłużyć pełny stos ethernetowy) w systemie dla tego projektu ale zrobił to w niecały tydzień. Był także jednym z liderów zespołu projektowego, który przede wszystkim zaprojektował i zaprogramował system operacyjny. Powiedzmy po prostu, że wszystko, co kiedykolwiek miał do powiedzenia na temat komputerów / programowania / architektury, bez względu na to, jak długo było to rozwinięte lub jak bardzo już byłem nowy, słuchałbym każdego słowa.

Evan Plaice
źródło
5

Tło

  • W kluczowej aplikacji WCF prowadzącej stronę internetową i zapewniającej przetwarzanie transakcyjne zaplecza ..
  • Aplikacja o dużej objętości (setki połączeń na sekundę)
  • Wiele serwerów, wiele wystąpień
  • setki zdanych testów jednostkowych i niezliczone ataki QA

Bug

  • Po przeniesieniu do produkcji serwer działałby dobrze przez losowy czas, a następnie zaczął gwałtownie ulegać degradacji i zwiększał wydajność procesora do 100%.

Jak to znalazłem

Na początku byłem pewien, że to normalny problem z wydajnością, dlatego tworzę skomplikowane rejestrowanie. Sprawdzona wydajność każdego połączenia rozmawiała z bazą danych, że ludzie o wykorzystaniu obserwowali serwery pod kątem problemów. 1 tydzień

Wtedy byłem pewien, że mam problem z niezgodnością wątków. Sprawdziłem, czy moje zakleszczenia próbowały stworzyć sytuację, stworzyć narzędzia do próby stworzenia sytuacji w debugowaniu. Z rosnącą frustracją w zarządzaniu zwróciłem się do moich rówieśników, jak zasugerowali rzeczy od ponownego uruchomienia projektu od zera do ograniczenia serwera do jednego wątku. 1,5 tygodnia

Potem spojrzałem na blog Tess Ferrandez, który utworzył plik zrzutu użytkownika i zindywidualizowałem go za pomocą windebug, kiedy następnym razem serwer zrobił zrzut. Odkryłem, że wszystkie moje wątki utknęły w funkcji dictionary.add.

Długi, krótki, mały słownik, który właśnie śledził, w którym dzienniku zapisywać błędy x wątków, nie był zsynchronizowany.

ponownie odtwarzać
źródło
3

Mieliśmy aplikację, która rozmawiała z urządzeniem sprzętowym, które w niektórych przypadkach nie działałoby poprawnie, gdyby urządzenie było fizycznie odłączone do momentu ponownego podłączenia i dwukrotnego resetu.

Problemem okazało się to, że aplikacja działająca przy starcie okazjonalnie powodowała awarię podczas próby odczytu z systemu plików, który nie został jeszcze zamontowany (na przykład, jeśli użytkownik skonfigurował go do odczytu z wolumenu NFS). Podczas uruchamiania aplikacja wysyła pewne ioctle do sterownika w celu zainicjowania urządzenia, a następnie odczytuje ustawienia konfiguracji i wysyła więcej ioctlów, aby ustawić urządzenie w prawidłowym stanie.

Błąd w sterowniku powodował, że podczas wykonywania połączenia inicjującego zapisywano na urządzeniu niepoprawną wartość, ale wartość ta została zastąpiona prawidłowymi danymi po wywołaniu urządzenia w określonym stanie.

Samo urządzenie miało baterię i wykrywało, czy straciło moc z płyty głównej, i zapisywało w nietrwałej pamięci flagę wskazującą, że straciło moc, a następnie przy następnym włączeniu przechodziło w określony stan i określone konieczne było wysłanie instrukcji, aby usunąć flagę.

Problem polegał na tym, że jeśli zasilanie zostało odłączone po wysłaniu ioctls w celu zainicjowania urządzenia (i zapisania nieprawidłowej wartości na urządzeniu), ale przed wysłaniem prawidłowych danych. Gdy urządzenie zostanie ponownie włączone, zobaczy, że flaga została ustawiona i spróbuje odczytać nieprawidłowe dane, które zostały wysłane ze sterownika z powodu niepełnej inicjalizacji. Spowodowałoby to nieprawidłowe działanie urządzenia, w którym flaga wyłączenia została usunięta, ale urządzenie nie otrzymywałoby dalszych instrukcji, dopóki nie zostanie ponownie zainicjowane przez sterownik. Drugi reset oznaczałby, że urządzenie nie próbuje odczytać niepoprawnych danych, które zostały na nim zapisane, i otrzyma prawidłowe instrukcje konfiguracji, pozwalając na ustawienie go we właściwym stanie (przy założeniu, że aplikacja wysyłająca ioctls nie uległa awarii ).

Ostatecznie ustalenie dokładnego zestawu okoliczności, które spowodowały problem, zajęło około dwóch tygodni.

Cercerilla
źródło
2

W przypadku projektu uniwersyteckiego pisaliśmy rozproszony system węzłów P2P, który udostępnia pliki, obsługuje to multiemisję w celu wzajemnego wykrywania, wielu pierścieni węzłów i serwera nazw, dzięki czemu węzeł jest przypisany do klienta.

Napisany w C ++ użyliśmy do tego POCO, ponieważ pozwala na ładne programowanie IO, Socket i Thread.


Pojawiły się dwa błędy, które nas zirytowały i spowodowały, że straciliśmy dużo czasu, naprawdę logiczna:

Losowo komputer współdzielił swój lokalny adres IP zamiast zdalnego adresu IP.

Spowodowało to, że klienci łączą się z węzłem na tym samym komputerze lub węzły, aby łączyć się ze sobą.

Jak to zidentyfikowaliśmy? Kiedy poprawiliśmy dane wyjściowe w serwerze nazw, odkryliśmy w późniejszym czasie, kiedy ponownie uruchomiliśmy komputery, które nasz skrypt w celu ustalenia adresu IP był nieprawidłowy. Losowo urządzenie lo pojawiło się na pierwszym miejscu zamiast urządzenia eth0 ... Naprawdę głupie. Więc teraz na stałe zapisaliśmy żądanie od eth0, ponieważ jest to wspólne dla wszystkich komputerów uniwersyteckich ...


A teraz bardziej irytujący:

Losowo przepływ pakietów losowo zatrzymywałby się.
Gdy następny klient się połączy, będzie kontynuował ...

Stało się to naprawdę losowo, a ponieważ zaangażowany jest więcej niż jeden komputer, debugowanie tego problemu stało się bardziej denerwujące, komputery uniwersyteckie nie pozwalają nam uruchamiać Wireshark na tych, więc możemy zgadywać, czy problem był po stronie wysyłającej, czy odbierającej bok.

Z dużą ilością danych wyjściowych w kodzie po prostu przyjęliśmy założenie, że wysyłanie poleceń idzie dobrze,
pozostawiło nas to zastanawianie się, gdzie był prawdziwy problem ... Wydawało się, że sposób sondowania POCO jest nieprawidłowy i że zamiast tego powinniśmy sprawdzić dostępne znaki na przychodzącym gnieździe.

Przyjęliśmy założenie, że działało to, ponieważ prostsze testy w prototypie z mniejszą liczbą pakietów nie spowodowały tego problemu, więc to sprawiło, że po prostu założyliśmy, że instrukcja ankiety działała, ale ... Nie było. :-(


Zdobyta wiedza:

  • Nie rób głupich założeń, takich jak kolejność urządzeń sieciowych.

  • Frameworki nie zawsze dobrze wykonują swoje zadanie (implementację lub dokumentację).

  • Podaj wystarczającą ilość danych wyjściowych w kodzie, jeśli nie jest to dozwolone, pamiętaj, aby zapisać szczegółowe informacje w pliku.

  • Gdy kod nie został przetestowany jednostkowo (ponieważ jest zbyt trudny), nie zakładaj, że coś zadziała.

Tamara Wijsman
źródło
1
Rozwiązywanie problemów z siecią bez wireshark (lub podobnego narzędzia) jest heroiczne w / of iteslf.
Evan Plaice,
2

Nadal jestem na najtrudniejszym polowaniu na robale. Jest to jeden z tych, które czasem tam są, a czasem nie ma błędów. Właśnie dlatego tu jestem, o 6:10 następnego dnia.

Tło:

  • Kontekst: język, aplikacja, środowisko itp.
    • PHP OS Commerce
  • Jak zidentyfikowano błąd?
    • Losowe zamówienia, które działają częściowo w przypadkowych przypadkach awarii i przekierowań
  • Kto lub co zidentyfikowało błąd?
    • Klient i problem z przekierowaniem był oczywisty
  • Jak skomplikowane było odtwarzanie błędu?
    • Nie byłem w stanie się rozmnażać, ale klient był w stanie.

Polowanie

  • Jaki był twój plan
    • Dodaj kod debugowania, wypełnij zamówienie, przeanalizuj dane, powtórz
  • Jakie napotkałeś trudności?
    • Brak powtarzalnych problemów i okropny kod
  • Jak w końcu znaleziono obrażający kod?
    • znaleziono wiele szkodliwych kodów ... po prostu nie do końca to, czego potrzebowałem.

The Killing.

  • Jak skomplikowana była poprawka?
    • bardzo
  • Jak określiłeś zakres poprawki?
    • nie było zasięgu ... było wszędzie
  • Ile kodu wymagało poprawki?
    • Wszystko? Nie sądzę, żeby plik był nietknięty

Sekcja zwłok.

  • Jaka była podstawowa przyczyna technicznie? przepełnienie bufora itp.
    • zła praktyka kodowania
  • Jaka była pierwotna przyczyna od 30 000 stóp?
    • Wolę nie mówić...
  • Jak długo trwał ten proces?
    • zawsze i jeden dzień dłużej
  • Czy poprawka wpłynęła negatywnie na jakieś funkcje?
    • cecha? czy to błąd?
  • Jakie metody, narzędzia i motywacje uważasz za szczególnie pomocne? ... okropnie bezużyteczne?
  • Gdybyś mógł zrobić to wszystko jeszcze raz? ............
    • Ctrl + Del
WalterJ89
źródło
Jeśli powodem była „zła praktyka kodowania”, możesz porozmawiać z szefem, czy to dobry moment na zrewidowanie praktyk kodowania w swoim zespole i być może wprowadzenie wzajemnej oceny?
2

Musiałem naprawić pewne mylące rzeczy dotyczące współbieżności w ostatnim półroczu, ale błąd, który wciąż najbardziej się dla mnie wyróżniał, to gra tekstowa, którą pisałem w zestawie PDP-11, aby wykonać zadanie domowe. Opierał się on na grze życia Conwaya iz jakiegoś dziwnego powodu duża część informacji obok siatki była ciągle zastępowana informacjami, które nie powinny tam być. Logika była również dość prosta, więc była bardzo myląca. Po kilkakrotnym przejrzeniu tego, aby odkryć, że cała logika jest poprawna, nagle zauważyłem, na czym polega problem. Ta rzecz:.

W PDP-11 ta mała kropka obok liczby powoduje, że jest to podstawa 10 zamiast 8. Była ona obok liczby ograniczającej pętlę, która miała być ograniczona do siatki, której rozmiar został zdefiniowany przy użyciu tych samych liczb, ale w podstawie 8

Wciąż mnie to wyróżnia, ponieważ ze względu na wielkość obrażeń spowodowanych przez tak mały dodatek o wielkości 4 pikseli. Więc jaki jest wniosek? Nie koduj w zestawie PDP-11.

EpsilonVector
źródło
2

Program ramy głównej przestał działać bez powodu

Właśnie zamieściłem to na inne pytanie. Zobacz post tutaj

Stało się tak, ponieważ zainstalowali nowszą wersję kompilatora na Main-Frame.

Aktualizacja 06.11.13: (Oryginalna odpowiedź została usunięta przez OP)

Odziedziczyłem tę aplikację ramki głównej. Któregoś dnia przestało działać. To jest to ... po prostu przestało.

Moim zadaniem było jak najszybsze działanie. Kod źródłowy nie był modyfikowany przez dwa lata, ale nagle przestał. Próbowałem skompilować kod, który zepsuł się na linii XX. Spojrzałem na linię XX i nie mogłem powiedzieć, co spowodowałoby przerwanie linii XX. Poprosiłem o szczegółowe specyfikacje dla tej aplikacji i nie było żadnych. Linia XX nie była winowajcą.

Wydrukowałem kod i zacząłem go przeglądać od góry do dołu. Zacząłem tworzyć schemat blokowy tego, co się działo. Kod był tak zawiły, że ledwie mogłem go zrozumieć. Zrezygnowałem z próbowania schematu blokowego. Bałam się dokonywać zmian, nie wiedząc, jak ta zmiana wpłynie na resztę procesu, zwłaszcza że nie miałem szczegółowych informacji na temat działania aplikacji.

Postanowiłem więc zacząć od początku kodu źródłowego i dodać białe znaki i hamulce linii, aby kod był bardziej czytelny. Zauważyłem, że w niektórych przypadkach występowały warunki, które łączyły operatory AND i operatory OR i nie można było jednoznacznie odróżnić, które dane były operatorem AND i jakie dane były operatorem OR. Zacząłem więc umieszczać nawiasy wokół warunków AND i OR, aby były bardziej czytelne.

Gdy powoli przesuwałem się w dół, aby go wyczyścić, okresowo zapisywałem swoją pracę. W pewnym momencie próbowałem skompilować kod i wydarzyło się coś dziwnego. Błąd przeskoczył, przekroczył pierwotny wiersz kodu i był teraz niższy. Więc kontynuowałem, rozróżniając warunki AND i OR za pomocą parens. Kiedy skończyłem sprzątać, zadziałało. Domyśl.

Następnie postanowiłem odwiedzić sklep operacyjny i zapytać, czy ostatnio zainstalowali jakieś nowe komponenty na ramie głównej. Powiedzieli tak, niedawno zaktualizowaliśmy kompilator. Hmmmm

Okazuje się, że stary kompilator niezależnie oceniał wyrażenie od lewej do prawej. Nowa wersja kompilatora oceniała również wyrażenia od lewej do prawej, ale niejednoznaczny kod, co oznacza, że ​​nie można rozwiązać niejasnej kombinacji AND i OR.

Lekcja, której się nauczyłem z tego ... ZAWSZE, ZAWSZE, ZAWSZE używajcie parenów do oddzielenia ORAZ warunków i LUB warunków, gdy są one używane w połączeniu ze sobą.

Michael Riley - AKA Gunny
źródło
post, na który wskazuje Twój link, został usunięty - czy mógłbyś zaktualizować odpowiedź?
komar
1
@gnat - Znaleziono go na archive.org :)
Michael Riley - AKA Gunny
1

Tło:

  • Kontekst: Serwer sieci Web (C ++), który umożliwia klientom samodzielne zameldowanie
  • Błąd: podczas żądania strony po prostu nie odpowiadałby, to znaczy cała farma, a procesy zostałyby zabite (i ponownie uruchomione), ponieważ zajęły zbyt długo (tylko kilka sekund) na wyświetlenie strony
  • Niektórzy użytkownicy narzekali, ale było to bardzo sporadyczne, więc w większości niezauważalne (ludzie po prostu klikają „Odśwież”, gdy strona nie jest wyświetlana). Zauważyliśmy jednak zrzuty rdzeni;)
  • Naprawdę nigdy nie udało nam się odtworzyć w naszych lokalnych środowiskach, błąd pojawił się kilka razy w systemach testowych, ale nigdy nie pojawił się podczas testów wydajności?

Polowanie

  • Plan: Cóż, skoro mieliśmy zrzuty pamięci i dzienniki, chcieliśmy je przeanalizować. Ponieważ miało to wpływ na całą farmę i mieliśmy w przeszłości pewne problemy z bazami danych, podejrzewaliśmy, że baza danych (pojedynczy DB dla kilku serwerów)
  • Trudności: Pełny zrzut serwera jest ogromny, więc są one często czyszczone (aby nie zabrakło miejsca), więc musieliśmy szybko go złapać, kiedy to wystąpiło ... Trwaliśmy. Zrzuty pokazały różne stosy (nigdy żadnych danych DB, tyle za to), nie powiodły się podczas przygotowywania samej strony (nie w poprzednich obliczeniach) i potwierdziły to, co pokazały dzienniki, przygotowanie strony czasami zajmuje dużo czasu, nawet chociaż jest to tylko podstawowy silnik szablonów z wstępnie obliczonymi danymi (tradycyjny MVC)
  • Dotarcie do tego: po kilku próbkach i przemyśleniu zdaliśmy sobie sprawę, że zajęło to odczytanie danych z dysku twardego (szablon strony). Ponieważ dotyczyło to całej farmy, najpierw szukaliśmy zaplanowanych zadań (crontab, partie), ale czasy nigdy nie pasowały do ​​jednego wystąpienia do drugiego ... W końcu przyszło mi do głowy, że zawsze tak było na kilka dni przed aktywacją nowej wersji oprogramowania i miałem AhAh! chwila ... było to spowodowane dystrybucją oprogramowania! Dostarczenie kilkuset megabajtów (skompresowanych) może nieco obniżyć wydajność dysku: / Oczywiście dystrybucja jest zautomatyzowana, a archiwum wypychane na wszystkie serwery jednocześnie (multiemisja).

The Killing.

  • Napraw złożoność: przejście na skompilowane szablony
  • Podatny kod: brak, prosta zmiana w procesie kompilacji

Sekcja zwłok.

  • Główna przyczyna: problem operacyjny lub brak planowania do przodu :)
  • Skala czasu: wytropienie zajęło miesiące, naprawa i testowanie zajęło kilka dni, kilka tygodni na kontrolę jakości i testowanie wydajności i wdrożenie - nie spiesz się, ponieważ wiedzieliśmy, że wdrożenie poprawki spowoduje błąd ... i nic inaczej ... trochę zboczeńcem naprawdę!
  • Niekorzystne skutki uboczne: niemożność przełączania szablonów w czasie wykonywania, ponieważ są one wypierane w dostarczonym kodzie, jednak nie korzystaliśmy z tej funkcji zbyt często, ponieważ ogólnie zmiana szablonów oznacza, że ​​masz więcej danych do wlania. Korzystanie z css jest w większości wystarczające do „małych” zmian układu.
  • Metody, narzędzia: gdb + monitorowanie! Właśnie zajęło nam podejrzenie dysku, a następnie zidentyfikowanie przyczyny skoków aktywności na wykresie monitorowania ...
  • Następnym razem: potraktuj wszystkie IO jako niekorzystne!
Matthieu M.
źródło
1

Najtwardszy nigdy nie został zabity, ponieważ nigdy nie mógł być odtworzony inaczej niż w pełnym środowisku produkcyjnym z działającą fabryką.

Najbardziej szalony, którego zabiłem:

Rysunki drukują bełkot!

Patrzę na kod i nic nie widzę. Wyciągam zadanie z kolejki drukarki i sprawdzam, czy wszystko wygląda dobrze. (To było w erze dos, PCL5 z wbudowanym HPGl / 2 - w rzeczywistości bardzo dobry do kreślenia rysunków i bez problemów z budowaniem obrazu rastrowego w ograniczonej pamięci.) Kieruję go do innej drukarki, która powinna to zrozumieć, drukuje dobrze .

Cofnij kod, problem nadal występuje.

Wreszcie ręcznie tworzę prosty plik i wysyłam go do drukarki - bełkot. Okazuje się, że to wcale nie był mój błąd, ale sama drukarka. Firma serwisowa sflashowała go do najnowszej wersji, gdy naprawiali coś innego, a ta najnowsza wersja miała błąd. Sprawienie, by zrozumieli, że wyjęli krytyczną funkcjonalność i musieli ją przywrócić do wcześniejszej wersji, było trudniejsze niż znalezienie samego błędu.

Ten był jeszcze bardziej irytujący, ale ponieważ był tylko na moim pudełku, nie postawiłbym na pierwszym miejscu:

Borland Pascal, kod DPMI do obsługi niektórych nieobsługiwanych interfejsów API. Uruchom, czasami działało, zwykle szło boom próbując poradzić sobie z nieprawidłowym wskaźnikiem. Jednak nigdy nie przyniosło to złych rezultatów, tak jak można się spodziewać po tupaniu wskaźnikiem.

Debugowanie - jeśli przejdę krok po kroku przez kod, zawsze będzie działał poprawnie, w przeciwnym razie byłby tak samo niestabilny jak poprzednio. Inspekcja zawsze pokazywała właściwe wartości.

Sprawca: Były dwa.

1) W kodzie biblioteki Borlanda wystąpił poważny błąd: wskaźniki trybu rzeczywistego były przechowywane w zmiennych wskaźnikowych w trybie chronionym. Problem polega na tym, że większość wskaźników trybu rzeczywistego ma nieprawidłowe adresy segmentów w trybie chronionym, a kiedy próbujesz skopiować wskaźnik, ładuje go do pary rejestrów, a następnie zapisuje.

2) Debugger nigdy nie powiedziałby nic o tak nieprawidłowym obciążeniu w trybie jednoetapowym. Nie wiem, co zrobił wewnętrznie, ale to, co zostało przedstawione użytkownikowi, wyglądało zupełnie poprawnie. Podejrzewam, że tak naprawdę nie wykonywał instrukcji, tylko ją symulował.

Loren Pechtel
źródło
1

To tylko bardzo prosty błąd, który jakoś zamienił dla mnie koszmar.

Tło: Pracowałem nad stworzeniem własnego systemu operacyjnego. Debugowanie jest bardzo trudne (instrukcje śledzenia to wszystko, co możesz mieć, a czasem nawet nie to)

Błąd: Zamiast robić dwa przełączniki wątków w trybie użytkownika, zamiast tego generowałby ogólny błąd ochrony.

Polowanie na błędy: spędziłem prawdopodobnie tydzień lub dwa, próbując rozwiązać ten problem. Wstawianie instrukcji śledzenia wszędzie. Badanie wygenerowanego kodu zestawu (z GCC). Wydrukowałem każdą wartość, jaką mogłem.

Problem: Gdzieś na początku polowania na błędy umieściłem hltinstrukcję w crt0. Crt0 jest w zasadzie tym, co ładuje program użytkownika do użycia w systemie operacyjnym. Ta hltinstrukcja powoduje GPF po uruchomieniu z trybu użytkownika. Umieściłem go tam i po prostu o tym zapomniałem. (pierwotnie problem polegał na przepełnieniu bufora lub błędzie alokacji pamięci)

Poprawka: Usuń hltinstrukcję :) Po usunięciu wszystko działało gładko.

Czego się nauczyłem: Kiedy próbuję debugować problem, nie trać śledzenia próbowanych poprawek. Regularnie rób różnice w stosunku do najnowszej stabilnej wersji kontroli źródła i zobacz, co ostatnio zmieniłeś, gdy nic innego nie działa

Earlz
źródło