Dans ce tutorial, nous allons continuer l'exploration des 'win32 debug API'. Et spécialement, nous allons voir comment tracer le debuggee.
Downloadez l'exemple.
Si vous avez déjà utilisé un débugger auparavant, vous êtes familiers avec le fait de tracer un autre programme grâce à lui. Quand vous "tracez" un programme, les Break Points sur n'importe quelle instruction, vous donne la chance d'examiner les valeurs des Registres et des mémoires. Le fait de tracer un programme, on appelle ça officiellement 'Single-stepping'.
Plus concrètement, tracer un programme c'est exécuter une ligne d'instruction du programme cible puis voir ce que ça produit,( ou bien regarder les valeurs des Registres ou des mémoires qui viennent de changer) puis passer à la ligne suivante et encore voir ce qu'elle fait et ainsi de suite pour comprendre comment fonctionne le programme. Seul un debugger est capable de faire ça.
Le traçage pas à pas (ou 'Single-stepping') a la particularité d'être générer pas le CPU lui-même. (Le CPU, 'Central Processing Unit' ou 'Unité Centrale de Traitement'). Le 8ème bit du flag 'REGISTER' est appelé le Trap Flag. Si ce flag est mis à 1, le CPU fonctionne en mode pas à pas ou traçage, c'est pareil, enfin bon en 'Single-stepping'. Le CPU produira un 'debugging événement' de type 'exception' (voir tut28) pour le stopper après chaque instruction. Après que cette 'exception' se soit produite, le 'Trap Flag' est automatiquement purifié.
Nous pouvons aussi tracer le debuggee, en employant les win32 debug API. Les étapes sont les suivantes :
.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 "Win32 Debug Example no.4",0
ofn OPENFILENAME <>
FilterString db "Executable Files",0,"*.exe",0
db
"All Files",0,"*.*",0,0
ExitProc db "The debuggee exits",0Dh,0Ah
db "Total Instructions executed
: %lu",0
TotalInstruction dd 0
.data?
buffer db 512 dup(?)
startinfo STARTUPINFO <>
pi PROCESS_INFORMATION <>
DBEvent DEBUG_EVENT <>
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
Ce programme ouvre une boîte de dialogue openfile. Quand l'utilisateur choisit un fichier exécutable, il exécute le programme cible en mode single-step (tracage pas à pas), et il compte le nombre d'instructions exécutées jusqu'à la sortie de ce programme cible.
.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
.if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
Nous en profitons pour faire fonctionner le programme cible en mode single-step. Souvenez-vous que Windows envoie un EXCEPTION_BREAKPOINT juste avant qu'il n'exécute la première instruction du prog cible.
mov context.ContextFlags, CONTEXT_CONTROL
invoke
GetThreadContext, pi.hThread, addr context
On appelle GetThreadContext pour remplir la structure CONTEXT avec les valeurs actuelles des Registres du debuggee. Plus spécifiquement, nous avons besoin de la valeur actuelle du flag REGISTER.
or context.regFlag,100h
Nous mettons le trap bit (le 8ème bit) à 1 dans l'image du flag REGISTER.
invoke SetThreadContext,pi.hThread, addr context
invoke
ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
.continue
Ainsi nous appelons la fonction SetThreadContext pour écrire par dessus les valueurs de la structure CONTEXT avec les nouvelles on appelle la fonction ContinueDebugEvent munie en plus du flag DBG_CONTINUE pour relancer le debuggee.
.elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP
inc
TotalInstruction
Quand une instruction est exécutée dans le debuggee, nous recevons le fameux 'debugging événement' (de type 'Exeption') EXCEPTION_DEBUG_EVENT. Nous devons examiner la valeur de u.Exception.pExceptionRecord.ExceptionCode. Si sa valeur est EXCEPTION_SINGLE_step, alors ce 'debugging événement' s'est produit à cause du mode Pas à Pas (single-step). Dans ce cas, nous pouvons incrémenter la variable TotalInstruction car nous savons qu'une seule et unique instruction vient d'être exécutée dans le programme cible.
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
Après que ce 'debugging exception' se soit produit on enlève (on purifie) le 'trap flag'. On doit remettre le trap flag de nouveau si on souhaite continuer en mode Pas à Pas (single-step).
Avertissement : n'employez pas l'exemple dans ce tutorial avec un grand programme : le traçage est LENT. Il vous faudrait attendre pendant dix minutes avant que vous ne puissiez fermer le debuggee.
[Iczelion's Win32 Assembly Homepage]