Windbg w akcji Ostatnimi czasu bawię się bardziej zaawansowanym (powiedzmy :-)) debuggowaniem przy użyciu WinDbg. Jak różne jest to od debuggowania w Visual Studio chyba nie trzeba tłumaczyć, ale jeśli ktoś nigdy nie uświadczył to zachęcam do zainstalowania i zobaczenia (link). Dzisiejszy wpis będzie bez C#, .NETów i innych wysokopoziomowych rzeczy. Jeśli od czasu do czasu lubisz pobawić się w niskopoziomowym kodem zapraszam. Wracając jednak do problemu…

Każdy kto bawił się w debuggowanie bez źródeł wie, że nie jest to łatwy kawałek chleba. Mało znaczące kawałki assemblera, brak nazw wywoływanych funkcji a zamiast tego call 450123. Czyż nie łatwiej byłoby nam analizować aplikację gdyby zamiast nieszczęsnego call 450123 widniałoby call ConvertToSth lub coś podobnego? Niestety my, w przeciwieństwie do maszyn, wolimy zapamiętywać nazwy niż ciągli liczb. Czy nie posiadając symboli możemy coś z tym zrobić?

Zastanawiałem się nad tym problemem przeglądając *ironia* bardzo kompletną dokumentację *ironia* windbg doszedłem do wniosku, że można a z pomocą przyjdzie nam funkcja AddSyntheticSymbol. Pozwala ona dodać sztuczny symbol dla danego adresu z przestrzeni danej aplikacji, którą właśnie debuggujemy.  Pierwsze co zrobiłem to spróbowałem znaleźć czy jakaś istniejąca komenda, która zrobiła by to dla mnie. Niestety – nie znalazłem jej na Common Windbg Commands. Prawdopodobnie jeśli takie polecenie istnieje nie jest ono takie common. Jeśli jednak znasz polecenie WinDbg, które potrafi dodać symbol dla danego adresu daj mi znać.

Dość tego bezproduktywnego pisania – przejdźmy do kodowania, ale wcześniej mała uwaga.
UWAGA: Na co dzień nie programuję w C/C++. Choć miałem przygodę z tymi językami – ekspertem w nich się nie czuję i na pewno nie powinieneś brać mojego kodu za przykład do naśladowania. Widzisz błąd w kodzie lub da się coś napisać lepiej? Pisz śmiało!

Rozszerzenia w WinDbg

WinDbg jak większość programów pozwala nam na rozszerzenie jego funkcjonalności za pomocą rozszerzeń/pluginów. Ten wpis nie będzie o tym jak zacząć ich pisanie – jeśli chcesz się tego dowiedzieć możesz przeczytać wpis napisanego przez Toby’iego Opfermana – Debug Tutorial Part 4: Writing WINDBG Extensions (i w ogóle cały kurs jeśli temat cię interesuje). Takie rozszerzenie to nic innego jak dllk’a z odpowiednimi metodami. Tak więc aby zacząć w miarę szybko możemy skorzystać z tego co juz jest przygotowane z tym tutorialu i zabrać się za wypełnianie metody naszym kodem.

ssymbol.add

Nasze rozszerzenie musi posiadać jakąś nazwę (oczywista oczywistość). Jako że, będziemy wykorzystywać metodę AddSyntheticSymbol postanowiłem nazwać ją ssymbol (syntheticsymbol jest za długa). Tak więc nazywamy nasz plik ssymbol a w nim dajemy metodę

/***********************************************************
* !add
*
* Purpose: WINDBG will call this API when the user types !ssymbol.add
*         
*
*  Parameters:
*    !ssymbol.add

*
*  Return Values:
*    N/A
*
***********************************************************/
DECLARE_API (add)
{
  //put code hear
}

Mamy naszą metodę oraz powyżej opisaliśmy jak chcielibyśmy jej użyć. Chcemy mieć możliwość tak jak w przypadku innych metod jako adres podać nie tylko wartość liczbową ale także wyrażenie np. ssymbol esp GetStringValue i chcielibyśmy aby esp zamieniło się na wartośc. Na szczęście nie musimy robić tego sami gdyż jest dostępna metoda GetExpression (oraz GetExpressionEx) – dzięki temu uzyskamy adres, który można zapisać w zmiennej.  Args jest argumentem, który otrzymujemy dzięki DECLARE_API.

DECLARE_API (add)
{
    ULONG64  GetAddress;
    PCSTR name; 
    name = new char[100];   
    GetExpressionEx(args, &GetAddress, &name);
}

Następnie spróbujmy dobrać się do naszej głównej metody – AddSyntheticSymbol. Jako, że jest metodą na interface (IDebugSymbols3) musimy posłużyć się COM (buuu :/).

    1hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    2if (FAILED(hr))
    3{
    4  dprintf(“Error: CoInitializeEx – 0x%08x\n”, hr);
    5  return;
    6}
    7hr = DebugCreate(IID_IDebugSymbols3, (void**)&pDebug);
    8if (FAILED(hr))
    9{     
   10  dprintf(“Error: DebugCreate(IID_IDebugSymbols3) – 0x%08x\n”, hr);
   11  return;
   12}
   13hr = pDebug->AddSyntheticSymbol(GetAddress, strlen(name)-1,   
                               name+1, DEBUG_ADDSYNTHSYM_DEFAULT, NULL);
   14if (FAILED(hr))
   15{
   16  dprintf(“Error: AddSyntheticSymbol – 0x%08x\n”, hr);
   17  return;
   18}

Z COM’ami za dużo do czynienia nie miałem tak więc trochę się wymęczyłem starając się napisać ten kod (i jakby coś było nie tak to także piszcie). Prześledźmy ten kod krok po kroku. Pracując z jakimkolwiek obiektem COM zawsze musimy zainicjalizować całą tą maszynkę a robimy to właśnie za pomocą CoInitializeEx(lub ConInitialize) – bez tego wszystkie wołania nie powiodą się. Z utworzeniem interface’ów IDebugClient oraz IDebugSymbols3. Na początku próbowałem uzyskać je za pomocą zwykłego CoCreateInstance ale wszystkie wywołania kończyły się błędem 0x80040154 – Class not registered. Ehh…COM. Przeglądając jeszcze helpa znalazłem funkcję, która wyglądała na coś co możemy użyć do stworzenia interface’u COM. Dzięki niej możemy stworzyć obiekt typu IDebugSymbol i na nim wywołać naszą metodę dodającą symbol. Pierwszym parametrem jest adres, który uzyskaliśmy z metody GetExpression, następnym długość symbolu (tu nie jestem do końca przekonany czy chodzi o długość łańcucha znaków) oraz samą nazwę. Dajemy +1, aby usunąć spację, która rozdziela argumenty.
Na zakończenie wystarczy po sobie posprzątać i gotowe.

pDebug->Release();
CoUninitialize();   
delete name;

Działanie

Zobaczmy jak działa nasze rozszerzenie. Aby dodatek został wczytany przez WinDbg wystarczy umieścić go w c:\windows. A jak sobie radzi w działaniu?

Poniżej kawałek kodu przed użyciem naszego dodatku:
windbg_before
Czy nie łatwiej byłoby nam się w tym połapać, gdyby zamiast call image00400000+0x5b71a widzieć coś bardziej przyjaznego? Wywołajmy nasze rozszerzenie:
!ssymbol.add 400000+0x5b71a GetStringExpr
windbg_after
Od razu czytelniej!

Epilog

Ten dodatek do Windbg na pewno nie jest kompletny i sporo mu jeszcze brakuje. Przydałaby się możliwość usuwania symboli, gdyż w przypadku gdy będziemy chcieli zmienić jakiś już nazwany, otrzymamy błąd. Funkcja zapisywania dodanych symboli do pliku i ich wczytywania przy kolejnej sesji lub przy przeładowaniu debuggowanego programu także byłaby przydatna. Sporo pracy do zrobienia jeszcze jest, ale pierwszy kroku już wykonany…
Zachęcam do pobierania dodatku oraz do komentowania.
Do pobrania – SyntheticSymbol.zip