概述:
Windows提供了一組供程序員使用的API,使用這些API,我們能夠建立或捆綁到已運行的程序上來對他們進行調試,能獲得程序的底層信息和調試信息.如果你原意的話,甚至可以對被調試程序進行任意的修改(用WritePRocessMemory).
先讓我們從一個有趣的小例子開始吧: 打開DELPHI,新建工程,然后雙擊主窗體,在主窗體的Create事件中寫下如下代碼.
procedure TForm1.FormCreate(Sender: TObject);
var
isDebuggerPresent: function:Boolean;
DllModule: THandle;
begin
DllModule := LoadLibrary('kernel32.dll');
isDebuggerPresent := GetProcAddress(DllModule, 'IsDebuggerPresent');
if isDebuggerPresent then
begin
MessageBox(self.Handle, '請不要調試我!', '抗議', MB_OK or MB_ICONASTERISK);
application.Terminate;
end;
end;
按F9運行,程序執行得并不順利,在彈出來一個抱怨你調試了它的窗口后就中止了.然后我們再在DELPHI的Projecs目錄下找到剛剛編釋出來的程序, 雙擊執行它,這次窗口就老老實實的出來了,這是怎么回事呢?
原來上面的isDebuggerPresent就是Win32調試API中的一員,它的作用是判斷調用它的進程是否在調試描述表下運行(也就是是否處于被調試狀態),另一方面也說明了DELPHI的調試器也是用Win32調試API實現的.這下對調試API有興趣了吧?那讓我們來繼續深入調試API的世界!
得到一個供調試的程序:
由于我們的程序要扮演調試器的角色,我們還必需要有一個供調試的程序.這個程序可以通過二種方法獲得:
1:使用DebugActiveProcess函數.
這個函數的定義是DebugActiveProcess(dwProcessID: DWord):Bool; stdcall, dwProcessID用于指定被調試的進程的標識符,如果函數調用成功返回TRUE,失敗返回FALSE.注意,如果是在NT/2000/XP上,如果目標進程是由一個安全描述器創建的,而該安全描述符使調試器沒有充分的訪問權,那么此函數的調用可能失敗.
2:使用CreateProcess函數.
這個函數的定義是CreateProcess(lpApplicationName: PChar; lpCommandLine: PChar;lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
lpCurrentDirectory: PChar; const lpStartupInfo: TStartupInfo;
var lpProcessInformation: TProcessInformation): BOOL; stdcall
由于篇幅原因,這兒就不詳解CreateProcess的每個參數的含義,具體請參考API大全,我們這兒只談如何創建一個被調試的進程.即設置dwCreationFlags參數,你可以指定DEBUG_PROCESS標志來建立一個被調試進程,同時被調試進程的子進程的調試信息也將通知我們的調試器.還可以指定DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS標志來表示只調試當前過程.
處理調試信息:
當我們用上面的方法之一打開了被調試的程序后,我們的程序應調用WaitForDebugEvent等待處理調試事件.它阻塞調用線程直到調試的事件發生.此函數的定義是:
WaitForDebugEvent(var lpDebugEvent: TDebugEvent; dwMilliseconds: DWORD): BOOL; stdcall;
其中lpDebugEvent結構將在調試事件發生時返回發生的調試事件信息.dwMilliseconds值指定函數等待調試事件的時間,以毫秒為單位,一般設為INFINITE,表示一直等待直到調試事件發生.
這有點于類似于一個消息循環.我們一般都會新建一個線程,在線程中使用DebugActiveProcess或CreateProcess得到一個供調試的程序,然后用一個循環調用WaitForDebugEvent來處理隨后發生的調試事件.至于為什么要在新的線程中處理呢?你不會想你的調試器一打開被調試程序后就一動也不能動了吧 ;-)
繼續運行被調試程序:
當調試事件發生后,被調試程序會被WINDOWS掛起,當我們處理完了調試事件后,還要讓被調試程序繼續運行,這就要用到ContinueDebugEvent函數,定義如下:
ContinueDebugEvent(dwProcessId, dwThreadId, dwContinueStatus: DWORD): BOOL; stdcall;
其中dwProcessID和dwThreadID是要被恢復的進程和線程ID,可以從lpDebugEvent結構中的dwProcessID和dwThreadID取得.dwContinueStatus是指明如何恢復線程,可能的取值有DBG_CONTINUE 和DBG_EXCEPTION_NOT_HANDLED,DBG_CONTINUE指明了如果被調試程序發生了異常,由調試器來處理異常.DBG_EXCEPTION_NOT_HANDLED則表示調試器不處理被調試程序的異常,由被調試程序的默認異常處理程序來處理異常.
下面是一個簡單的例子,實現了監視被調試程序的建立和退出.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{調試信息處理過程}
procedure DebugPro;
var
si: _STARTUPINFOA; {進程啟動信息}
pi: _PROCESS_INFORMATION; {進程信息}
Flage: DWORD;
DebugD: DEBUG_EVENT; {調試事件}
Rc: Boolean;
begin
{建立調試進程}
Flage := DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS;
GetStartupInfo(si); {初始化si結構,不然無法正常建立進程}
if not CreateProcess(nil, Pchar('C:/winnt/NOTEPAD.EXE C:/Boot.ini'), nil, nil,
False, Flage, nil, nil, si, pi) then
begin
MessageBox(Application.Handle, '建立被調試進程失敗', '!!!', MB_OK or MB_ICONERROR);
exit;
end;
while WaitForDebugEvent(DebugD, INFINITE) do
begin {根據事件代碼進行相應處理}
case DebugD.dwDebugEventCode of
EXIT_PROCESS_DEBUG_EVENT:
begin
MessageBox(Application.Handle, '被調試進程中止', '!!!', MB_OK or MB_ICONERROR);
Break;
end;
CREATE_PROCESS_DEBUG_EVENT:
MessageBox(Application.Handle, '被調試進程建立', '!!!', MB_OK or MB_ICONERROR);
EXCEPTION_DEBUG_EVENT:
begin
if (DebugD.Exception.ExceptionRecord.ExceptionCode <> EXCEPTION_SINGLE_STEP) and
(DebugD.Exception.ExceptionRecord.ExceptionCode <> EXCEPTION_BREAKPOINT) then
Rc := False {如果被調試程序產生了異常,讓它自己處理}
else
Rc := True;
end;
end;
if Rc then
ContinueDebugEvent(DebugD.dwProcessId, DebugD.dwThreadId,
DBG_CONTINUE)
else
ContinueDebugEvent(DebugD.dwProcessId, DebugD.dwThreadId,
DBG_EXCEPTION_NOT_HANDLED);
end;
CloseHandle(pi.hProcess);
Closehandle(pi.hThread);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
ThreadHandle, ThreadID: THandle;
begin
ThreadHandle := CreateThread(nil, 0, @DebugPro, nil, 0, ThreadID);
end;
end.
下一篇文章將祥細講解調試事件的處理和事件結構的含義.敬請關注.
新聞熱點
疑難解答