psDooM - zarządzaj swoimi procesami!

Wpis zamieszczony o 17:18:06, 02 września 2007 - 58 komentarzy


psDooM to monitor systemowy dla systemów Linuksowych. Z użytkownikiem komunikuje się poprzez wygodny i ładny interfejs graficzny (screen obok). Posiada funkcjonalność programów takich jak 'ps', 'renice' oraz oczywiście 'kill' :-).

psDooM (strona projektu) to implementacja XDoom jako środowiska do zarządzania procesami. Twórca projektu David Koppenhofer, nudząc się zapewne w pracy, przeglądał kod źródłowy Doom'a i dopisał do niego kilka funkcji bazujących na wywołaniach systemowych. Dlatego też w psDooM każdy potworek to określony proces z przyporządkowanym PID'em oraz nazwą. Jak widać na screenie powyżej nasze procesy zbyt grzeczne nie są... 'xv' potrafi dać w kość.

Niestety, psDooM posiada wiele błędów. Jeden z takich opiszę. Wyobraźmy sobie wielkiego i groźnego Basha szarżującego na nas i zasypującego gradem kul. My, w panicznej ucieczce chowamy się za pierwszy z brzegu proces, który całkowitym przypadkiem nazywa się 'init'. Kule groźnego Basha dosięgnęły niewinnego 'inita'. Chyba nie trzeba pisać co się stało z systemem? :-)
Dodatkowo: w systemie z wieloma uruchomionymi procesami psDooM staje się symulatorem hipermarketu w dzień świąteczny. Dodatkowym problemem staje się szef.. Bo jak mu wytłumaczyć, że właśnie zarządzamy największym klastrem w firmie a nie gramy w DooM'a? :-)

Z plusów psDooM'a autor wymienia:

Cóż, nie można odebrać Davidowi dobrego poczucia humoru :-). psDooM to tylko ciekawostka i swoisty żart ze strony programisty//admina GNU/Linux. Pamiętajcie - używanie go w swoich systemach może prowadzić do niestabilności Twojego systemu. Zresztą oboje wiemy - po to go uruchamiasz ;-).

Oto strona, która opisuje sposób instalacji psDooM. A ja spadam. Chyba firefox-bin za bardzo urósł i się do Amaroka mi dobiera... EOT.

ENJOY! :-) ps. Niestety, psDooM to projekt zapomniany, David już się nim nie zajmuje. W chwili wolnego czasu chciałbym kontynuować jego pracę, dobrałem się do kodu źródłowego DooM'a (świetnie opisany!) oraz zaczynam rozumieć mechanizmy, które zaimplementował David. Może psDooM nie umrze - pomysł jest genialny! Zainteresowani?

Czytaj dalej : 58 komentarzy

Debug pamięci w Linuksie (pamięć od strony technicznej)

Wpis zamieszczony o 17:11:06, 15 czerwca 2007 - 5 komentarzy


Artykuł ten będzie traktował o debugingu pamięci operacyjnej w środowisku GNU/Linux. I w zasadzie nie jest przeznaczony dla ZU (choć starałem się opisać to w jak najprzystępniejszy sposób) :-), adresowany jest głównie do programistów oraz tzw. hackerów jądra - osób, które mają udział w procesie powstawania jądra systemu GNU/Linux. Na początek garść potrzebnej teorii.

Wszystkie programy korzystają z pamięci operacyjnej (nawet te, które pozornie nic nie robią lub też ich nie widzimy). Zła implementacja zarządzania pamięcią w programie skutkuje zazwyczaj poważnymi błędami, wyciekami oraz niestabilnym działaniem. Postaram opisać się pokrótce takie zachowania, przyczyny ich występowania oraz sposoby jak sobie z nimi radzić. No to lecimy!

Pamięć jest urządzeniem do przechowywania informacji, które pochodzą z uruchomionych//działających aplikacji. Proces zazwyczaj przechowywany jest w określonym, zaadresowanym przedziale pamięci (zapisujemy ten przydział komórki za pomocą systemu heksadecymalnego), może on jednak rezydować na dysku twardym (w swap'ie) w sytuacji gdy nie jest używany lub gdy system operacyjny nie potrafi zaalokować odpowiedniej ilości pamięci dla programu. Pamięć użytkownika (wydzielony dział pamięci operacyjnej, do którego użytkownik ma pełen dostęp) jest zarządzana na dwa sposoby - przez jądro samo w sobie oraz użycie funkcji wywołań pamięci takich jak malloc() i pochodne.

Pamięć jądra

Jądro systemu operacyjnego zarządza zapotrzebowaniem procesów na pamięć operacyjną. Gdy użytkownik uruchamia aplikację, kernel automagicznie alokuje przestrzeń adresową w pamięci użytkownika dla tego procesu. W kolejnym etapie sam proces rozpoczyna zarządzanie przydzieloną mu strukturą pamięci, adresując i dzieląc ją wedle danego schematu:

Pamięć użytkownika

Pamięć użytkownika składana jest na stosie (nie, nie palona jak w średniowieczu ;-)) w "polu walki". Miejsce to jest zarządzane przez funkcje malloc(), realloc(), free() oraz calloc(). Są one częścią biblioteki libc. Z ciekawostek warto wymienić fakt, iż użytkownik 'root' zawsze ma dostęp do pamięci użytkownika, użytkownik nigdy nie ma dostępu do przestrzeni adresowej 'roota' oraz żaden z nich nie potrafi wpisywać danych do przestrzeni adresowej jądra (za wyłączeniem specjalnych wywołań do systemu /proc, które konfigurujemy w czasie kompilacji jądra). Zatem bzdurą są opowiastki, jakoby to użytkownik wyrzucił dump'a z /proc/kmem i uzyskał informacje zastrzeżone. Chociaż, zdarzają się sytuacje, w których root musi przesłać coś bezpośrednio do kernela. To tzw. SysRq, o których pisałem już kiedyś. Ale to nie o tym mówić chciałem! Wróć!

Inne funkcje pamięci

W systemach GNU/Linux programy zwiększają swoje miejsce w pamięci w przekalkulowanych, wcześniej zdefiniowanych inkrementacjach, zazwyczaj o jedną stronę pamięci lub alokację do granicy obecnej strony. Gdy tylko stos będzie potrzebował więcej 'miejsca' niż obecnie zadeklarowała pamięć użytkownika (przy starcie procesu), wywołana jest funkcja systemowa brk(), która żądają więcej pamięci od kernela. Co ciekawe, ilość przydzielonej pamięci może być definiowana funkcją sbrk()!

Aby obejrzeć obecny stos oraz "pole walki", zobacz na /proc//maps konkretnego procesu. Ujrzysz, z jakich bibliotek korzysta program, ile każda z nich zabiera pamięci użytkownika, możesz podejrzeć adresację pamięci i popatrzeć jak działa stos (watch). Mniam ;-)

Struktura

Za każdym razem gdy malloc() alokuje pamięć - tak naprawdę alokuje jej więcej niż funkcja sbrk() kazała. "Memory routines" (czyli schematy zarządzania pamięcią) używają tej dodatkowej przestrzeni do dbania o stabilność procesu (swoisty zapętlony maintance). Aby zadeklarować dokładnie taką wartość pamięci, jaką potrzebujemy, użyjmy wywołania malloc_usable_space(). Paczka pamięci alokowanej jest zazwyczaj o 8 bitów większa, z tym musimy się już pogodzić.

A jak wygląda taka paczka? Struktura paczki (strony) jest odpowiednio ustalona. Na początku znajduje się pole zawierające wielkość poprzedniej strony, dalej kolejno wielkość obecnej strony, żądana pamięć (wraz z nadmiarem pamięci (wspomniane min. 8 bit)) i na końcu znowu wielkość strony. Ostatnie pole zawiera flagę, która wskazuje, czy system zarządzania pamięcią ma zająć się stroną od razu po zakończeniu poprzedniej (ciągłe dane).

Schemat zarządzania pamięcią w GNU/libc używa tzw. "koszyków" aby utrzymać strony pamięci w podobnych rozmiarach dla zwiększenia wydajności i zapobieżeniu fragmentacji pamięci (nieużywane, bardzo małe strony pamięci między wielkimi stronami nigdy nie zostałyby użyte (wymagałoby to przepisania praktycznie każdego obecnie używanego programu)). Obecne systemy zarządzania tym procesem są bardzo dobrze dobrane i wyważone, nie są jednak zorientowane na szybkość operacji (jedynie na bezpieczeństwo tejże). Tutaj nadal pozostaje pole do popisu dla hackerów jajka. I to ogromne!
i wreszcie:

Debug

Pamięć może sprawiać problemy. Np. przez użycie już zadeklarowanej przez proces pamięci w innym wątku//z odwołaniem do innej strony, bądź też próbą zwolnienia (free()) pamięci już zwolnionej. Choć zazwyczaj nie zaczyna to być problemem samym w sobie, zazwyczaj coś idzie źle gdy proces chce przejąć ponownie źle zaalokowany//zwolniony przydział pamięci. W rezultacie - adres jest używany dwukrotnie (pierwszy - ten "źle" wykonany, drugi - obecne odwołanie), co zazwyczaj powoduje zrzut pamięci (core dump) w sytuacji gdy adres zawiera wskaźniki do innych części pamięci lub offsety zadokowane statycznie.

Kolejnym znanym problemem jest "łażenie" po preambule strony pamięci. Proces potrafi dowolnie modyfikować pola strony, w ten sposób może też zastąpić całą preambułę strony. Zazwyczaj powoduje to całkowitą odmowę posłuszeństwa ze strony zarządcy pamięci i w czasie odwołań do danej strony - początek memory leaka. (wycieku pamięci)

Czasem nadpisywany jest właściwy stos danych - a to prowadzi do całkowitego zniszczenia danych. Użytkownik widzi taką sytuację na ekranie monitora zazwyczaj jako nagłe i nieoczekiwane zakończenie programu nad którym prowadził pracę z całkowitą stratą danych, którymi program owy zarządzał (Microsoft Word jest tutaj dobrym przykładem). Podobne zachowanie reprezentuje bardzo często Mozilla Firefox, Opera oraz OpenOffice. Zazwyczaj takie zniszczenie danych w stronie pamięci nie zakańcza procesu od razu. Użytkownik często zaczyna dostawać przeróżne, dziwne komunikaty (znane "pamięć nie może być read") bądź też zauważyć dziwne zachowanie programu (wolne działanie, wysyłanie na ekran dziwnych znaków, problemy z myszką). W takiej sytuacji ZU powinien natychmiast zamknąć program zapisując swoją pracę.

Podobnie, jeśli zapis nagłówka strony zostanie zniszczony, zarządca zwróci błąd na głównej pętli (co zaowocuje podobnym przypadkiem jak powyżej).

Użycie niezaalokowanej pamięci w "polu bitwy" także może spowodować błędy. Najczęstszą sytuacją, która ma miejsce, jest zapis do pamięci poza stosem, lecz ciągle w stronie pamięci. W zasadzie sytuacja taka nie powoduje błędu, dopóki nowo zaalokowana pamięć (np. poprzez jądro) nie odwoła się do wcześniej zadeklarowanej i zapisanej pamięci poza stosem.

Najczęstszym jednak błędem zarządcy pamięci jest moment, w którym program próbuje użyć pamięci poza obszarem zadeklarowanej dla danego procesu przestrzeni adresowej. Rezultatem takiej operacji jest uwielbiany przez programistów SIGSEGV (segmentation violation fault), a program automatycznie zrzuca pamięć (tzw. memory dump, core dump). Najprostszym sposobem zaimplikowania takiej sytuacji jest kompilacja i uruchomienie poniższego programu:

#include <stdio.h>
int main(){ printf(*"Hello World!"); return 0;}

ciekawostka: jest to najszybszy sposób edycji popularnego Hello World zważając na kryterium minimalnej odległości Levenshteina, aby wyrzucił on SIGSEGV. ;-)

Jednak najbardziej destruktywną sytuacją, która może się przydarzyć, jest sytuacja, w której cały stos pamięci w stronie jest uszkodzony. Program traci wówczas wszystkie zmienne lokalne, parametry, rejestry poprzednich ramek//stron i co najgorsze - zwraca adres komórki pamięci do stosu. W takim przypadku program staje się niemożliwy do debugowania, gdyż ramki są renderowane i przekazywane do samych siebie. Aby wykryć tego typu uszkodzenia pamięci należy użyć jednego z płatnych debuggerów, które potrafią zastąpić kod wykonywalny w samej binarce i już na etapie przetwarzania stosu sygnalizować błędy. Niestety, nigdy takich programów nie używałem i nie potrafię Wam polecić jednego, konkretnego.

A żeby sobie ułatwić życie:

memprof - debugujemy graficznie!

Mając dość bawienia się w gdb czy inne, konsolowe debuggery zechcemy może użyć jednego z narzędzi graficznych na licencji GNU/GPL. Takim narzędziem jest memprof. Korzysta on z wywołań gdb, które w odpowiedni sposób parsuje wyrzucając miłe dla oka komunikaty :-). Kontroluje on procesy poprzez BFD (binary file descriptor) i w zasadzie nie sprawia problemów. Interfejs graficzny oferuje poprzez bilbioteki GTK1.2 oraz GTK2.


I to by było w zasadzie tyle. Artykuł ten miał na celu otwarcie oczu programistom, którzy czasem podchodzą do swojego programu "ok, kompiluje się, działa, jest odporny na głupotę użytkownika!", po czym okazuje się, że za 1056 uruchomieniem na komputerze nasz kochany program się wywala.... Debugujcie swoje dzieła! Nie bądźmy jak Microsoft ;-)))

Bardzo był bym rad, gdy któryś ze zwykłych użytkowników Linuksa dzięki temu artowi zainteresował się głębiej jądrem i procesami. Hackerzy jądra ciągle są poszukiwani. :-)

I na koniec - dziękuję Tobie - za to że przeczytałeś ten artykuł i dobrnąłeś do końca. Zdaję sobie sprawę, że tematyka odbiega znacznie od tego, co robiłem na tym joggerze do tej pory. Cóż - w końcu to w założeniu miał być blog techniczny :-). W następnym cyklu - używanie gdb, czyli szybki tutorial krok po kroczku.

Enjoy!

Czytaj dalej : 5 komentarzy

LinkLift