Rozdział XXX - Funkcje uruchomieniowe Część 3


Na tej lekcji będziemy kontynuować eksplorację funkcji uruchomieniowych zawartych w bibliotece Win32 API. W szczególności nauczymy się śledzić testowany proces.

 

obrazek

 

Załaduj {ten przykład}

 

Teoria

Jeśli już wcześniej korzystałeś z usług programu uruchomieniowego (debuggera), to powinieneś wiedzieć, co to jest śledzenie. Polega ono na tym, iż śledzony program zatrzymuje się po wykonaniu każdej instrukcji, co daje ci szansę sprawdzenia zawartości rejestrów oraz pamięci. Oficjalną nazwą śledzenia jest praca krokowa (single-stepping).

Sam mikroprocesor umożliwia tryb pracy krokowej. Ósmy bit rejestru znaczników jest nazywany znacznikiem pułapki (trap flag). Jeśli ten bitowy znacznik zostanie ustawiony, to mikroprocesor będzie pracował w trybie krokowym. Po każdej wykonanej instrukcji powstanie wyjątek uruchomieniowy (debug event). Po wygenerowaniu tego wyjątku bit pułapki jest automatycznie zerowany, co pozwala procedurze obsługi wyjątku wykonywać się w sposób ciągły.

Wykorzystując dostępne funkcje w bibliotece Win32 API możemy również nakazać wykonywanie testowanego procesu w trybie krokowym. Oto co należy wykonać w tym celu:

  1. Wywołaj GetThreadContext określając wartość CONTEXT_CONTROL w polu ContextFlags struktury CONTEXT, aby otrzymać zawartość rejestru znaczników.
  2. Ustaw bit pułapki w polu regFlag struktury CONTEXT.
  3. Wywołaj SetThreadContext, co spowoduje uaktualnienie zawartości zapamiętanych rejestrów wątku.
  4. Jak zwykle zaczekaj na wystąpienie zdarzeń uruchomieniowych. Testowany proces będzie wykonywany w trybie krokowym. Po każdej wykonanej instrukcji otrzymamy powiadomienie EXCEPTION_DEBUG_EVENT z wartością EXCEPTION_SINGLE_STEP w polu u.Exception.pExceptionRecord.ExceptionCode.
  5. Jeśli chcesz śledzić następną instrukcję, to ponownie musisz ustawić bit pułapki.

Przykład

Plik DEBUG4.ASM

 

.386

.MODEL FLAT, STDCALL

OPTION CASEMAP:NONE

INCLUDE    \masm32\include\windows.inc
INCLUDE    \masm32\include\kernel32.inc
INCLUDE    \masm32\include\comdlg32.inc
INCLUDE    \masm32\include\user32.inc
INCLUDELIB \masm32\lib\kernel32.lib
INCLUDELIB \masm32\lib\comdlg32.lib
INCLUDELIB \masm32\lib\user32.lib

.DATA

AppName          DB "Przykład uruchamiania nr 4", 0
ofn              OPENFILENAME <>
FilterString     DB "Pliki wykonywalne", 0, "*.exe", 0,
                    "Wszystkie pliki", 0, "*.*", 0, 0
ExitProc         DB "Testowany proces kończy pracę", 0Dh, 0Ah,
                    "Całkowita liczba wykonanych instrukcji : %lu", 0
TotalInstruction DD 0

.DATA?

buffer    DB 512 DUP(?)
startinfo STARTUPINFO <>
pi        PROCESS_INFORMATION <>
DBEvent   DEBUG_EVENT <>

ALIGN DWORD

context CONTEXT <>

.CODE

start:

    mov    ofn.lStructSize, SIZEOF ofn
    mov    ofn.lpstrFilter, OFFSET FilterString
    mov    ofn.lpstrFile, OFFSET buffer
    mov    ofn.nMaxFile, 512
    mov    ofn.Flags, OFN_FILEMUSTEXIST OR \
                      OFN_PATHMUSTEXIST OR OFN_LONGNAMES OR\
                      OFN_EXPLORER OR OFN_HIDEREADONLY
    INVOKE GetOpenFileName, ADDR ofn
    .IF eax==TRUE
        INVOKE GetStartupInfo, ADDR startinfo
        INVOKE CreateProcess, ADDR buffer, NULL,
                              NULL, NULL, FALSE,
                              DEBUG_PROCESS + DEBUG_ONLY_THIS_PROCESS,
                              NULL, NULL, ADDR startinfo, ADDR pi
        .WHILE TRUE
            INVOKE WaitForDebugEvent, ADDR DBEvent, INFINITE
            .IF DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
                INVOKE wsprintf, ADDR buffer, ADDR ExitProc,
                                 TotalInstruction
                INVOKE MessageBox, 0, ADDR buffer, ADDR AppName,
                                   MB_OK + MB_ICONINFORMATION
                .BREAK
            .ELSEIF DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
                .IF DBEvent.u.Exception.pExceptionRecord.ExceptionCode==\
                    EXCEPTION_BREAKPOINT
                    mov    context.ContextFlags, CONTEXT_CONTROL
                    INVOKE GetThreadContext, pi.hThread, ADDR context
                    or     context.regFlag, 100h
                    INVOKE SetThreadContext, pi.hThread, ADDR context
                    INVOKE ContinueDebugEvent, DBEvent.dwProcessId,
                                               DBEvent.dwThreadId,
                                               DBG_CONTINUE    
                    .CONTINUE
                .ELSEIF DBEvent.u.Exception.pExceptionRecord.ExceptionCode==\
                        EXCEPTION_SINGLE_STEP
                    inc    TotalInstruction
                    INVOKE GetThreadContext, pi.hThread, ADDR context
                    or     context.regFlag, 100h
                    INVOKE SetThreadContext, pi.hThread, ADDR context
                    INVOKE ContinueDebugEvent, DBEvent.dwProcessId,
                                               DBEvent.dwThreadId,
                                               DBG_CONTINUE    
                    .CONTINUE
                .ENDIF
            .ENDIF
            INVOKE ContinueDebugEvent, DBEvent.dwProcessId,
                                       DBEvent.dwThreadId,
                                       DBG_EXCEPTION_NOT_HANDLED
        .ENDW
    .ENDIF
    INVOKE CloseHandle, pi.hProcess
    INVOKE CloseHandle, pi.hThread
    INVOKE ExitProcess, 0

END start

Analiza

Program wyświetla na ekranie okno dialogowe otwierania pliku. Gdy użytkownik wybierze plik wykonywalny, to zostanie on załadowany do pamięci i uruchomiony w trybie krokowym przy jednoczesnym zliczaniu wszystkich instrukcji, aż zakończy swoje działanie.

 

    .ELSEIF DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
        .IF DBEvent.u.Exception.pExceptionRecord.ExceptionCode==\
            EXCEPTION_BREAKPOINT

 

Korzystamy z tej okazji do ustawienia testowanego procesu w tryb krokowy. Przypomnij sobie, iż system Windows wysyła powiadomienie EXCEPTION_BREAKPOINT tuż przed uruchomieniem pierwszej instrukcji w testowanym procesie.

 

    mov    context.ContextFlags, CONTEXT_CONTROL
    INVOKE GetThreadContext, pi.hThread, ADDR context

 

Wywołujemy funkcję GetThreadContext w celu wypełnienia struktury CONTEXT bieżącą zawartością rejestrów testowanego procesu. Szczególnie interesuje nas bieżąca zawartość rejestru znaczników mikroprocesora.

 

    or     context.regFlag, 100h

 

W obrazie rejestru znaczników ustawiamy bit pułapki (ósmy bit).

 

    INVOKE SetThreadContext, pi.hThread, ADDR context
    INVOKE ContinueDebugEvent, DBEvent.dwProcessId,
                               DBEvent.dwThreadId,
                               DBG_CONTINUE    
    .CONTINUE

 

Następnie wywołujemy SetThreadContext w celu zapisania zmienionych zawartości rejestrów w strukturze CONTEXT do kontekstu procesu i wywołujemy ContinueDebugEvent z ustawionym znacznikiem DBG_CONTINUE w celu wznowienia wykonania testowanego procesu.

 

    .ELSEIF DBEvent.u.Exception.pExceptionRecord.ExceptionCode==\
            EXCEPTION_SINGLE_STEP
        inc    TotalInstruction

 

Gdy zostanie wykonana instrukcja w testowanym procesie, otrzymamy od systemu Windows powiadomienie EXCEPTION_DEBUG_EVENT. Musimy sprawdzić zawartość u.Exception.pExceptionRecord.ExceptionCode. Jeśli wynosi ona EXCEPTION_SINGLE_STEP, to dane zdarzenie uruchomieniowe powstało w wyniku pracy krokowej. W takim przypadku zwiększamy o jeden zawartość zmiennej TotalInstruction, ponieważ wiemy, iż w testowanym procesie została wykonana dokładnie jedna instrukcja.

 

    INVOKE GetThreadContext, pi.hThread, ADDR context
    or     context.regFlag, 100h
    INVOKE SetThreadContext, pi.hThread, ADDR context
    INVOKE ContinueDebugEvent, DBEvent.dwProcessId,
                               DBEvent.dwThreadId,
                               DBG_CONTINUE    
    .CONTINUE

 

Ponieważ znacznik pułapki jest zerowany po wygenerowaniu wyjątku uruchomieniowego, musimy go ponownie ustawić, jeśli chcemy utrzymać w testowanym procesie tryb krokowy.

Uwaga: nie stosuj tego przykładu z dużym programem: śledzenie jest WOLNE. Możesz czekać nawet do 10 minut, zanim zakończy działanie testowany proces.

 

Autorem kursu jest Iczelion. Kurs programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną zgodą autora.
Tłumaczenie z języka angielskiego, opracowanie HTML i konwersję przykładów programów wykonał mgr Jerzy Wałaszek.


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe