Poprawność programów. Błąd, źródła błędów.
Bug to w żargonie informatycznym, wyrażenie oznaczające pewien błąd, usterkę - niepożądane, powtarzające się przy zajściu określonych czynników działanie programu, wynikające z błędu człowieka na jednym z etapów tworzenia oprogramowania, zwykle na etapie projektowania lub tworzenia kodu źródłowego.
Nazwa
Wprowadzenie do użycia wspomnianego terminu angielskiego jest przypisywane admirał Grace Brewster Murray Hopper, choć pewne źródła negują jej udział w poniższej historii.
Podczas prowadzonych - według różnych źródeł w 1945 lub 1947 r. - pracach z prymitywnym komputerem Harvard Mark II operator stwierdził jego nieprawidłowe działanie, i po poszukiwaniach przyczyny znalazł pomiędzy gołymi przewodami przekaźnika drobnego insekta (ang. bug) - ćmę, która powodowała spięcie. Dziennik obecnie znajduje się w Naval Surface Warfare Center Computer Museum w Dahlgren w stanie Wirginia (USA).
Słowo bug jest często tłumaczone w tym kontekście jako pluskwa, oznaczającym obecnie - podobnie jak w angielskim źródłosłowie - również podsłuch lub podgląd elektroniczny (mikrofon, kamera).
Pluskwy, czyli błędy programistyczne są obiektem wielu spośród tzw. praw Murphy'ego, m.in. głęboko słusznego "w każdym programie (dłuższym niż 100 linijek) jest jeszcze jeden błąd".
Diagnoza i usuwanie
Bug jako nazwa błędu programistycznego występuje w nazwach programów pomagających usuwać błedy, tzw. debugerów, czy też "odpluskwiaczy". Programy te pozwalają śledzić wartości określonych zmiennych i rejestrów wykorzystywanych w programie do momentu wystąpienia błędu celem znalezienia dokładnego miejsca w kodzie źródłowym, które należy zmienić, by bug się nie pojawiał.
Środowisko otwartego oprogramowania wykształciło złożone systemy zbierania informacji o istniejących usterkach i niedogodnościach w programach. Do najpopularniejszych należy Bugzilla (podobieństwo do nazwy Mozilla nieprzypadkowe), stosowany również przez fundację MediaWiki do zbierania informacji o błędach w oprogramowaniu Wikipedii i pokrewnych Wiki (patrz: tu). W systemie Bugzilla błąd może zgłosić każdy, przez określenie warunków, w jakich się pojawia. Zgłoszenie jest następnie przydzielane określonemu programiście, a system zawiera aktualne informacje o postępach w naprawianiu usterki.
Prewencja
W typowych warunkach można się spodziewać, że w każdym nietrywialnym programie będzie sporo bugów.
Ich ilość jednak można znacząco ograniczyć. Uważa się, że ilość bugów na linijkę kodu jest w przybliżeniu niezależna od języka, czyli program o tej samej funkcjonalności napisany w języku wyższego poziomu (np. Perl czy Python) będzie miał mniej bugów niż w języku niższego poziomu (Java, C, czy asembler).
Ilość bugów można też ograniczyć przez pisanie testów. Testy te powinny być w miarę możliwości zautomatyzowane - komputer potrafi przeprowadzić o kilka rzędów wielkości więcej testów na godzinę niż człowiek. Ilość bugów można redukować przez ręczne audyty kodu, jawne opisanie założeń jakie przyjmuje kod (np. co do typów danych wejściowych, czy spodziewanego sposobu użycia), unikanie trudnych w analizie konstrukcji (jak słynne goto, czy ewaluacja kodu w trakcie wykonania), czy przez używanie narzędzi wykrywających podejrzane fragmenty kodu (lint, ostrzeżenia kompilatora).
Czasem bugi wykrywa się przez karmienie programu losowymi danymi i sprawdzanie otrzymywanych odpowiedzi. Ponieważ typowe bugi dotyczą wielu danych, wykrywa się w ten sposób większość bugów.
Bardzo rzadko prowadzi się dowody matematyczne programów. Nie dają one jednak w praktyce pewności, ponieważ nie ma gwarancji, że nie było buga w modelu zachowania programu (jeśli ta sama osoba pisze kod i dowód, ten sam bug mógł się pojawić w obu), ani też, że stosowany przez nas model matematyczny odpowiada rzeczywistości (np. kompilator czy nawet sam procesor może wprowadzić optymalizacje, które psują "poprawny" kod).
Ponieważ testowanie dużych czynności jest trudną operacją, zwykle testuje się osobno podzespoły programu, oraz program w całości (zakładając przy tym, że podzespoły działają poprawnie). Może to oczywiście przeoczyć pewną klasę bugów.
Często, np. w przypadku programów sieciowych, operujących bezpośrednio na sprzęcie czy wymagających interakcji z wieloma użytkownikami, trudno jest testować program w naturalnym środowisku, i konieczne jest testowanie w środowisku sztucznym, za pomocą emulatorów sprzętu, sieci czy sztucznego karmienia programu wydarzeniami udającymi użytkowników ("wpisano X w pole Y", "kliknięto na przycisk" itd.).
Testy pisze się zwykle w późnej fazie rozwoju oprogramowania. W metodologii extreme programming testy pisze się zanim rozpocznie się pisanie danej części oprogramowania, co ma zmniejszyć liczbę błędów.
Do tworzenia testów i zarządzania nimi istnieje wiele systemów, tzw. testing frameworks, takich jak XUnit.
Ogólny błąd ochrony
Ogólny błąd ochrony (ang. general protection fault) w architekturze Intel x86 jest błędem pojawiającym się, gdy bieżąco wykonywany program komputerowy w jakiś sposób narusza zasady sprzętu, na którym jest wykonywany. Te zasady mają za zadanie chronić programy przed oczywistymi i potencjalnie katastrofalnymi błędami wykonywania. Błędy, które powodują wystąpienie ogólnego błędu ochrony, są zazwyczaj nieodwracalne i uważa się, że najbezpieczniejszą reakcją na nie jest przerwanie programu.
Przyczyny błędów
Wyróżnia się na ogół cztery typy zdarzeń, które powodują powstanie tego błędu.
W Microsoft Windows każde z nich powoduje zgłoszenie komunikatu "ogólny błąd ochrony". W systemach takich jak Unix lub Linux, błędy te są zgłaszane osobno (np. segmentation fault w przypadku błędów pamięci).
Błędy pamięci
Ten przypadek zachodzi, gdy program próbuje wykonać operację na części pamięci komputera, do której nie powinien mieć dostępu. Obejmuje on:
Próbę odczytania danych z pamięci przeznaczonej na instrukcje
Inne konflikty pomiędzy przeznaczeniem pamięci a jej użytkowaniem przez program
Błędy braku uprawnień
W komputerze są obszary, które są zarezerwowane do wyłącznego użytku systemu operacyjnego. Jeśli program, który nie jest częścią systemu operacyjnego próbuje użyć któregoś z nich, może spowodować to powstanie błędu ochrony.
Błędne zachowanie
Specyfikacja architektury Intela zawiera pewne konwencje, do których programy powinny się stosować, przeważnie ze względu na ochronę poprawności danych w programach i pomiędzy nimi. Program, który ich nie przestrzega, może spowodować powstanie ogólnego błędu ochrony.
Błędy formatu
Ogólny błąd ochrony może się pojawić, jeśli instrukcja załadowana przez procesor jest nieprawidłowa lub zbyt długa.
Techniczny opis przyczyn błędów
Ogólny błąd ochrony może zostać wywołany z wielu różnych powodów:
błędy naruszenia segmentacji poprzez przekroczenie granic segmentu
przy użyciu rejestrów CS, DS, ES, FS, lub GS,
poprzez dostęp do tablic deskryptorów (takich jak globalna tablica deskryptorów - GDT, tablica deskryptorów przerwań - IDT, lub lokalna tablica deskryptorów - LDT),
błędy naruszenia segmentacji poprzez złamanie reguł dostępu do segmentu
skok do kodu w segmencie oznaczonym jak nie wykonywalny (ang. nonexecutable segments)
zapis do segmentu kodu lub do segmentów tylko do odczytu
czytanie z segmentów przeznaczonych tylko do wykonania (ang. execute-only segments)
nieprawidłowa zawartość rejestrów segmentowych
segment stosu (ang. stack segment - SS) zawiera selektor segmentu tylko do odczytu, wykonania lub segment pusty (ang. null segment)
segment kodu (ang. code segment - CS) zawiera selektor segmentu danych lub segmentu pustego
DS, ES, ES, FS, GS zawiera selektor segmentu wskazujący na segment tylko do wykonania
dostęp do pamięci przy użyciu DS, ES, FS, lub GS, gdy zawierają one zerowy selektor
przełączanie zadań (TSS)
przełączanie do zajętego zadania podczas wywołania lub skoku
przełączanie do dostępnego zadania podczas IRET
inne
próba zapisu 1 do zarezerwowanych bitów CR4
próba wykonania uprzywilejowanej instrukcji przy poziomie uprzywilejowania (ang. current privilege level - CPL) różnym od 0
zapis zarezerwowanego bitu w MSR
dostęp do bramy zawierającej zerowy selektor segmentu
wykonanie przerwania programowego (ang. software interrupt) gdy CPL jest większy niż DPL dla bramy wywołania przerwania
selektor segmentu w gramie wywołania (ang. call), przerwania (ang. interrupt) lub pułapki (ang. trap) nie wskazuje na segment kodu
przekroczenie uprawnień