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”
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:
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
MOD
tak, że-4096 MOD 4096
spowodowało-1
HP wprowadziły go poprawnie matematycznie tak, że-4096 MOD 4096
spowodował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,
MOD
a następnie pomnożeniem wyniku przez wartość znaku.Zajęło to kilka dni.
źródło
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.
źródło
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:
Następnie wybrałem Wireshark i zacząłem ręcznie dekodować przechwytywanie pakietów. Wynik:
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.
źródło
Tło
Bug
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.
źródło
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.
źródło
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.
źródło
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:
Polowanie
The Killing.
Sekcja zwłok.
źródło
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.
źródło
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ą.
źródło
Tło:
Polowanie
The Killing.
Sekcja zwłok.
gdb
+ monitorowanie! Właśnie zajęło nam podejrzenie dysku, a następnie zidentyfikowanie przyczyny skoków aktywności na wykresie monitorowania ...źródło
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ł.
źródło
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
hlt
instrukcję w crt0. Crt0 jest w zasadzie tym, co ładuje program użytkownika do użycia w systemie operacyjnym. Tahlt
instrukcja 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ń
hlt
instrukcję :) 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
źródło