Projektowanie oprogramowania systemów

Transkrypt

Projektowanie oprogramowania systemów
Projektowanie
oprogramowania
systemów
KONFIGURACJE OPROGRAMOWANIA; DEBUGGING, PROFILOWANIE,
NARZĘDZIA ZAPEWNIANIA JAKOŚCI
plan

Konfiguracje budowania i uruchamiania oprogramowania

Debugowanie



Funkcje debuggerów

Zdalne debugowanie

Analiza post-mortem

Narzędzia
Narzędzia profilowania i optymalizacji kodu

Hotspoty w kodzie

Narzędzia
Zapewnianie jakości

Metryki pokrycia kodu

Śledzenie problemów z alokacją pamięci

Narzędzia
“
Programmers waste enormous amounts of time thinking about, or
worrying about, the speed of noncritical parts of their programs, and
these attempts at efficiency actually have a strong negative impact
when debugging and maintenance are considered. We should forget
about small efficiencies, say about 97% of the time: premature
optimization is the root of all evil. Yet we should not pass up our
opportunities in that critical 3%.
DONALD KNUTH
w skrócie: przedwczesna optymalizacja jest źródłem wszelkiego zła ;)
”
konfiguracje budowania i
uruchamiania oprogramowania

Tworząc nowy projekt większość współczesnych środowisk IDE
stworzy automatycznie 2 konfiguracje: Debug i Release

Zgodnie z nazwą, konfiguracja Debug służy do debugowania
(odpluskwiania, uzdatniania) kodu (a w zasadzie większości prac
wdrożeniowych), zaś konfiguracja Release jest tym, co dostarczamy
klientowi

Uwaga więc na subtelne bugi, które pojawiają się tylko w
konfiguracji Release!

Debugowanie takiego kodu jest boleśnie trudne, ale czasami
nieuniknione

Z tego względu większość nietrywialnych systemów opiera się nie tylko
na debugowaniu ale również implementuje pewnego rodzaju
logowanie wykonania
konfiguracje
Debug

Optymalizacje wprowadzane przez
kompilator są wyłączone

Wyłączone jest rozwijanie funkcji
inline

Działają makra i funkcje
debugowania (np. assert - asercje)

Symbole debugowania są obecne
w kodzie

Kod działa dużo wolniej
g++ -g –O0 source.cpp
Release

Optymalizacje są włączone, przez
co debugowanie jest
nieprzewidywalne: kod ma
zmienioną kolejność, zmienne są
wyoptymalizowane z pamięci do
rejestrów, pomijane są ramki stosu

Makra debugowania nie są
zdefiniowane lub nie robią nic

Symbole debugowania są usunięte
(stripped) z kodu lub przeniesione do
osobnych plików

Kod działa z pełną prędkością
g++ -O2 –DNDEBUG source.cpp
asercje

Warunki, które programista zakłada, że zawsze muszą być spełnione

Wprowadzane poprzez użycie makra assert (C/C++) lub analogicznych
funkcji/słów kluczowych w innych językach

W konfiguracji Release asercje są usuwane

W konfiguracji Debug asercje spowodują zatrzymanie programu,
wyświetlenie komunikatu debugującego, wywołanie funkcji abort(), która
spowoduje crash (wywalenie się) programu sygnałem SIGABRT (Unix), a
następnie zapisanie zrzutu pamięci (core dump) dla debugera post-mortem

Używaj asercji dla weryfikacji warunków wstępnych i końcowych (pre- and
post-conditions), nie dla regularnej obsługi błędów!
przełączanie konfiguracji
debugowanie

W dalekiej przeszłości komputerów lampowych częstym źródłem
błędów były owady które osiedlał się wewnątrz urządzeń
powodując ich przegrzewanie, stąd nazwa „odpluskwianie”
(debugging)

Debugger jest narzędziem dynamicznej analizy innych
programów, używanym do znajdowania i identyfikowania
błędów programistycznych

Większość środowisk IDE zawiera zintegrowany debugger, albo
swój własny, albo „front-end” do GDB (GNU Debugger)
typowe funkcje debuggera


Wykonanie krokowe

Program jest wykonywany linia-po-linii albo instrukcja-po-instrukcji,
umożliwiając zbadanie efektów każdego wywołania

Tryby: step over, step into, step out, continue to line
Ustawianie pułapek (breakpoints)


Wstrzymuje wykonanie programu na określonej linii kodu albo kiedy
określony warunek jest spełniony
Call stack (podgląd stosu)

Pokazuje stos wywołań funkcji umożliwiając wizualizację
„Jak się znaleźliśmy w tym miejscu?”
typowe funkcje debugera


Zmienne lokalne/obserwowanie
(locals/watches)

Badanie wartości lokalnych zmiennych
w danej chwili czasu lub śledzenie
jak wartość określonej zmiennej zmienia
się w czasie

Modyfikacja wartości zmiennych w czasie
wykonania programu
Wątki (threads)


Badanie stanu wątków w programie,
wymuszanie przełączenia wątków,
badanie stosu każdego wątku,
diagnozowanie deadlocków
Pamięć (memory)

Badanie i zmiana zawartości przestrzeni adresowej programu
typowe funkcje debugera

Deasemblacja (disassembly)

Pokazuje kod programu jako mnemoniki
asemblera

Krokowe wykonanie kodu asemblera

Użyteczne do znajdowania bardzo
tajemniczych bugów

Kod asemblera jest przeplatany z
odpowiadającym mu kodem
wysokopoziomowym – fajny sposób nauki
asemblera ;>
typowe funkcje debugera

Zdalne debugowanie

Zainstaluj “debugging stub” na zdalnej maszynie

Podłącz swój lokalny debuger do zdalnego komputera za pomocą sieci
lub portu szeregowego

Debuguj zdalny program tak jakby działał na maszynie lokalnej

Użyteczne dla:

Debugowania programów niskopoziomowych, np. jądra systemu
operacyjnego, sterowników urządzeń

Debugowania programów działających w ograniczonych środowiskach, np.
na telefonie komórkowym, systemach osadzonych

Debugowania problemów związanych z interfejsem użytkownika, kiedy
interakcja z UI debugera sprawia, że nie można powtórzyć błędu
typowe funkcje debugera

Podłączanie debugera do działającego programu

Użyteczne dla debugowania programów działających w specyficznych
środowiskach, nad którymi nie mamy pełnej kontroli, np. usług systemu
operacyjnego/demonów, obiektów COM, kodu działającego wewnątrz
serwerów aplikacji

Debugowanie bibliotek współdzielonych ładowanych przez inne
programy

Modyfikuj ścieżkę wykonania programu grzebiąc w jego wewnętrznym
stanie, wykrywaj luki bezpieczeństwa, projektuj i szerz wirusy!
typowe funkcje debugera

Debugowanie post-mortem (pośmiertne)

Badanie stanu „padniętej” aplikacji w momencie jej „wywalenia się”

Na Uniksie – badanie zrzutu pamięci - pliku core

Na Windows – możliwe za pomocą biblioteki dbghelp.dll, program musi
zawierać jawne wywołanie kodu zapisującego pliki minidump

Zwykle samo spojrzenie na stos programu w momencie crashu (jaka
instrukcja była wywołana i jaki był jej kontekst) wystarcza aby wyśledzić
przyczynę wywalenia się

Ta informacja jest bezcenna!

Po prostu załaduj zrzut do debugera i „uruchom”
go jak normalny kod…

Wymuś zrzut pamięci poprzez wywołanie
funkcji abort() lub niespełnioną asercję
tipsy

Debuger jest prawdopodobnie pojedynczą najważniejszą
przyczyną dla używania środowiska IDE zamiast ręcznego
systemu budowania i ogólnych edytorów tekstu

Naucz się i używaj skrótów klawiszowych debugera w
Twoim środowisku IDE; niesamowicie podniesie to Twoją
produktywność

Jedynym powodem olbrzymiego sukcesu pierwszych wersji
MS Visual Studio było to, że jego wizualny debuger był
rewelacyjny, jako że kompilator zawsze był przestarzały i
niezgodny ze standardami

Większość (prawie wszystkie?) środowiska IDE nie
pochodzące z Microsoftu używają GDB dla debugowania
kodu C/C++; ma on mnóstwo potężnych funkcji ale jest
generalnie trudny w obsłudze (interfejs z linii poleceń)
narzędzia







Microsoft Visual Studio – zintegrowany debuger, prawdopodobnie
najlepszy jaki istnieje ;>
GDB – “standardowy” debuger na Linuksie/Uniksach, używalny także na
Windows – interfejs wiersza poleceń (używaj front-end!)
Eclipse – przyjemny GUI do GDB, zbliża się użytecznością do MSVC;
zawiera własny, bardzo dobry debuger do Javy; również użyteczny do
zdalnego debugowania aplikacji Androida
Xcode – IDE firmy Apple wraz z debuggerem; działa na Makach i
pozwala również na zdalne debugowanie aplikacji iOS
WinDbg – Microsoftowy debuger dla zdalnego debugowania systemu
operacyjnego (jądro, sterowniki)
DDD (Data Display Debugger) – front-end do GDB i kilku innych
debugerów uniksowych, lepszy niż nic…
Kdbg, xxdbg… - patrz wyżej^
profilowanie i optymalizacja kodu
programu

“Profilowanie” – dynamiczna analiza programu w celu zmierzenia
czasu wykonania, użycia pamięci, wydajności

Najprostsze/ad hoc podejście – użyj wprost wywołań funkcji
pomiaru czasu do mierzenia czasu wykonania fragmentu kodu

Podejście systematyczne – użyj profilera aby wykonać różne
pomiary, m.in..:

Instrumentacja – wprowadza niewidoczny dla programisty kod mierzący
czas wywołań do kodu binarnego dla każdej lub wybranych instrukcji

Próbkowanie – użycie specyficznych funkcji sprzętu (procesora) w celu
próbkowania stosu wywołań programu z określoną częstotliwością

…
hot spoty

Właściwe użycie profilera pozwala zidentyfikować hot spoty w
kodzie programu

Hot spot jest to fragment kodu, który jest wywoływany często lub
program spędza wykonując go większość czasu wykonania

Wpływ kodu znajdującego się poza hot-spotami na czas wykonania
programu jest marginalny; nie ma sensu tracić czasu na ręczne
optymalizacje takiego kodu

Optymalizacja kodu hot spotów może w sposób znaczący wpłynąć
na wydajność programu

Decyzja o przeprowadzaniu optymalizacji bez znajomości hot
spotów jest bez sensu
profilowanie innych wielkości


Profilera można również użyć do mierzenia:

Zajętości pamięci – poprzez użycie specjalnych funkcji zastępujących
standardowe narzędzia alokacji lub instrumentację kodu

Wydajność I/O – poprzez użycie odpowiednich usług systemu
operacyjnego (counters) lub instrumentację funkcji I/O
Niektóre języki zawierają wbudowane wsparcie dla takich
pomiarów – np. C#, Java (pamięć)
wady profilerów

Instrumentacja i/lub proces przeprowadzania pomiarów
przeprowadzany przez profiler może zafałszowywać gromadzone
dane pomiarowe

Zbyt drobnoziarnista instumentacja spowoduje, że kod mierzący
stanie się właściwym hot spotem

Profilowanie w rzeczywistym świecie podlega „zasadzie
nieoznaczoności” (w mniejszym stopniu dotyczy to próbkowania, w
większym instrumentacji)!
narzędzia profilowania

Microsoft Visual Studio – wbudowany profiler używający
próbkowania CPU lub intrumentacji, również profiler pamięci w
środowisku .NET

Intel VTune – rozbudowany profiler Intela z funkcjami optymalizacji
kodu wielowątkowego (thread contention, deadlock detection)

AMD CodeAnalyst – podobny do VTune (free!)

gprof – GNU Profiler, opiera się na instrumentacji wspomaganej
przez kompilator (gcc), używaj z gcov dla śledzenia pogrycia kodu

Valgrid – profiler wykrywający również wycieki pamięci i mierzący
użycie pamięci (Unix)
narzędzia zapewnienia jakości

Statyczna lub dynamiczna analiza kodu w celu wykrycia
problemów poza tymi standardowo wykrywanymi przez kompilator

Przykłady

Wykrywanie wycieków pamięci w czasie wykonania

Sprawdzanie pokrycia kodu, wykrywanie martwego kodu
wykrywanie wycieków pamięci

Wycieki pamięci to powszechny problem w językach nie posiadających odśmiecacza
(Garbage Collector) – np. C/C++ – każdy zaalokowany fragment pamięci musi być
ręcznie zwolniony, inaczej jest bezpowrotnie tracony

Długo działający program z wyciekami ostatecznie „pożre” całą pamięć wirtualną
powodując spadek wydajności a potem wywalenie się

Narzędzia wykrywania wycieków przeprowadzają instrumentację funkcji alokacji pamięci
w celu śledzenia każdego fragmentu pamięci i określenia, które fragmenty „wyciekły”

Wycieki są też możliwe w językach z odśmiecaczem, ale są trudniejsze do wywołania (i
wykrycia) ;>

Wycieki mogą dotyczyć nie tylko pamięci,
ale również innych zasobów (uchwyty plików,
gniazda sieciowe, …)
narzędzia wykrywania i
zapobiegania wyciekom

Visual Leak Detector – biblioteka dla Visual C++ zastępująca
standardowe mechanizmy alokacji pamięci

Valgrind – narzędzie wykrywania wycieków i profiler dla Uniksa

ale również – używaj smart-pointerów dla zarządzania czasem życia
obiektów alokowanych na stercie!
narzędzia analizy i śledzenia
pokrycia kodu

Wykrywaj niedostępny (unreachable) kod – martwy kod jest
symptomem pomyłek programisty

Pokrycie kodu jest miarą określającą jak duży procent kodu jest
objęty procedurami testowymi


Kod, który nigdy nie został uruchomiony dla przetestowania, nigdy nie
może być nazwany działającym
Narzędzia:

profilery (MSVC, VTune, CodeAnalyst)

gcov

wykrywają również mnóstwo innych problemów…