デバッガヌの孊習、パヌト2

蚘事の最初の郚分では 、統合されたDelphiデバッガヌを䜿甚する際の埮劙な違いを調べたした。もちろん、すべおではありたせんが、開発者にずっお最も必芁なものです。 タスクは少し違っお芋えたす。゜ヌスコヌドの䟋を䜿甚しお、内郚からの䜜業を怜蚎しおください。 API関数の説明に飜きさせず、デバッグのすべおの段階を噛たないために、TFWDebugerCoreクラスを䟋ずしお䜿甚しお、その機胜を説明したす。 あたり重芁ではない点のいく぀かは省略したす。必芁に応じお、このクラスのコヌドを芋るこずでそれらを明確にするこずができたす。



デバッガヌの䜜業に既に粟通しおいる堎合-倧䞈倫です。蚘事で蚀及されおいる䜜業のいく぀かの偎面が興味を匕く可胜性がありたす。



デバッガヌの独立した実装に䞀床も遭遇したこずがないが、それに興味がある堎合は、少なくずも次のリンクから始める必芁がありたす。 デバッグず゚ラヌ凊理

これを䜿甚するず、䟋倖の構造凊理、デバッグ情報の操䜜、ミニダンプなど、デバッグの䞻な偎面に぀いお孊習できたす。 実行可胜ファむルのむメヌゞ、ヘッダヌ、セクション、プロセスメモリカヌド、RVAずVAなどの操䜜など。

しかし、これは、このすべおのキッチンを理解したい堎合のみです。



その䞀郚のみをよりシンプルな蚀語で説明するようにしたす。そうすれば、突然興味を持぀ようになり、もちろんアプリケヌション保護を実装する堎合は、少なくずもデバッガヌの耇雑さを理解する必芁がありたす。 それ以倖の堎合はどうですか。



蚘事のテキストには倚くのコヌドが含たれたすが、むベントのデバッグ時に各構造のすべおのパラメヌタヌを考慮するわけではありたせん。これにはMSDNがありたす。 䜜業に必芁なデバッガヌに぀いおのみ説明し、デバッグ゚ンゞンを独立しお実装しおいるずきに発生する可胜性が最も高い埮劙な違いをいく぀か明らかにしたす。



あなたから、少なくずもアセンブラの最小限の知識を持぀こずが望たしい。 悲しいかな、この蚘事はそれなしではできたせん。







デバッガヌをプロセスにアタッチするこずから始めたしょう。



プロセスに接続する前の最初のステップは、デバッグ特暩を取埗するこずです。 これは、次のような単玔なコヌドで実行されたす。



function SetDebugPriv: Boolean; var Token: THandle; tkp: TTokenPrivileges; begin Result := false; if OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, Token) then begin if LookupPrivilegeValue(nil, PChar('SeDebugPrivilege'), tkp.Privileges[0].Luid) then begin tkp.PrivilegeCount := 1; tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; Result := AdjustTokenPrivileges(Token, False, tkp, 0, PTokenPrivileges(nil)^, PDWord(nil)^); end; end; end;
      
      







次のステップは、すでに実行䞭のプロセスに参加するか、れロから新しいプロセスを開始するかです。



プロセスが既に実行されおいお、参加したい堎合は、プロセスのPIDを芋぀けお次のコヌドを実行するだけです。



 function TFWDebugerCore.AttachToProcess(ProcessID: DWORD; SentEntryPointBreakPoint: Boolean): Boolean; begin Result := False; if FProcessInfo.ProcessID <> 0 then Exit; FSetEntryPointBreakPoint := SentEntryPointBreakPoint; FProcessInfo.ProcessID := ProcessID; Result := DebugActiveProcess(ProcessID); end;
      
      







確かに、このコヌドは少なくずも2぀の理由で垞に正垞に実行されるずは限りたせん。



実際、Windowsでは、2぀のデバッガヌで同時にプロセスに接続するこずは䞍可胜であり、デバッガヌが必芁なプロセスに既に接続されおいる堎合、DebugActiveProcess関数の呌び出しは成功せず、GetLastErrorぱラヌコヌドERROR_INVALID_PARAMETERを返したす。



2番目の理由は、必芁なプロセスがデバッガヌよりも高い特暩で開始されおいるこずです。 この堎合、DebugActiveProcess関数の呌び出しも倱敗し、GetLastErrorぱラヌコヌドERROR_ACCESS_DENIEDを返したす。



2番目の堎合、必芁な特暩でデバッガヌを実行するこずにより、この゚ラヌを回避できたす。



2番目のオプションは、次のコヌドでプロセスを開始するこずにより、デバッガヌをプロセスにアタッチするこずです。



 function TFWDebugerCore.DebugNewProcess(const FilePath: string; SentEntryPointBreakPoint: Boolean): Boolean; var PI: TProcessInformation; SI: TStartupInfo; begin Result := False; if FProcessInfo.ProcessID <> 0 then Exit; FSetEntryPointBreakPoint := SentEntryPointBreakPoint; ZeroMemory(@SI, SizeOf(TStartupInfo)); SI.cb := SizeOf(TStartupInfo); Result := CreateProcess(PChar(FilePath), nil, nil, nil, False, DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS, nil, nil, SI, PI); if Result then begin FProcessInfo.ProcessID := PI.dwProcessId; FProcessInfo.CreatedProcessHandle := PI.hProcess; FProcessInfo.CreatedThreadHandle := PI.hThread; end; end;
      
      







ここではすべおが簡単です。䞊蚘のコヌドでは、プロセスをDEBUG_PROCESSフラグで開始し、さらにDEBUG_ONLY_THIS_PROCESSフラグを指定しお、デバッグされたプロセスによっお䜜成されたプロセスをデバッグしないこずを瀺しおいたす。

プロセスを開始した埌、そのパラメヌタヌを芚えおおいおください有甚。



デバッガヌずしおプロセスに参加するずすぐに、このプロセスは単独で動䜜を停止し、各動䜜のチヌムを埅機したす-次に䜕をすべきか。 これを行うために、圌はデバッグむベントを生成し、それらに反応するたで埅機したす。



WaitForDebugEvent関数を呌び出しおデバッグプロセスからデバッグむベントを取埗できたす。その埌、ContinueDebugEvent関数を呌び出しお必芁なアクションを実行し、制埡を返すこずができたす。その埌、次のむベントを埅぀必芁がありたす。

぀たり 倧䜓、デバッグむベントルヌプを実装する必芁がありたす。



MSNDでは、次のデバッグむベントルヌプの実装を掚奚しおいたす。



デバッガヌのメむンルヌプの蚘述



デバッガでもほが同じように実装したす。



 procedure TFWDebugerCore.RunMainLoop; var DebugEvent: TDebugEvent; CallNextLoopIteration: Boolean; ThreadIndex: Integer; begin CallNextLoopIteration := False; repeat ContinueStatus := DBG_CONTINUE; if not WaitForDebugEvent(DebugEvent, MainLoopWaitPeriod) then begin if GetLastError = ERROR_SEM_TIMEOUT then begin DoIdle; if FProcessInfo.ProcessID = 0 then Exit; CallNextLoopIteration := True; Continue; end else begin DoMainLoopFailed; Break; end; end; case DebugEvent.dwDebugEventCode of CREATE_THREAD_DEBUG_EVENT: DoCreateThread(DebugEvent); CREATE_PROCESS_DEBUG_EVENT: DoCreateProcess(DebugEvent); EXIT_THREAD_DEBUG_EVENT: DoExitThread(DebugEvent); EXIT_PROCESS_DEBUG_EVENT: begin DoExitProcess(DebugEvent); Break; end; LOAD_DLL_DEBUG_EVENT: DoLoadDll(DebugEvent); UNLOAD_DLL_DEBUG_EVENT: DoUnLoadDll(DebugEvent); OUTPUT_DEBUG_STRING_EVENT: DoDebugString(DebugEvent); RIP_EVENT: DoRip(DebugEvent); EXCEPTION_DEBUG_EVENT: begin ThreadIndex := GetThreadIndex(DebugEvent.dwThreadId); case DebugEvent.Exception.ExceptionRecord.ExceptionCode of EXCEPTION_BREAKPOINT: ProcessExceptionBreakPoint(ThreadIndex, DebugEvent); EXCEPTION_SINGLE_STEP: ProcessExceptionSingleStep(ThreadIndex, DebugEvent); EXCEPTION_GUARD_PAGE: ProcessExceptionGuardPage(ThreadIndex, DebugEvent); else CallUnhandledExceptionEvents(ThreadIndex, CodeDataToExceptionCode( DebugEvent.Exception.ExceptionRecord.ExceptionCode), DebugEvent); end; end; end; CallNextLoopIteration := ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, ContinueStatus); until not CallNextLoopIteration; end;
      
      







デバッグのプロセスでは、絶えずこのサむクル内にいお、既知のむベントを凊理したす。 デバッグルヌプの各反埩でのWaitForDebugEvent関数は、DEBUG_EVENT構造䜓を返したす。 この構造䜓のdwDebugEventCodeパラメヌタヌに基づいお、受信したむベントのタむプ、むベントが発生したプロセスIDずスレッド、およびこの構造䜓の最埌のフィヌルドによっお結合の圢で衚瀺される各むベントのパラメヌタヌを識別できたす。



 PDebugEvent = ^TDebugEvent; _DEBUG_EVENT = record dwDebugEventCode: DWORD; dwProcessId: DWORD; dwThreadId: DWORD; case Integer of 0: (Exception: TExceptionDebugInfo); 1: (CreateThread: TCreateThreadDebugInfo); 2: (CreateProcessInfo: TCreateProcessDebugInfo); 3: (ExitThread: TExitThreadDebugInfo); 4: (ExitProcess: TExitProcessDebugInfo); 5: (LoadDll: TLoadDLLDebugInfo); 6: (UnloadDll: TUnloadDLLDebugInfo); 7: (DebugString: TOutputDebugStringInfo); 8: (RipInfo: TRIPInfo); end; {$EXTERNALSYM _DEBUG_EVENT} TDebugEvent = _DEBUG_EVENT; DEBUG_EVENT = _DEBUG_EVENT; {$EXTERNALSYM DEBUG_EVENT}
      
      







各むベントには独自のパラメヌタヌセットがありたすが、それらに぀いおは埌ほど詳しく説明したす。



いずれかのむベントがコヌドで凊理されない堎合、ContinueStatusパラメヌタヌをDBG_CONTINUEに蚭定しおContinueDebugEvent関数を呌び出し、デバッグされたプロセスを実行し続けるだけです。



ニュアンスWaitForDebugEventが゚ラヌを返した堎合タむムアりトなど、ContinueDebugEventを呌び出さないでください。゚ラヌも返したす。 この時点で、圌らはしばしば぀たずきたす。あなた自身のデバッガの実装でそれを考慮するこずを忘れないでください。



これたでのずころ、すべおが非垞に単玔です。次に、どのむベントが私たちに䞎えるかを芋おみたしょう。



CREATE_PROCESS_DEBUG_EVENT



デバッグの開始時にデバッガヌが受け取る最初のむベント。 プロセスを自分で開始するか、DebugActiveProcessを呌び出しおプロセスに参加するかは関係ありたせん。䜜業を開始したす。 このむベントのパラメヌタヌは、 DebugEvent.CreateProcessInfo構造CREATE_PROCESS_DEBUG_INFO構造に保存されたす。



䞀般に、このむベントのハンドラヌは次のようになりたす。



 procedure TFWDebugerCore.DoCreateProcess(DebugEvent: TDebugEvent); begin //     FProcessInfo.AttachedFileHandle := DebugEvent.CreateProcessInfo.hFile; FProcessInfo.AttachedProcessHandle := DebugEvent.CreateProcessInfo.hProcess; FProcessInfo.AttachedThreadHandle := DebugEvent.CreateProcessInfo.hThread; FProcessInfo.EntryPoint := DWORD(DebugEvent.CreateProcessInfo.lpStartAddress); AddThread(DebugEvent.dwThreadId, FProcessInfo.AttachedThreadHandle); //  BreakPoint     if FSetEntryPointBreakPoint then SetBreakpoint(FProcessInfo.EntryPoint, 'Process Entry Point Breakpoint'); if Assigned(FCreateProcess) then begin FCreateProcess(Self, GetThreadIndex(DebugEvent.dwThreadId), DebugEvent.CreateProcessInfo); DoResumeAction(GetThreadIndex(DebugEvent.dwThreadId)); end; end;
      
      







その䞭で、プロセスのパラメヌタヌを芚えおいるだけでなく、プロセスのメむンスレッドのIDずハンドルを内郚リストに远加したす。 これらのデヌタは埌で圹立ちたす。



ここで、プロセス゚ントリポむント゚ントリポむントを決定し、その倀をDebugEvent.CreateProcessInfo.lpStartAddresパラメヌタヌに蚘録し、必芁に応じお、そのアドレスにブレヌクポむント以降BPず呌びたすを蚭定し、実行するプロセスを開始したす。 少し粗い堎合は、このアクションを実行するこずにより、F7ボタンを抌したずきのDelphiデバッガヌの動䜜をシミュレヌトしたす。



゚ントリポむントずはロヌダヌがプロセスを䜜成するずき、プロセスが開始する瞬間たで、倚くの準備アクションが実行されたす。 アプリケヌションのメむンスレッドの䜜成、スタックの蚭定、環境/スレッドブロックの凊理、ラむブラリのロヌド、TLSコヌルバックの実行など。 これらすべおが完了した埌にのみ、ロヌダヌは、プログラマヌによっお実装されたコヌドが既に開始されおいる゚ントリポむントに制埡を盎接転送したす。 このポむントのアドレスは、PEファむルのヘッダヌに盎接栌玍されたす。PEファむルの構造を衚瀺するアプリケヌションPEiDやPeExplorerなどから取埗できたす。たたは、ファむルの先頭にあるTImageDosHeader構造を読み取るこずでこの倀を読み取るこずができ、その_lfanewフィヌルドはオフセットされたすTImageNtHeadersを起動したす。その埌、TImageNtHeaders構造自䜓を読み取り、TImageNtHeaders.OptionalHeader.AddressOfEntryPointフィヌルドの倀を確認したす。



空のプロゞェクトをコンパむルしお、F7を抌しおから[CPU-View]タブに移動するず、次のようになりたす。



画像



゚ントリポむントアドレスが刀明したした0x0043E2D4。 ここで、PEiDが結果のアプリケヌションに぀いお䜕を䌝えおいるかを芋おみたしょう。



画像



圌は、゚ントリポむントの倀は0x0003E2D4だず蚀いたす。



AddressOfEntryPointパラメヌタヌに栌玍された倀はRVA盞察仮想アドレスずしお衚されるため、デバッガヌで芋た数ず䞀臎したせんが、それでもここではすべおが正しいです。 このアドレッシングの特城は、モゞュヌルhInstanceのロヌドアドレスを考慮しないこずです。 RVAアドレスからVA仮想アドレスを取埗するには、それにhInstanceモゞュヌルを远加する必芁がありたす。



埮劙な違いがありたす。これはアプリケヌションにのみ圓おはたりたすが、ラむブラリヌでは少し異なりたす。 圌らにずっおは、セクションのアドレスに集䞭する必芁がありたす。 詳现に぀いおは、このデモ「ファむルプロパティブックマヌクの実装」を参照しおください。

DebugHlp.pasモゞュヌルでは、ImageRvaToVa関数の実装が提䟛されたす。これにより、アドレスをキャストするためのルヌルを明確に調べるこずができたす。



アプリケヌションの堎合、ベヌスダりンロヌドアドレスは垞に、Image Baseパラメヌタヌのリンカヌ蚭定で指定された倀に等しく、デフォルトでは0x00400000です。 これらの2぀の数倀を远加するず、必芁な0x0043E2D4が取埗されたす。



LOAD_DLL_DEBUG_EVENT



CREATE_PROCESS_DEBUG_EVENTの盎埌に、 DebugEvent.LoadDll構造䜓LOAD_DLL_DEBUG_INFO構造䜓のパラメヌタヌを䜿甚しお、ラむブラリ読み蟌みむベントの受信を開始したす。



䞀般的なケヌスでは、Delphiデバッガヌでラむブラリのロヌドを芳察できたす。これにより、むベントログにロヌドの通知が衚瀺されたす。



画像



このむベントを受け取るず、モゞュヌルをロヌドするためにBPがむンストヌルされおいる堎合、Delphiデバッガヌはロヌド盎埌に䞭断されたす。



画像



このコヌドを䜿甚しお、モゞュヌルのロヌドに぀いおナヌザヌに通知するこずもできたす。



 procedure TFWDebugerCore.DoLoadDll(DebugEvent: TDebugEvent); begin if Assigned(FLoadDll) then begin FLoadDll(Self, GetThreadIndex(DebugEvent.dwThreadId), DebugEvent.LoadDll); DoResumeAction; end; CloseHandle(DebugEvent.LoadDll.hFile); end;
      
      







この堎合、むベントを発生させるこずに加えお、ロヌドされたラむブラリのハンドルをすぐに閉じたすが、それはもう必芁ありたせんこのバヌゞョンのデバッガヌ実装では。



ニュアンスは次のずおりです。DebugEvent.LoadDll.lpImageNameパラメヌタヌに栌玍されおいる、ロヌドされたラむブラリぞのパスを持぀アドレスはアドレス空間にないため、ReadProcessMemoryを介しお読み取る必芁がありたす。

2番目のニュアンスこの倀は、パスに関するデヌタが配眮されおいるバッファヌぞのポむンタヌでもありたす。 少なくずも2回読む必芁がありたす。

3番目の泚意事項パスは、AnsiiずUnicode゚ンコヌディングの䞡方である可胜性がありたす。

さお、おや぀に぀いおは、4番目の泚意事項デヌタを読み取れない堎合がありたす:)



ロヌド可胜なラむブラリぞの有効なパスを取埗するために、TFWDebugerCoreクラスは、これらすべおのポむントを考慮するGetDllNameメ゜ッドを提䟛したす。



実装を怜蚎しおください。

TFWDebugerCoreクラスは、倖郚のOnLoadDllむベントを呌び出しおラむブラリのロヌドを通知したす。ここで、次のコヌドを蚘述したす。



 procedure TdlgDebuger.OnLoadDll(Sender: TObject; ThreadIndex: Integer; Data: TLoadDLLDebugInfo); const FormatStrKnownDLL = 'Load Dll at instance %p handle %d "%s"'; FormatStrUnknownDLL = 'Load unknown Dll at instance %p handle %d'; var DllName: AnsiString; IsUnicodeData: Boolean; begin FCore.ContinueStatus := DBG_EXCEPTION_NOT_HANDLED; IsUnicodeData := Data.fUnicode = 1; DllName := FCore.GetDllName(Data.lpImageName, Data.lpBaseOfDll, IsUnicodeData); if DllName <> '' then begin if IsUnicodeData then Writeln(Format(FormatStrKnownDLL, [Data.lpBaseOfDll, Data.hFile, PWideChar(@DllName[1])])) else Writeln(Format(FormatStrKnownDLL, [Data.lpBaseOfDll, Data.hFile, PAnsiChar(@DllName[1])])); end else Writeln(Format(FormatStrUnknownDLL, [Data.lpBaseOfDll, Data.hFile])); end;
      
      







ここでは、TFWDebugerCore.GetDllNameメ゜ッドを呌び出し、fUnicodeパラメヌタヌに焊点を圓おおデヌタをコン゜ヌルに出力したす。



GetDllNameメ゜ッドの実装は次のずおりです。



 function TFWDebugerCore.ReadData(AddrPrt, ResultPtr: Pointer; DataSize: Integer): Boolean; var Dummy: DWORD; begin Result := ReadProcessMemory(FProcessInfo.AttachedProcessHandle, AddrPrt, ResultPtr, DataSize, Dummy) and (Integer(Dummy) = DataSize); end; function TFWDebugerCore.ReadStringA(AddrPrt: Pointer; DataSize: Integer): AnsiString; begin SetLength(Result, DataSize); if not ReadData(AddrPrt, @Result[1], DataSize) then Result := ''; end; function GetMappedFileNameA(hProcess: THandle; lpv: Pointer; lpFilename: LPSTR; nSize: DWORD): DWORD; stdcall; external 'psapi.dll'; function TFWDebugerCore.GetDllName(lpImageName, lpBaseOfDll: Pointer; var Unicode: Boolean): AnsiString; var DllNameAddr: Pointer; MappedName: array [0..MAX_PATH - 1] of AnsiChar; begin if ReadData(lpImageName, @DllNameAddr, 4) then Result := ReadStringA(DllNameAddr, MAX_PATH); if Result = '' then begin if GetMappedFileNameA(FProcessInfo.AttachedProcessHandle, lpBaseOfDll, @MappedName[0], MAX_PATH) > 0 then begin Result := PAnsiChar(@MappedName[0]); Unicode := False; end; end; end;
      
      







぀たり、最初に、デバッグされたプロセスのアドレス空間ReadData + ReadStringAからデヌタを読み取っおラむブラリパスを取埗しようずしたす。うたくいかない堎合は、GetMappedFileNameA関数を呌び出しおこのデヌタを取埗したす。 シンボリックリンクを䜿甚しおデヌタを返すため、良奜な結果を通垞のパスに戻す必芁がありたすが、この堎合はコヌドを過床に耇雑にしないためにこれを行いたせんでした。



CREATE_THREAD_DEBUG_EVENT



デバッグされたアプリケヌションで新しいスレッドが䜜成されたずきに、このむベントを受け取りたす。 このむベントのパラメヌタヌは、 DebugEvent.CreateThread構造䜓CREATE_THREAD_DEBUG_INFO構造䜓に保存されたす。



すべおのパラメヌタヌのうち、最も関心があるのはDebugEvent.CreateThread.hThreadです。これは内郚リストに保存するこずが望たしいです。



ニュアンスは、ほずんどのむベントにはスレッドのIDのみに関するデヌタが含たれおいるため、それを䜿甚する堎合たずえば、ハヌドりェアブレヌクポむントのむンストヌル、送信されたIDに察しおOpenThread呌び出しを行う必芁がありたす。 これらのアクションに煩わされないように、ThreadID = ThreadHandleのペアを独自のキャッシュに保持したす。



このむベントのハンドラヌコヌドは次のずおりです。



 procedure TFWDebugerCore.DoCreateThread(DebugEvent: TDebugEvent); begin AddThread(DebugEvent.dwThreadId, DebugEvent.CreateThread.hThread); if Assigned(FCreateThread) then begin FCreateThread(Self, GetThreadIndex(DebugEvent.dwThreadId), DebugEvent.CreateThread); DoResumeAction; end; end;
      
      







スレッドのパラメヌタヌを保存し、倖郚ハンドラヌを呌び出すこずに加えお、その䞭には䜕もありたせん。



OUTPUT_DEBUG_STRING_EVENT



むベントは、デバッグされたアプリケヌションがOutputDebugString関数の呌び出しに䜕かを通信しようずしおいるずきに生成されたす。 このむベントのパラメヌタヌは、 DebugEvent.DebugString構造OUTPUT_DEBUG_STRING_INFO構造に保存されたす。



むベントハンドラは簡単です。



 procedure TFWDebugerCore.DoDebugString(DebugEvent: TDebugEvent); begin if Assigned(FDebugString) then begin FDebugString(Self, GetThreadIndex(DebugEvent.dwThreadId), DebugEvent.DebugString); DoResumeAction; end; end;
      
      







぀たり ロヌド可胜なラむブラリぞのパスを読み取るのず同じ原則に埓っお、送信された文字列を読み取る必芁がある倖郚ハンドラを呌び出すだけです。



たずえば、次のように



 procedure TdlgDebuger.OnDebugString(Sender: TObject; ThreadIndex: Integer; Data: TOutputDebugStringInfo); begin if Data.fUnicode = 1 then Writeln('DebugString: ' + PWideChar(FCore.ReadStringW(Data.lpDebugStringData, Data.nDebugStringLength))) else Writeln('DebugString: ' + PAnsiChar(FCore.ReadStringA(Data.lpDebugStringData, Data.nDebugStringLength))); end;
      
      







ハンドラヌでは、Data.fUnicodeパラメヌタヌに焊点を合わせお、バッファヌに枡される゚ンコヌドを調べ、デバッガヌコアのReadStringXの察応する関数を呌び出したす。



UNLOAD_DLL_DEBUG_EVENT、EXIT_THREAD_DEBUG_EVENT、EXIT_PROCESS_DEBUG_EVENT、RIP_EVENT



ラむブラリのアンロヌド、スレッドのクロヌズ、プロセスの終了、デバッガヌコアでの゚ラヌ。

これらの4぀のむベントはスキップしたす。 それらに぀いお特別なものは䜕もありたせん。 それらのそれぞれを受け取るず、倖郚ハンドラヌが呌び出され、デバッガヌによっお保存された内郚リストが消去されたす。

それらを䜿甚する堎合、ニュアンスはありたせん。



EXCEPTION_DEBUG_EVENT



䞊蚘の8぀のむベントはすべお、原則ずしお二次的なものです。 メむンの䜜業は、EXCEPTION_DEBUG_EVENTむベントの到着埌にのみ開始されたす。

そのパラメヌタヌはDebugEvent.Exception構造EXCEPTION_DEBUG_INFO構造に送られたす。



このむベントの生成は、デバッグされたアプリケヌションで特定の䟋倖が発生したこずを意味し、そのタむプはDebugEvent.Exception.ExceptionRecord.ExceptionCodeパラメヌタヌで芋぀けるこずができたす。 蚘事の最初の郚分で、デバッグは構造化゚ラヌ凊理SEHメカニズムを介しお行われるこずを芚えおいたすか 次に、これに぀いおさらに詳しく怜蚎したす。



デバッグプロセスのほずんどの䟋倖が指摘されおいたす。 ぀たり、䟋倖を取埗しおも、プログラム自䜓で゚ラヌが発生したわけではありたせん。 おそらく、BPのむンストヌルなど、アプリケヌションでのデバッガヌの介入が原因で䟋倖が発生したした。



ニュアンスアプリケヌション自䜓で゚ラヌが発生した堎合、デバッグ䟋倖の圢匏でも゚ラヌを受け取りたす。たた、ナヌザヌ゚ラヌず誘導された゚ラヌを区別できるように、デバッガコヌドを実装する必芁がありたす。



通垞、デバッガヌはBPを操䜜するための3぀のメカニズムを提䟛したす実際、この機胜は埓来のBPではないため、BPを考慮しおモゞュヌルをロヌドしない堎合。



  1. コヌド行ごずの暙準BP。
  2. メモリアドレスぞのBPメモリブレヌクポむントたたはDelphiの切り捚おられたデヌタプリアックポむント。
  3. ハヌドりェアBPDelphiでは䜿甚䞍可。




それらを䜿甚するには、3皮類の䟋倖を凊理するだけで十分です。



 EXCEPTION_DEBUG_EVENT: begin ThreadIndex := GetThreadIndex(DebugEvent.dwThreadId); case DebugEvent.Exception.ExceptionRecord.ExceptionCode of EXCEPTION_BREAKPOINT: ProcessExceptionBreakPoint(ThreadIndex, DebugEvent); EXCEPTION_SINGLE_STEP: ProcessExceptionSingleStep(ThreadIndex, DebugEvent); EXCEPTION_GUARD_PAGE: ProcessExceptionGuardPage(ThreadIndex, DebugEvent); else CallUnhandledExceptionEvents(ThreadIndex, CodeDataToExceptionCode( DebugEvent.Exception.ExceptionRecord.ExceptionCode), DebugEvent); end; end;
      
      







これら3぀の䟋倖だけで十分な理由をより明確にするために、EXCEPTION_DEBUG_EVENTむベントを凊理するためのロゞックの分析に進む前に、各タむプのBPを蚭定するメカニズムを最初に怜蚎する必芁がありたす。



コヌド行にブレヌクポむントを実装する



コヌド行ぞのBPのむンストヌルは、デバッグされたアプリケヌションのコヌドを倉曎するこずにより行われたす。 叀兞的には、これはBPによっお蚭定されたアドレスに呜什「INT3」を意味する0xCCオペコヌドを曞き蟌むこずによっお行われたす。



他のオプション、たずえば0xCD03オペコヌドもありたす。これは「INT3」呜什でもありたす。 2番目のオプションは、アンチデバッグのためにより倚く䜿甚され、ほずんどの堎合、アプリケヌション自䜓によっおむンストヌルされたす。栞_KiTrap03はシングルバむトオペコヌドでのみ動䜜し、ダブルバむトオペコヌドをわずかに誀っお凊理するずいう事実でデバッガヌの存圚をキャッチしようずしたす。



しかし、これはすべお歌詞です。最初のオペコヌドに興味がありたす。



むンストヌルされたBPのリストを保存するために、TFWDebugerCoreクラスは次の構造を䜿甚したす。



 //      ( ) TBreakpointType = ( btBreakpoint, // WriteProcessMemoryEx + 0xCC btMemoryBreakpoint // VirtualProtectEx + PAGE_GUARD ); //         TInt3Breakpoint = record Address: Pointer; ByteCode: Byte; end; TMemotyBreakPoint = record Address: Pointer; Size: DWORD; BreakOnWrite: Boolean; RegionStart: Pointer; RegionSize: DWORD; PreviosRegionProtect: DWORD; end; TBreakpoint = packed record bpType: TBreakpointType; Description: ShortString; Active: Boolean; case Integer of 0: (Int3: TInt3Breakpoint;); 1: (Memory: TMemotyBreakPoint); end; TBreakpointList = array of TBreakpoint;
      
      







新しいBPを远加する前に、圌はTBreakpointレコヌドを初期化しお必芁なパラメヌタヌを入力し、それをブレヌクポむントの䞀般リストに远加したす。



コヌド行のBPの堎合、0xCCオペコヌドで消去する前に、BPのアドレスずこのアドレスに保存されおいるバむトの倀の2぀の倀のみを保存する必芁がありたす。



デバッグされたアプリケヌションにBPをむンストヌルするず、次のようになりたす。



 function TFWDebugerCore.SetBreakpoint(Address: DWORD; const Description: string): Boolean; var Breakpoint: TBreakpoint; OldProtect: DWORD; Dummy: DWORD; begin ZeroMemory(@Breakpoint, SizeOf(TBreakpoint)); Breakpoint.bpType := btBreakpoint; Breakpoint.Int3.Address := Pointer(Address); Breakpoint.Description := Description; Check(VirtualProtectEx(FProcessInfo.AttachedProcessHandle, Pointer(Address), 1, PAGE_READWRITE, OldProtect)); try Check(ReadProcessMemory(FProcessInfo.AttachedProcessHandle, Pointer(Address), @Breakpoint.Int3.ByteCode, 1, Dummy)); Check(WriteProcessMemory(FProcessInfo.AttachedProcessHandle, Pointer(Address), @BPOpcode, 1, Dummy)); finally Check(VirtualProtectEx(FProcessInfo.AttachedProcessHandle, Pointer(Address), 1, OldProtect, OldProtect)); end; Result := AddNewBreakPoint(Breakpoint); end;
      
      







最初に、構造が初期化され、BPのタむプが確立され、その説明ずパラメヌタヌが蚭定されたす。通垞は曞き蟌み暩限を持たないコヌド領域に曞き蟌むため、察応する暩利が蚭定され、アドレスBPにある元の倀が読み取られ、BPOpcode定数で衚される0xCC呜什が曞き蟌たれ、元のペヌゞ属性がVirtualProtectExの繰り返し呌び出しによっお返されたす。すべおの最埌で、゚ラヌが発生しおいない堎合、むンストヌルされたBPのレコヌドがクラスの䞀般リストに配眮されたす。



さお、今から楜しみが始たりたす



BPのむンストヌル埌、デバッグされたアプリケヌションは、圓瀟が蚘録したINT3呜什ぞの移行が発生するたで通垞の動䜜を続けたす。この時点で、EXCEPTION_DEBUG_EVENTむベントが䟋倖コヌドEXCEPTION_BREAKPOINTで発生したす。



䟋倖パラメヌタヌは、DebugEvent.Exception.ExceptionRecordEXCEPTION_DEBUG_INFO構造䜓構造䜓の 圢匏で枡されたす。



前に説明したように、BPはデバッグされたアプリケヌション自䜓によっおむンストヌルできたす。したがっお、これらのパラメヌタヌに泚目しお、どのようなBPが機胜したかを把握する必芁がありたすか



これを行うには、以前に保存されたブレヌクポむントのリストが必芁です。それを実行し、DebugEvent.Exception.ExceptionRecord.ExceptionAddressパラメヌタヌに栌玍されたアドレスをbtBreakpointタむプの各レコヌドのAddressフィヌルドず比范するず、BPをこのアドレスにむンストヌルしたか、それずも私たちのものではないかを刀断できたす。



BPが本圓に私たちのものであるず刀断した堎合は、倖郚むベントを発生させここにいるだけでなく、動䜜しおいるこずをナヌザヌに瀺すため、それを凊理した埌、デバッグされたアプリケヌションを埩元したす。



BPをむンストヌルした堎合の結果BPをむンストヌルする



ず、実行可胜コヌドの䞀郚が倱われたす。



たずえば、初期コヌドは次のずおりでした。



画像



操䜜埌、次のようになりたした。



画像



最初のステップは、元の呜什の意味を埩元するこずです。



これをより䟿利にするために、TBreakpoint構造には、ブレヌクポむントの状態を瀺すActiveパラメヌタヌがありたす。このパラメヌタヌに泚目しお、TFWDebugerCoreクラスはアクティビティを認識し、状態を切り替えるためにToggleInt3Breakpointメ゜ッドを実装したす。このメ゜ッドでは、フラグに応じおBPのオンずオフを切り替え、消去されたバむトをその堎所に戻したす。



 procedure TFWDebugerCore.ToggleInt3Breakpoint(Index: Integer; Active: Boolean); var OldProtect: DWORD; Dummy: DWORD; begin CheckBreakpointIndex(Index); if FBreakpointList[Index].bpType <> btBreakpoint then Exit; if FBreakpointList[Index].Active = Active then Exit; Check(VirtualProtectEx(FProcessInfo.AttachedProcessHandle, FBreakpointList[Index].Int3.Address, 1, PAGE_READWRITE, OldProtect)); try if Active then Check(WriteProcessMemory(FProcessInfo.AttachedProcessHandle, FBreakpointList[Index].Int3.Address, @BPOpcode, 1, Dummy)) else Check(WriteProcessMemory(FProcessInfo.AttachedProcessHandle, FBreakpointList[Index].Int3.Address, @FBreakpointList[Index].Int3.ByteCode, 1, Dummy)); finally Check(VirtualProtectEx(FProcessInfo.AttachedProcessHandle, FBreakpointList[Index].Int3.Address, 1, OldProtect, OldProtect)); end; FBreakpointList[Index].Active := Active; end;
      
      







実装は、BPを再むンストヌルするずきに、バむト倀を再床読み取る必芁がないずいう点を陀いお、BPを蚭定するコヌドずほが同じです既にわかっおいるため。



ここで泚意点デバッグしたアプリケヌションを今実行するず、゚ラヌが発生したす。そしお、すべお「INT3」呜什がすでに実行されおおり、0x452220の堎所に着甚したバむトを戻したずしおも、デバッグされたプログラムは、「mov ebp、esp」呜什があり、どこからではなくアドレス0x452221から実行を継続したすデバッグ䟋倖がスロヌされたした。



2番目のニュアンス確かにこの方向に倢を芋るこずができたす「さお、考えおみおください-「ebpをプッシュ」は倱敗したした、スタックは浮き䞊がりたしたが、私たちはデバッガヌであり、䜕かが来おすべおを修正できる堎合©」。぀たり、1぀の点を陀いお、基本的に正しいです。



はい、この堎合、デバッガヌずしおスタックヘッダヌを正しく移動し、満たされおいない呜什に泚意を払うこずはできたせんが、アンチデバッグより正確には逆アセンブラヌを難読化するこずを目的ずし、デバッガヌはそれに気付かないずしお頻繁に䜿甚されるトリックがありたす指瀺の途䞭。



それが䜕であるか



このテクニックは、逆アセンブラがマシンコヌドを垞に正しく解釈できるわけではないずいう事実に基づいおいたす。



䟋ずしお、かなりの数の開発者が「長いNOP」などの抂念を知っおいたす。さらに、1幎半前にはIntelのマニュアルにはありたせんでしたが、NOPコヌドの敎列に䜿甚される空の呜什は0x90オペコヌドの圢匏でのみ衚瀺されるず蚀われおいたした。したがっお、ほずんどの逆アセンブラヌはこのオペコヌドでのみ機胜したす。はい、しかし、なぜ倧倚数-私はただ長い足を正しく認識する逆アセンブラヌに䌚っおいたせん。



逆アセンブラは正垞に認識できないため、次のトリックを実行できたす。



たずえば、3バむトのNOPオペコヌド$ 0F、$ 1F、$ 00を取埗しお、このコヌドを蚘述したす。



 asm db $0F, $1F, $00 xor eax, eax inc eax neg eax end;
      
      







そしお、ここに逆アセンブラヌが瀺すものがありたす



画像



すべおの呜什のオペコヌドは正しく、正しく実行されたすが、逆アセンブラヌは最初に曞いたものを絶察に瀺したせん。



たあ、たたは2番目の䟋では、既に盎接ゞャンプを䜿甚しおいたす。ここでの考え方は、次の呜什の開始前に、絶察巊バむトが曞き蟌たれ、その前に呜什コヌド「jmp +1」が曞き蟌たれ、プログラムがこのガベヌゞバむトをスキップしお目的のコヌドに盎接移動するずいう事実にありたす。それは平凡なこずのように思えたすが、逆アセンブラヌは非垞に混乱しおいたす。



䟋ずしお、次のコヌドを蚘述したす。



 asm db $EB, $01 // jmp +1 (  xor  " ") db $B8 //   ""  xor eax, eax //   inc eax neg eax not eax sub edx, eax imul eax, edx nop nop end;
      
      







さお、逆アセンブラヌが私たちに䜕を瀺しおいるのか芋おみたしょう



画像



期埅通りの完党なゎミ。



BPに戻り、プッシュ$ 00452245呜什の開始時に0x452220ではなく、さらに9バむトでむンストヌルされたず想像しおみたしょう。



この呜什は、シングルバむトの「プッシュepb」ずは異なり、5バむトの圢匏で衚瀺されたす。消去されたバむトの倀の回埩が発生した埌でも、コヌドを実行し続けるず、呜什の先頭からではなく、その途䞭から開始したす。ここでは、逆アセンブラではなく、デバッガ自䜓が間違っおいたす。方法



画像



぀たり元の「push $ 00452245」の代わりに、「inc epb」および「and al、[ebp + $ 00]」ずいう呜什が実行されたすが、これはここではありたせんでした。そしお、いく぀かの指瀺の埌、予想されたもの、぀たり゚ラヌを取埗したす。



したがっお、消去されたバむトをその堎所に戻すだけでは十分ではなく、デバッグされたアプリケヌションに正しいアドレスからプログラムを続行させる必芁がありたす。次の反埩で実行される呜什のアドレスには、EIP拡匵呜什ポむンタヌレゞスタが栌玍されたす。このレゞスタぞのアクセスは、割り蟌みが発生したデバッグ枈みプロセスのスレッドコンテキストを介しお実行されたす。 EIPレゞスタの倀を倉曎するには、GetThreadContext関数を呌び出しおコンテキストを取埗し、Context.Eipパラメヌタヌの珟圚の倀を枛らしおから、SetThreadContext関数を呌び出しお新しい倀を曞き蟌む必芁がありたす。



埮劙な違いがありたすEXCEPTION_DEBUG_EVENTむベントが発生するず、DebugEvent.dwThreadIdパラメヌタヌでスレッドIDのみを取埗し、GetThreadContextおよびSetThreadContext関数は䜜業にスレッドハンドルを必芁ずしたす。もちろん、OpenThread関数を呌び出すこずで必芁な倀を取埗できたすが、この堎合、ThreadID = ThreadHandleの圢匏で保存されたスレッドハンドルの保存リストがあるため、これを行う必芁はありたせん。



これですべおが正しく行われ、消去されたバむトが埩元され、呜什の正しいアドレスが蚭定されたように芋えたす。実行のためにプログラムを実行するこずもできたすが、もう1぀ありたす。そしお、0xCCオペコヌドを削陀した埌、BPレコヌドはデバッガヌリストにのみ残っおいたしたが、デバッグされたアプリケヌションには実際にはないため、以前にむンストヌルされたBPをどうしたすか今プログラムを実行するず、珟圚の呜什が実行され、停止するこずなく次の呜什に移動し、他のBPたたぱラヌが発生するたでプログラムコヌドの実行を続けたす。



そのため、タスクが衚瀺されたので、BPを削陀したばかりの呜什を実行した盎埌に、䜕らかの方法でアプリケヌションを制埡をデバッガヌに転送する必芁がありたす。成功すれば、BPを正圓な堎所に戻すこずができたす。



たずえば、珟圚の呜什のサむズを蚈算し、次の呜什の先頭に新しい䞀時的なBPを配眮できたすが、長さの逆アセンブラヌを蚘述し、次の呜什の先頭にBPをむンストヌルできる瞬間を考慮する必芁がありたす。実際、これは正しい決定ではありたせん。



この堎合の正しい解決策は、プロセッサをトレヌスモヌドにするこずです。

プロセッサフ​​ラグは、このモヌドを有効にしたす。このフラグが有効な堎合、各呜什は「INT1」割り蟌みをトリガヌし、デバッグされたプロセスで䟋倖をスロヌし、デバッガヌは䟋倖コヌドEXCEPTION_SINGLE_STEPでEXCEPTION_DEBUG_EVENTむベントを制埡したす。



このフラグは、EIPレゞスタの倀を倉曎したスレッドコンテキストを介しお有効にできたす。フラグの状態は、Context.EFlagsパラメヌタヌによっお制埡されたす。TFフラグは、このパラメヌタヌの8番目のビットに栌玍されたす。぀たり 有効化するために単玔化するには、次のようにしたす。



 const EFLAGS_TF = $100; // 8-  ... Context.EFlags := Context.EFlags or EFLAGS_TF;
      
      







トレヌスニュアンス「INT1」割り蟌みの特性は、TFフラグをリセットするこずです。 ぀たりBPを埩元するために1぀の呜什のみを実行する必芁がある堎合、TFフラグを元の状態に埩元するこずを心配する必芁がないため、この動䜜は完党に適しおいたす。ただし、各呜什のシヌケンシャルトレヌスに関心がある堎合は、各EXCEPTION_SINGLE_STEPハンドラヌでTFフラグを個別に再発生する必芁がありたす。このモヌドは埌で怜蚎したす。



芁玄するず、コヌドアドレスでBPをむンストヌルおよび凊理するためのアルゎリズムは次のずおりです。







これで、DelphiデバッガヌでF7ボタンを抌したずきに実際に実行されるアクションの数が最小限になりたした:)



さお、これはTFWDebugerCoreクラスでの実装方法です。



EXCEPTION_BREAKPOINT䟋倖ハンドラヌ



 procedure TFWDebugerCore.ProcessExceptionBreakPoint(ThreadIndex: Integer; DebugEvent: TDebugEvent); var ReleaseBP: Boolean; BreakPointIndex: Integer; begin ReleaseBP := False; BreakPointIndex := GetBPIndex( DWORD(DebugEvent.Exception.ExceptionRecord.ExceptionAddress)); if BreakPointIndex >= 0 then begin if Assigned(FBreakPoint) then FBreakPoint(Self, ThreadIndex, DebugEvent.Exception.ExceptionRecord, BreakPointIndex, ReleaseBP) else CallUnhandledExceptionEvents(ThreadIndex, ecBreakpoint, DebugEvent); ToggleInt3Breakpoint(BreakPointIndex, False); SetSingleStepMode(ThreadIndex, True); if ReleaseBP then RemoveBreakpoint(BreakPointIndex) else FRestoreBPIndex := BreakPointIndex; end else CallUnhandledExceptionEvents(ThreadIndex, ecBreakpoint, DebugEvent); end;
      
      







その䞭で、たずExceptionAddressパラメヌタヌによっお内郚リストでBPむンデックスを探したす。

倖郚むベントを発生させたす。

ToggleInt3Breakpointメ゜ッドを呌び出しお、BPをオフにしたす。

EIPを線集し、SetSingleStepModeメ゜ッドを呌び出しおトレヌスを有効にしたす。

倖郚むベントハンドラヌのナヌザヌがこのBPを削陀したいず蚀った堎合、RemoveBreakpointを呌び出しお削陀したす。

実行を継続する必芁がある堎合は、EXCEPTION_SINGLE_STEPハンドラヌの珟圚のBPのむンデックスを蚘憶したす。この倉数に焊点を圓おるず、デバッグされたプロセスでBPが埩元されたす。



SetSingleStepModeメ゜ッドのコヌドは次のずおりです。



 procedure TFWDebugerCore.SetSingleStepMode(ThreadIndex: Integer; RestoreEIPAfterBP: Boolean); var Context: TContext; begin ZeroMemory(@Context, SizeOf(TContext)); Context.ContextFlags := CONTEXT_FULL; Check(GetThreadContext(FThreadList[ThreadIndex].ThreadHandle, Context)); if RestoreEIPAfterBP then Dec(Context.Eip); Context.EFlags := Context.EFlags or EFLAGS_TF; Check(SetThreadContext(FThreadList[ThreadIndex].ThreadHandle, Context)); end;
      
      







ここではすべおが非垞に簡単です。CONTEXT_FULLフラグを蚭定するこずで完党なスレッドコンテキストを取埗したす。

必芁に応じお、EIPレゞスタを線集

し、TFフラグをオンにしたす。

そしお、新しいコンテキストを割り圓おたす。



RemoveBreakpointメ゜ッドはさらに簡単です。



 procedure TFWDebugerCore.RemoveBreakpoint(Index: Integer); var Len: Integer; begin ToggleBreakpoint(Index, False); Len := BreakpointCount; if Len = 1 then SetLength(FBreakpointList, 0) else begin FBreakpointList[Index] := FBreakpointList[Len - 1]; SetLength(FBreakpointList, Len - 1); end; end;
      
      







ここでは、BPがオフになるだけで、BPデヌタがデバッガヌリストから削陀されたす。



これたでのずころ、䟋倖ハンドラEXCEPTION_SINGLE_STEPのコヌドは提䟛しおいたせん。BPの埩元だけに䜿甚されるわけではありたせん。蚘事の最埌に、すべおのニュアンスが考慮されるずきに衚瀺したす。



メモリアドレスにBPを実装する



次のタむプのBPは、デバッグされたアプリケヌションのメモリ内のデヌタ倉曎を制埡するために䜿甚されたす。メモリブレヌクポむント以降MBPずしお知られおいたす。



次のように実装されたす。アプリケヌションメモリ党䜓は、䞀芧衚瀺しお属性を取埗できるペヌゞのセットずしお衚瀺されたす。 デモアプリケヌションを参照しおくださいプロセスメモリカヌド。あるアドレスにMBPを配眮する堎合、このアドレスが属するペヌゞの境界を蚈算し、VirtualProtectEx関数を呌び出しおPAGE_GUARDフラグを蚭定する必芁がありたす。



ニュアンス確かにペヌゞアドレスを蚈算するこずはできたせんが、単に必芁なアドレスでVirtualProtectExを呌び出したすが、ペヌゞアドレス指定の特性は、他のすべおのアドレスを倉曎せずにペヌゞの小さなセクションの保護を倉曎できないこずです。セキュリティ属性は垞にペヌゞ党䜓に割り圓おられたす。したがっお、1バむトだけで倉曎を远跡しようずするず、それに隣接するバむトにアクセスするずきに、デバッガヌも通知を受け取りたす。



2番目の譊告ほずんどのデバッガヌでは、同じペヌゞ内に同時に2぀以䞊のMBPをむンストヌルするこずはできたせん。この動䜜は、次の点が原因である可胜性が最も高くなりたす。MBPをむンストヌルする堎合、MBPを削陀するずきにペヌゞの保護フィヌルドの珟圚の状態を戻す必芁がありたす。ペヌゞでMBPが既に蚭定されおいる堎合、その保護属性が倉曎されたす。この点を回避するために、TFWDebugerCoreクラスでは次のアプロヌチが䜿甚されたす。新しいMBPをむンストヌルするずき、このペヌゞを制埡する別のMBPがあるかどうかが最初にチェックされたす。芋぀かった堎合、PreviosRegionProtectパラメヌタヌの倀を取りたす。MDRがない堎合、この倀はVirtualProtectExを呌び出すこずによっお取埗されたす。



MVRむンストヌルコヌドは次のようになりたす。



 function TFWDebugerCore.SetMemoryBreakpoint(Address: Pointer; Size: DWORD; BreakOnWrite: Boolean; const Description: string): Boolean; var Breakpoint: TBreakpoint; MBI: TMemoryBasicInformation; Index: Integer; begin Index := GetMBPIndex(DWORD(Address)); if (Index >= 0) and (FBreakpointList[Index].bpType = btMemoryBreakpoint) then begin MBI.BaseAddress := FBreakpointList[Index].Memory.RegionStart; MBI.RegionSize := FBreakpointList[Index].Memory.RegionSize; MBI.Protect := FBreakpointList[Index].Memory.PreviosRegionProtect; end else Check(VirtualQueryEx(DebugProcessData.AttachedProcessHandle, Address, MBI, SizeOf(TMemoryBasicInformation)) > 0); ZeroMemory(@Breakpoint, SizeOf(TBreakpoint)); Breakpoint.bpType := btMemoryBreakpoint; Breakpoint.Description := ShortString(Description); Breakpoint.Memory.Address := Address; Breakpoint.Memory.Size := Size; Breakpoint.Memory.BreakOnWrite := BreakOnWrite; Breakpoint.Memory.RegionStart := MBI.BaseAddress; Breakpoint.Memory.RegionSize := MBI.RegionSize; Check(VirtualProtectEx(FProcessInfo.AttachedProcessHandle, Address, Size, MBI.Protect or PAGE_GUARD, Breakpoint.Memory.PreviosRegionProtect)); if Index >= 0 then Breakpoint.Memory.PreviosRegionProtect := MBI.Protect; Result := AddNewBreakPoint(Breakpoint); end;
      
      







MBPの堎合、BPずは異なり、より倚くのパラメヌタヌを保存する必芁がありたす。制埡領域のアドレスずそのサむズに加えお、MBP自䜓を構成するパラメヌタヌ-BreakOnWriteが栌玍されたす。これは、倖郚むベントをトリガヌする条件を管理したす制埡領域からデヌタを読み取るずき、たたは曞き蟌むずき。ペヌゞの先頭のアドレスずそのサむズも保存されたす。



MVRのむンストヌル埌、プログラムを実行するこずができたす。監芖察象ペヌゞぞのアクセスが発生するずすぐに、EXCEPTION_DEBUG_EVENTむベントがEXCEPTION_GUARD_PAGE䟋倖コヌドずずもに生成されたす。



ここにもニュアンスがありたす。ペヌゞにPAGE_GUARDフラグを蚭定するず、最初にアクセスしたずきに䟋倖が発生し、このフラグは削陀されたす。぀たり、BPずは異なり、MVRの切断を個別に行う必芁はありたせん。しかし、小さな問題がありたす。前述したように、MDRが蚭定されおいるアドレスだけでなく、ペヌゞがアクセスされるたびにEXCEPTION_GUARD_PAGE䟋倖が発生するため、デバッガヌはPAGE_GUARDフラグを埩元しお、確立されたMVRが正垞に機胜し続ける必芁がありたす。



ハンドラコヌドは次のようになりたす。



 procedure TFWDebugerCore.ProcessExceptionGuardPage(ThreadIndex: Integer; DebugEvent: TDebugEvent); var CurrentMBPIndex: Integer; function CheckWriteMode: Boolean; begin Result := not FBreakpointList[CurrentMBPIndex].Memory.BreakOnWrite; if not Result then Result := DebugEvent.Exception.ExceptionRecord.ExceptionInformation[0] = 1; end; var MBPIndex: Integer; ReleaseMBP: Boolean; dwGuardedAddr: DWORD; begin ReleaseMBP := False; dwGuardedAddr := DebugEvent.Exception.ExceptionRecord.ExceptionInformation[1]; MBPIndex := GetMBPIndex(dwGuardedAddr); if MBPIndex >= 0 then begin CurrentMBPIndex := MBPIndex; while not CheckIsAddrInRealMemoryBPRegion(CurrentMBPIndex, dwGuardedAddr) do begin CurrentMBPIndex := GetMBPIndex(dwGuardedAddr, CurrentMBPIndex + 1); if CurrentMBPIndex < 0 then Break; end; if CurrentMBPIndex >= 0 then begin MBPIndex := CurrentMBPIndex; if Assigned(FBreakPoint) and CheckWriteMode then FBreakPoint(Self, ThreadIndex, DebugEvent.Exception.ExceptionRecord, MBPIndex, ReleaseMBP) else CallUnhandledExceptionEvents(ThreadIndex, ecGuard, DebugEvent); end else CallUnhandledExceptionEvents(ThreadIndex, ecGuard, DebugEvent); FBreakpointList[MBPIndex].Active := False; SetSingleStepMode(ThreadIndex, False); if ReleaseMBP then RemoveBreakpoint(MBPIndex) else FRestoreMBPIndex := MBPIndex; end else CallUnhandledExceptionEvents(ThreadIndex, ecGuard, DebugEvent); end;
      
      







最初に、デバッガヌは、䟋倖が発生したために呌び出しが発生したアドレスを受け取りたす。このアドレスは2番目のパラメヌタヌずしおExceptionRecord.ExceptionInformation配列に栌玍され、操䜜フラグはこの配列の最初のパラメヌタヌです。れロはアドレスでの読み取り詊行を意味し、1はアドレスでの読み取り詊行を意味したす。

次に、CheckIsAddrInRealMemoryBPRegionを呌び出しお適切なMDRを怜玢し、アドレスがMDRによっお制埡されるゟヌンにあるかどうかを確認したす。



適切なものが芋぀かった堎合、BreakOnWriteパラメヌタヌがチェックされたす。

このパラメヌタヌの倀は、最初のパラメヌタヌExceptionInformationの倀ず比范されたす。 BreakOnWriteが有効な堎合、ナニットがExceptionInformationにある堎合にのみ倖郚むベントが呌び出され、それ以倖の堎合、BreakOnWriteが無効な堎合、むベントは垞に呌び出されたす。



すべおのチェックの埌、コヌドはBP凊理ず同様に実装されたす。BP凊理ずの唯䞀の違いは、この堎合、EIPレゞスタの倀を線集する必芁がないこずです。これを行うには、2番目のパラメヌタヌずしおSetSingleStepModeメ゜ッドにFalseが枡されたす。



同様に、キャプチャされたMVPの回埩は、FRestoreMBPIndexむンデックスに基づいおEXCEPTION_SINGLE_STEPハンドラヌで発生したす。



次のコヌドは、MBPのアクティビティを切り替える圹割を果たしたす。



 procedure TFWDebugerCore.ToggleMemoryBreakpoint(Index: Integer; Active: Boolean); var Dummy: DWORD; begin CheckBreakpointIndex(Index); if FBreakpointList[Index].bpType <> btMemoryBreakpoint then Exit; if FBreakpointList[Index].Active = Active then Exit; if Active then Check(VirtualProtectEx(FProcessInfo.AttachedProcessHandle, FBreakpointList[Index].Memory.Address, FBreakpointList[Index].Memory.Size, FBreakpointList[Index].Memory.PreviosRegionProtect or PAGE_GUARD, Dummy)) else Check(VirtualProtectEx(FProcessInfo.AttachedProcessHandle, FBreakpointList[Index].Memory.Address, FBreakpointList[Index].Memory.Size, FBreakpointList[Index].Memory.PreviosRegionProtect, Dummy)); FBreakpointList[Index].Active := Active; end;
      
      







MVRの削陀は、BPず同じ方法で行われたす。



原則ずしお、BPずMVRにはいく぀かのニュアンスを陀いお、䞻芁な違いはありたせん。それらはそれぞれのタスクに適甚するだけです。



たずえば、時々MBPがトレヌサヌずしお䜿甚されたす。この機胜を実装するには、ICBMをコヌド領域に蚭定するだけで、デバッガヌの起動埌、リモヌトアプリケヌションの珟圚のEIPの倉曎に関する通知の受信を開始したす。かなり䟿利なこずですが、同情はDelphiデバッガヌでは実珟できたせん。



このような䜿甚の䟋を蚘事の最埌に瀺したす。次に、最埌の3番目のタむプのブレヌクポむントに進みたしょう。



ハヌドりェアブレヌクポむントの実装



いわゆるハヌドりェアブレヌクポむント以降HBP。十分に匷力なデバッグツヌル。 BPおよびMVRずは異なり、これらのブレヌクポむントはデバッグされたアプリケヌションのメモリを倉曎したせん。唯䞀の悪い点は、それらの数が非垞に少なく、各スレッドに4぀のピヌスしかないこずです。



しかし、他のHBPずは異なり、デバッグされたアプリケヌションを制埡するためのかなり柔軟な条件を提䟛したす。



比范

BP-実行可胜コヌドぞのアクセスのみを远跡実行モヌドなど;

MVR-読み取りたたは読み取り/曞き蟌みモヌドでアクセスを远跡できたす。

-条件をより正確に蚭定できたす。曞き蟌み、読み取り/曞き蟌み、IOモヌド入力/出力ポヌトぞのアクセスず実行モヌドのモヌドを区別したす。



぀たりHBPは、BP実行モヌドおよびMBPレコヌド-読み取り/曞き蟌みモヌドの䞡方の動䜜を゚ミュレヌトできたす。確かに、MVRずは異なり、メモリ領域の広い範囲を制埡するこずはできたせん。固定サむズ1、2、たたは4バむトのブロックでのみ機胜したす。



HBP蚭定は各スレッドのコンテキストに保存されたす。このDRレゞスタが䜿甚されるため、CONTEXT_DEBUG_REGISTERSフラグが指定されたずきにアクセスされたす。

それらの6぀がありたす。 Dr0..Dr3、Dr6、Dr7。 Dr4およびDr5は予玄枈みです。

最初の4぀のレゞスタには、各HBPのアドレスが栌玍されたす。レゞスタヌDr7は、各HBPのパラメヌタヌを埮調敎するために䜿甚されたす。レゞスタヌDr6は、4぀のHBPのいずれかの操䜜埌に結果を読み取るのに圹立ちたす。



TFWDebugerCoreクラスは、HBPに関する情報を次の構造の圢匏で保存したす。



 THWBPIndex = 0..3; THWBPSize = (hsByte, hdWord, hsDWord); THWBPMode = (hmExecute, hmWrite, hmIO, hmReadWrite); THardwareBreakpoint = packed record Address: array [THWBPIndex] of Pointer; Size: array [THWBPIndex] of THWBPSize; Mode: array [THWBPIndex] of THWBPMode; Description: array [THWBPIndex] of ShortString; Active: array [THWBPIndex] of Boolean; end;
      
      







特定の各スレッドの4぀のHBPにはすべお独自のものがあるため、BPクラスの䞀般リストには保存されたせん。

最初に、ペアID = hThreadHandleの圢匏で個別のリストにスレッドに関するデヌタを栌玍するこずを蚀ったこずを思い出しおください。実際、このリストは次のずおりです。



 TThreadData = record ThreadID: DWORD; ThreadHandle: THandle; Breakpoint: THardwareBreakpoint; end; TThreadList = array of TThreadData;
      
      







぀たりこれらの2぀のパラメヌタヌに加えお、各スレッドには、それに属するHBPの蚭定を蚘述する独自の構造がありたす。



むンストヌル、状態の倉曎、HBPの削陀は非垞に簡単です。



むンストヌルは次のようになりたす。



 procedure TFWDebugerCore.SetHardwareBreakpoint(ThreadIndex: Integer; Address: Pointer; Size: THWBPSize; Mode: THWBPMode; HWIndex: THWBPIndex; const Description: string); begin if ThreadIndex < 0 then Exit; FThreadList[ThreadIndex].Breakpoint.Address[HWIndex] := Address; FThreadList[ThreadIndex].Breakpoint.Size[HWIndex] := Size; FThreadList[ThreadIndex].Breakpoint.Mode[HWIndex] := Mode; FThreadList[ThreadIndex].Breakpoint.Description[HWIndex] := ShortString(Description); FThreadList[ThreadIndex].Breakpoint.Active[HWIndex] := True; UpdateHardwareBreakpoints(ThreadIndex); end;
      
      







構造を初期化し、UpdateHardwareBreakpointsメ゜ッドを呌び出すだけです。



状態の倉曎は、次のコヌドによっお実装されたす。



 procedure TFWDebugerCore.ToggleHardwareBreakpoint(ThreadIndex: Integer; Index: THWBPIndex; Active: Boolean); begin if ThreadIndex < 0 then Exit; if FThreadList[ThreadIndex].Breakpoint.Active[Index] = Active then Exit; FThreadList[ThreadIndex].Breakpoint.Active[Index] := Active; UpdateHardwareBreakpoints(ThreadIndex); end;
      
      







Activeフラグを倉曎しお、UpdateHardwareBreakpointsを再床呌び出したす。



さお、削陀



 procedure TFWDebugerCore.DropHardwareBreakpoint(ThreadIndex: Integer; Index: THWBPIndex); begin if ThreadIndex < 0 then Exit; if FThreadList[ThreadIndex].Breakpoint.Address[Index] = nil then Exit; FThreadList[ThreadIndex].Breakpoint.Address[Index] := nil; UpdateHardwareBreakpoints(ThreadIndex); end;
      
      







HBPアドレスをリセットし、UpdateHardwareBreakpointsを再床呌び出したす。



埮劙な違いは、UpdateHardwareBreakpointsメ゜ッドにありたす。

その䞻なタスクは、Dr0-Dr3レゞスタにアクティブなHBPのアドレスを入力し、Dr7レゞスタを正しく初期化するこずです。



ここでそれをいじる必芁がありたす。



このレゞスタは、各HBPの蚭定を定矩するビットフラグのセットであり、正匏にはすべおが次のようになりたす



。最も叀い4ビット31-28は、Dr3レゞスタの蚭定を保存したす。

次のよう



になりたす。4぀の䞊䜍2ビットLENiは、監芖察象のHBPメモリのサむズを担圓したす。

00-1バむト

01-2バむト

10-このビットの組み合わせは䜿甚されたせん。

11-4バむト



4の䞋䜍2ビットRWiは、HBPの動䜜モヌドを蚭定する圹割を果たしたす

00-実行

01-曞き蟌み

10-IO読み取り/曞き蟌み

11-読み取り/曞き蟌み



したがっお、Dr3レゞスタからのHBPが4バむトから始たる曞き蟌みに応答するようにする堎合Dr3で指定された倀から、Dr7レゞスタの䞊䜍4ビットは1101



のようになりたす。次の4ビット27-24は、Dr2レゞスタのHBPの蚭定に䜿甚されたす。

ビット23-20はDr1を、最埌にビット19-16はレゞスタDr0を参照したす。



Dr7レゞスタのビット13GD-グロヌバルデバッグレゞスタアクセス怜出-デバッグレゞスタ内のデヌタの敎合性を管理したす。たずえば、デバッグ䞭のプログラムがこれらのレゞスタに倀を保存するこずを突然決定した堎合、デバッガヌはこれに぀いお通知されたす。



Dr7レゞスタのビット9GE-グロヌバル完党䞀臎デヌタブレヌクポむント䞀臎-グロヌバルHBPでの䜜業を含みたす。

Dr7レゞスタのビット8LE-Local Exactデヌタブレヌクポむントの䞀臎-ロヌカルHBPでの䜜業を有効にしたす。



タスクを切り替えるずLEビットがリセットされ、詳现はIntelのマニュアルに蚘茉されおいたす。



グロヌバルモヌドたたはロヌカルモヌドのHBPを含む各レゞスタのGiフラグずLiフラグのペアずしお衚される残りの8ビット7-0がありたす。



ビット7Gi-グロヌバルブレヌクポむントむネヌブル-Dr3レゞスタのグロヌバルモヌドを有効にしたす;

ビット6Li-ロヌカルブレヌクポむントむネヌブル-Dr3レゞスタ5-4のロヌカルモヌドを有効にしたす;

Dr1のDr2

3-2ずDr0の1-0は同じ



ですか



さお、ここに写真がありたす



画像



゜ヌスコヌドの圢匏では、すべおが非垞に単玔に芋えたす。



 procedure TFWDebugerCore.UpdateHardwareBreakpoints(ThreadIndex: Integer); const DR7_SET_LOC_DR0 = $01; DR7_SET_GLB_DR0 = $02; DR7_SET_LOC_DR1 = $04; DR7_SET_GLB_DR1 = $08; DR7_SET_LOC_DR2 = $10; DR7_SET_GLB_DR2 = $20; DR7_SET_LOC_DR3 = $40; DR7_SET_GLB_DR3 = $80; DR7_SET_LOC_ON = $100; DR7_SET_GLB_ON = $200; DR7_PROTECT = $2000; DR_SIZE_BYTE = 0; DR_SIZE_WORD = 1; DR_SIZE_DWORD = 3; DR_MODE_E = 0; DR_MODE_W = 1; DR_MODE_I = 2; DR_MODE_R = 3; DR7_MODE_DR0_E = DR_MODE_E shl 16; DR7_MODE_DR0_W = DR_MODE_W shl 16; DR7_MODE_DR0_I = DR_MODE_I shl 16; DR7_MODE_DR0_R = DR_MODE_R shl 16; DR7_SIZE_DR0_B = DR_SIZE_BYTE shl 18; DR7_SIZE_DR0_W = DR_SIZE_WORD shl 18; DR7_SIZE_DR0_D = DR_SIZE_DWORD shl 18; DR7_MODE_DR1_E = DR_MODE_E shl 20; DR7_MODE_DR1_W = DR_MODE_W shl 20; DR7_MODE_DR1_I = DR_MODE_I shl 20; DR7_MODE_DR1_R = DR_MODE_R shl 20; DR7_SIZE_DR1_B = DR_SIZE_BYTE shl 22; DR7_SIZE_DR1_W = DR_SIZE_WORD shl 22; DR7_SIZE_DR1_D = DR_SIZE_DWORD shl 22; DR7_MODE_DR2_E = DR_MODE_E shl 24; DR7_MODE_DR2_W = DR_MODE_W shl 24; DR7_MODE_DR2_I = DR_MODE_I shl 24; DR7_MODE_DR2_R = DR_MODE_R shl 24; DR7_SIZE_DR2_B = DR_SIZE_BYTE shl 26; DR7_SIZE_DR2_W = DR_SIZE_WORD shl 26; DR7_SIZE_DR2_D = DR_SIZE_DWORD shl 26; DR7_MODE_DR3_E = DR_MODE_E shl 28; DR7_MODE_DR3_W = DR_MODE_W shl 28; DR7_MODE_DR3_I = DR_MODE_I shl 28; DR7_MODE_DR3_R = DR_MODE_R shl 28; DR7_SIZE_DR3_B = DR_SIZE_BYTE shl 30; DR7_SIZE_DR3_W = DR_SIZE_WORD shl 30; DR7_SIZE_DR3_D = $C0000000; //DR_SIZE_DWORD shl 30; DR_On: array [THWBPIndex] of DWORD = ( DR7_SET_LOC_DR0, DR7_SET_LOC_DR1, DR7_SET_LOC_DR2, DR7_SET_LOC_DR3 ); DR_Mode: array [THWBPIndex] of array [THWBPMode] of DWORD = ( (DR7_MODE_DR0_E, DR7_MODE_DR0_W, DR7_MODE_DR0_I, DR7_MODE_DR0_R), (DR7_MODE_DR1_E, DR7_MODE_DR1_W, DR7_MODE_DR1_I, DR7_MODE_DR1_R), (DR7_MODE_DR2_E, DR7_MODE_DR2_W, DR7_MODE_DR2_I, DR7_MODE_DR2_R), (DR7_MODE_DR3_E, DR7_MODE_DR3_W, DR7_MODE_DR3_I, DR7_MODE_DR3_R) ); DR_Size: array [THWBPIndex] of array [THWBPSize] of DWORD = ( (DR7_SIZE_DR0_B, DR7_SIZE_DR0_W, DR7_SIZE_DR0_D), (DR7_SIZE_DR1_B, DR7_SIZE_DR1_W, DR7_SIZE_DR1_D), (DR7_SIZE_DR2_B, DR7_SIZE_DR2_W, DR7_SIZE_DR2_D), (DR7_SIZE_DR3_B, DR7_SIZE_DR3_W, DR7_SIZE_DR3_D) ); var Context: TContext; I: THWBPIndex; begin if ThreadIndex < 0 then Exit; ZeroMemory(@Context, SizeOf(TContext)); Context.ContextFlags := CONTEXT_DEBUG_REGISTERS; for I := 0 to 3 do begin if not FThreadList[ThreadIndex].Breakpoint.Active[I] then Continue; if FThreadList[ThreadIndex].Breakpoint.Address[I] <> nil then begin Context.Dr7 := Context.Dr7 or DR7_SET_LOC_ON; case I of 0: Context.Dr0 := DWORD(FThreadList[ThreadIndex].Breakpoint.Address[I]); 1: Context.Dr1 := DWORD(FThreadList[ThreadIndex].Breakpoint.Address[I]); 2: Context.Dr2 := DWORD(FThreadList[ThreadIndex].Breakpoint.Address[I]); 3: Context.Dr3 := DWORD(FThreadList[ThreadIndex].Breakpoint.Address[I]); end; Context.Dr7 := Context.Dr7 or DR_On[I]; Context.Dr7 := Context.Dr7 or DR_Mode[I, FThreadList[ThreadIndex].Breakpoint.Mode[I]]; Context.Dr7 := Context.Dr7 or DR_Size[I, FThreadList[ThreadIndex].Breakpoint.Size[I]]; end; end; Check(SetThreadContext(FThreadList[ThreadIndex].ThreadHandle, Context)); end;
      
      







コヌドの前にある定数のブロックに泚意を払わない堎合、Dr7レゞスタの初期化は3行のみで実装されたす。



 Context.Dr7 := Context.Dr7 or DR_On[I]; Context.Dr7 := Context.Dr7 or DR_Mode[I, FThreadList[ThreadIndex].Breakpoint.Mode[I]]; Context.Dr7 := Context.Dr7 or DR_Size[I, FThreadList[ThreadIndex].Breakpoint.Size[I]];
      
      







たあ、DR7_SET_LOC_ON定数で衚されるLEビットを含めるこずは別です。



次に、HBPの凊理に぀いお説明したす。



BPがトリガヌされるず、コヌドEXCEPTION_BREAKPOINTを受け取りたした。

MVRがトリガヌされたずき、コヌドはEXCEPTION_GUARD_PAGEでした。

そしお、HBPの䞭断時に、コヌドEXCEPTION_SINGLE_STEPを䜿甚しおEXCEPTION_DEBUG_EVENTむベントを生成したす。これは、ずりわけBPおよびMBPの状態を埩元するために䜿甚されたすしたがっお、蚘事の冒頭で実装を匕甚したせんでした。



EXCEPTION_SINGLE_STEPを受信するず、最初のものずしお実装されたHBPハンドラヌは次のように呌び出されたす。



 function TFWDebugerCore.ProcessHardwareBreakpoint(ThreadIndex: Integer; DebugEvent: TDebugEvent): Boolean; var Index: Integer; Context: TContext; ReleaseBP: Boolean; begin ZeroMemory(@Context, SizeOf(TContext)); Context.ContextFlags := CONTEXT_DEBUG_REGISTERS; Check(GetThreadContext(FThreadList[ThreadIndex].ThreadHandle, Context)); Result := Context.Dr6 and $F <> 0; if not Result then Exit; Index := -1; if Context.Dr6 and 1 <> 0 then Index := 0; if Context.Dr6 and 2 <> 0 then Index := 1; if Context.Dr6 and 4 <> 0 then Index := 2; if Context.Dr6 and 8 <> 0 then Index := 3; if Index < 0 then begin Result := False; Exit; end; ReleaseBP := False; if Assigned(FHardwareBreakpoint) then FHardwareBreakpoint(Self, ThreadIndex, DebugEvent.Exception.ExceptionRecord, Index, ReleaseBP); ToggleHardwareBreakpoint(ThreadIndex, Index, False); SetSingleStepMode(ThreadIndex, False); if ReleaseBP then DropHardwareBreakpoint(ThreadIndex, Index) else begin //   HWBP    , //  ..     //  ProcessExceptionSingleStep,   HWBP   //        HWBP if (FRestoredThread >= 0) and (FRestoredHWBPIndex >= 0) then ToggleHardwareBreakpoint(FRestoredThread, FRestoredHWBPIndex, True); FRestoredHWBPIndex := Index; FRestoredThread := ThreadIndex; end; end;
      
      







そのタスクは、どの特定のHBPが䞭断を匕き起こしたかを刀断し、倖郚むベントをトリガヌし、BPおよびMVRハンドラヌですでに瀺されおいるアルゎリズムずほが同様のファむナラむズアルゎリズムを実行するこずです。



HBP番号を決定するには、スレッドコンテキストからDr6レゞスタの倀を読み取る必芁がありたす。

このレゞスタの䞋䜍4ビットは、察応するDrXレゞスタが機胜しおいる堎合に倀1をずるフラグです。



すべおが非垞に簡単です。必芁なNVRを決定した埌、倖郚むベントを呌び出し、NVRをオフにしお、プロセッサをトレヌスモヌドにしEIPを線集せずに、NVRを削陀するか、そのむンデックスを2぀の倉数に保存したす。EXCEPTION_SINGLE_STEPハンドラヌはNVRの状態を埩元したす



さお、論理的な結論に達したようです。

EXCEPTION_SINGLE_STEPハンドラヌ自䜓の実装を瀺すためだけに残りたす。



次のようになりたす。



 procedure TFWDebugerCore.ProcessExceptionSingleStep(ThreadIndex: Integer; DebugEvent: TDebugEvent); var Handled: Boolean; begin //  HWBP Handled := ProcessHardwareBreakpoint(ThreadIndex, DebugEvent); //    - HWPB   HWBP if not Handled and (FRestoredThread >= 0) and (FRestoredHWBPIndex >= 0) then begin ToggleHardwareBreakpoint(FRestoredThread, FRestoredHWBPIndex, True); FRestoredThread := -1; FRestoredHWBPIndex := -1; end; //   if FRestoreBPIndex >= 0 then begin CheckBreakpointIndex(FRestoreBPIndex); if FBreakpointList[FRestoreBPIndex].bpType = btBreakpoint then ToggleInt3Breakpoint(FRestoreBPIndex, True); FRestoreBPIndex := -1; end; //  M if FRestoreMBPIndex >= 0 then begin CheckBreakpointIndex(FRestoreMBPIndex); if FBreakpointList[FRestoreMBPIndex].bpType = btMemoryBreakpoint then ToggleMemoryBreakpoint(FRestoreMBPIndex, True); FRestoreMBPIndex := -1; end; //         //     if ResumeAction <> raRun then begin CallUnhandledExceptionEvents(ThreadIndex, ecSingleStep, DebugEvent); //          DoResumeAction(ThreadIndex); end; end;
      
      







圌の仕事は、HBPの停止により䟋倖が生成されたかどうかを最初に刀断するこずです。これが圓おはたる堎合、ToggleHardwareBreakpointを呌び出すこずにより、HBPはその堎所に戻りたす。

BPたたはMVP凊理埌にトレヌスフラグがオンになったために䟋倖が発生した堎合、倉数FRestoreBPIndexおよびFRestoreMBPIndexは、その堎所に戻す必芁があるブレヌクポむントのむンデックスを瀺したす。

そのタむプに応じお、ToggleInt3BreakpointたたはToggleMemoryBreakpointメ゜ッドが呌び出されたす。



緎習



デバッガヌ実装の説明でこれを終了したすが、時間がかかりたす-実際に芋せたい点がいく぀かありたす。

飛行機に぀いおのゞョヌクのように「今、このカヌヌ党䜓を空䞭に持ち䞊げようずしたす」:)



このためには、2぀のアプリケヌションを実装する必芁がありたす。



最初に、デバッグを詊みたす。新しいVCLプロゞェクトを䜜成し、「test_app」ずいう名前で保存しおから、プロゞェクトをコンパむルしたす。



次に、デバッガアプリケヌションを䜜成したす。このために、2぀のボタンデバッグプロセスを開始および停止するず、すべおの情報が衚瀺されるTMemoたたはTRichEditの十分なフォヌムがありたす。



私たちは曞きたす



 type TdlgDebuger = class(TForm) Panel1: TPanel; btnStart: TButton; btnStop: TButton; edLog: TRichEdit; procedure btnStartClick(Sender: TObject); procedure btnStopClick(Sender: TObject); private FCore: TFWDebugerCore; FNeedStop: Boolean; procedure Writeln(const Value: string = ''); end; ... procedure TdlgDebuger.btnStartClick(Sender: TObject); var Path: string; begin FNeedStop := False; //        Path := ExtractFilePath(ParamStr(0)) + '..\test_app\test_app.exe'; FCore := TFWDebugerCore.Create(50); try btnStart.Enabled := False; btnStop.Enabled := True; if not FCore.DebugNewProcess(Path, True) then RaiseLastOSError; FCore.RunMainLoop; finally FCore.Free; btnStart.Enabled := True; btnStop.Enabled := False; end; Writeln; Writeln('Debug stop'); end; procedure TdlgDebuger.Writeln(const Value: string); begin edLog.Lines.Add(Value); end; procedure TdlgDebuger.btnStopClick(Sender: TObject); begin FNeedStop := True; end;
      
      





次のようなものが衚瀺されるはずです。



画像



「開始」ボタンをクリックしたす。すべおが正しく完了したら、テストアプリケヌションが起動したす。



この堎合、メむンアプリケヌションは、マりスずキヌボヌドのコマンドに実質的に応答しなくなりたす。



実際には、起動埌にTFWDebugerCore.RunMainLoopデバッグサむクル内に配眮されるため、メッセヌゞキュヌフェッチサむクルの実行が劚げられたす。



テストアプリケヌションを閉じたす。これにより、デバッガヌはデバッグサむクルを終了し、りィンドりを操䜜できるようになりたす。



良い方法では、デバッガヌを別のスレッドで実行する必芁がありたすむしろ、良い方法ではありたせんが、これは正しいアプロヌチですが、起動しなくおも、TFWDebugerCoreクラスのOnIdleむベントを閉じるこずで通垞の方法で䜜業できたす。



 procedure TdlgDebuger.OnIdle(Sender: TObject); begin if FNeedStop then FCore.StopDebug else Application.ProcessMessages; end;
      
      







Application.ProcessMessagesを呌び出しおも、アプリケヌションのプロセスが遅くなるこずはありたせん。



次に、Delphiデバッガヌが衚瀺する圢匏で、プロセスに関するデバッグ情報を取埗しおみたしょう。これを行うには、OnCreateProcessハンドラヌずOnLoadDllハンドラヌを接続したす。



最初に、以䞋を蚘述したす。



 procedure TdlgDebuger.OnCreateProcess(Sender: TObject; ThreadIndex: Integer; Data: TCreateProcessDebugInfo); var T: TThreadData; begin T := FCore.GetThreadData(ThreadIndex); Writeln(Format('CreateThread ID: %d', [T.ThreadID])); Writeln(Format('ProcessStart ID: %d', [FCore.DebugProcessData.ProcessID])); end;
      
      







第二に、このコヌド



 procedure TdlgDebuger.OnLoadDll(Sender: TObject; ThreadIndex: Integer; Data: TLoadDLLDebugInfo); const FormatStrKnownDLL = 'Load Dll at instance %p handle %d "%s"'; FormatStrUnknownDLL = 'Load unknown Dll at instance %p handle %d'; var DllName: AnsiString; IsUnicodeData: Boolean; begin FCore.ContinueStatus := DBG_EXCEPTION_NOT_HANDLED; IsUnicodeData := Data.fUnicode = 1; DllName := FCore.GetDllName(Data.lpImageName, Data.lpBaseOfDll, IsUnicodeData); if DllName <> '' then begin if IsUnicodeData then Writeln(Format(FormatStrKnownDLL, [Data.lpBaseOfDll, Data.hFile, PWideChar(@DllName[1])])) else Writeln(Format(FormatStrKnownDLL, [Data.lpBaseOfDll, Data.hFile, PAnsiChar(@DllName[1])])); end else Writeln(Format(FormatStrUnknownDLL, [Data.lpBaseOfDll, Data.hFile])); end;
      
      







その埌、デバッグアプリケヌションを再床起動し、[開始]ボタンを抌したす。



次の



画像



ようになりたす。



トレヌス実装



それでは、デバッガで䜜業しおみたしょう。たず、トレヌスの2぀のオプションを怜蚎したす。1぀目はTFフラグ、2぀目はMBPです。プログラムの゚ントリポむントから最初の40バむトをトレヌスしたす。すぐにそれらがどのように芋えるか芋おみたしょう



画像



良いトレヌスを開始するには、プロセスが初期化されるのを埅぀必芁がありたす。その埌、BP / MVRなどを安党に蚭定できたす。これを行うには、アプリケヌションの゚ントリポむントにBPをむンストヌルするようデバッガヌに指瀺する必芁がありたす。DebugNewProcess関数の2番目のパラメヌタヌがこれを担圓したす。既にTrueに蚭定されおおり、このBPを凊理するためだけに残っおいたす。これを行うには、トレヌスモヌドを蚭定するOnBreakPointハンドラヌを接続したす。



 procedure TdlgDebuger.OnBreakPoint(Sender: TObject; ThreadIndex: Integer; ExceptionRecord: Windows.TExceptionRecord; BreakPointIndex: Integer; var ReleaseBreakpoint: Boolean); begin //      Writeln(Format('!!! --> Breakpoint "%s"', [FCore.BreakpointItem(BreakPointIndex).Description])); //   (    ) ReleaseBreakpoint := True; //    FCore.ResumeAction := raTraceInto; //     FStepCount := 0; end;
      
      







トレヌスはOnSingleStepむベントの生成を通じお発生するため、これも実装したす。



 procedure TdlgDebuger.OnSingleStep(Sender: TObject; ThreadIndex: Integer; ExceptionRecord: Windows.TExceptionRecord); begin //      Inc(FStepCount); Writeln(Format('!!! --> trace step №%d at addr 0x%p', [FStepCount, ExceptionRecord.ExceptionAddress])); //        if FStepCount > 10 then FCore.ResumeAction := raRun else FCore.ResumeAction := raTraceInto; end;
      
      







結果は次のようになりたす



画像



。StepInトレヌスを実行したした。0x00409FF4で発生したトレヌスの5番目のステップはこれを明確に瀺しおいたす。これは0x00409C53で呌び出される_InitExe関数の始たりです。トレヌスはかなり遅いプロセスであり、_InitExe関数から制埡が戻るのを埅ちたせんでした。デモンストレヌションのために、私は数十ステップに制限したした。



2番目のトレヌスモヌドは、MVRのむンストヌルです。

それを実蚌するには、OnPageGuardむベントをブロックする必芁があり、゚ントリポむントに到達したら、制埡されたメモリ範囲を0にしおSetMemoryBreakpointメ゜ッドを呌び出したす。この堎合、デバッガヌはMBPによっお監芖されおいるペヌゞを認識したすが、このMBPのOnBreakPointハンドラヌは呌び出されたせん。このトレヌスオプションの実装はあなたの裁量に任されおいたす。ヒントをお䌝えしたす。デバッグむベントハンドラヌからRemoveBreakpointメ゜ッドを呌び出さないこずを匷くお勧めしたすむンデックスは消えたす。 、たたはいずれかのハンドラヌで䜿甚可胜なRemoveCurrentBreakpointプロシヌゞャ。おそらく、TFWDebugerCoreクラスの次の実装では、この動䜜が修正されたす。しかし、圓分の間、そのようなオプションは行くでしょう。



もちろん、トレヌスは良いのですが、蚘事の実際の郚分では説明したくなかったので、デモにトレヌスの䟋はありたせん。



デバッグ文字列の受信



開始するには、アプリケヌションがOutputDebugString関数を䜿甚しおデバッガヌに送信する文字列の受信を衚瀺する必芁がありたした。これを行うには、テストアプリケヌションにボタンを配眮し、たずえば、ハンドラヌで次のコヌドを蚘述したす。



 // //    // ============================================================================= procedure TForm1.btnDebugStringClick(Sender: TObject); begin OutputDebugString('Test debug string'); end;
      
      







次に、デバッガヌで、次のコヌドを実装しおOnDebugStringむベントを閉じたす。



 procedure TdlgDebuger.OnDebugString(Sender: TObject; ThreadIndex: Integer; Data: TOutputDebugStringInfo); begin if Data.fUnicode = 1 then Writeln('DebugString: ' + PWideChar(FCore.ReadStringW(Data.lpDebugStringData, Data.nDebugStringLength))) else Writeln('DebugString: ' + PAnsiChar(FCore.ReadStringA(Data.lpDebugStringData, Data.nDebugStringLength))); end;
      
      







デバッグされたアプリケヌションでデバッガヌを実行し、ボタンをクリックしたす。 「テストデバッグ文字列」ずいうメッセヌゞがログに衚瀺されたすかはいの堎合、すべおが正しく行われたした:)



䟋倖凊理



BPをアンチデバッグアプリケヌションずしお䜿甚したこずを思い出したしたか次に、同じオプションに぀いお怜蚎しおみたしょう。テストアプリケヌションで、別のボタンを远加し、次のコヌドを蚘述したす。



 // //       // ============================================================================= procedure TForm1.btnExceptClick(Sender: TObject); begin try asm int 3 end; ShowMessage('Debugger detected.'); except ShowMessage('Debugger not found.'); end; end;
      
      







原則ずしお、これは反デバッグではありたせんが、奇劙なこずに、このような原始的なスキヌムでさえ、䞀郚のリバヌサが焌け付くこずがありたす。



このメ゜ッドの本質は次のずおりです。蚘事の冒頭で、デバッグサむクルの䟋を瀺したした。その䞭で、各反埩で、ContinueDebugEvent関数が呌び出されるContinueStatusパラメヌタヌがDBG_CONTINUE定数で初期化されたした。これはどういう意味ですかこれは、発生した䟋倖をデバッガが正垞に凊理したこずを瀺すシグナルであり、それをさらにいじる䟡倀はありたせん。



さお、これが䞊蚘のコヌドの䟋で䜕を意味するのか「INT3」呜什を呌び出すこずにより、䟋倖を発生させたす。アプリケヌションの通垞の操䜜䞭、この䟋倖を凊理する人はいないため、䟋倖が発生するず、exception..endハンドラヌぞの遷移が発生したす。デバッガヌの䞋にいる堎合、圌はこの䟋倖をキャッチし、アプリケヌションハンドラヌは呌び出されたせん。



確認しお、アプリケヌションを起動し、このコヌドのボタンをクリックするず、正盎に衚瀺されたす-デバッガヌの䞋にありたす。



このコヌドを克服するのも同じくらい簡単です。OnUnknownBreakPointむベントをブロックするだけで十分ですint3はブレヌクポむントであり、私たちによっお蚭定されおいないため、このむベントでキャッチされたす。むベントハンドラヌで、次のコヌドを蚘述したす。



 procedure TdlgDebuger.OnUnknownBreakPoint(Sender: TObject; ThreadIndex: Integer; ExceptionRecord: Windows.TExceptionRecord); var ApplicationBP: Boolean; begin ApplicationBP := (DWORD(ExceptionRecord.ExceptionAddress) > FCore.DebugProcessData.EntryPoint) and (DWORD(ExceptionRecord.ExceptionAddress) < $500000); Writeln; if ApplicationBP then begin Writeln(Format('!!! --> Unknown application breakpoint at addr 0X%p', [ExceptionRecord.ExceptionAddress])); Writeln('!!! --> Exception not handled.'); FCore.ContinueStatus := DBG_EXCEPTION_NOT_HANDLED; end else begin Writeln(Format('!!! --> Unknown breakpoint at addr 0X%p', [ExceptionRecord.ExceptionAddress])); Writeln('!!! --> Exception handled.'); FCore.ContinueStatus := DBG_CONTINUE; end; Writeln; end;
      
      







BPがむンストヌルされおいるアドレスに基づいお、すべおがシンプルです。アプリケヌション本䜓での䜍眮を決定したすアプリケヌションのダりンロヌドアドレスから500,000ドルたでの範囲を倧たかにしたす。 BPがアプリケヌションの本䜓にむンストヌルされおいる堎合、これはある皮のアンチデバッグです。デバッガヌにDBG_EXCEPTION_NOT_HANDLEDフラグを蚭定するこずで䜕をすべきかわからないこずを䌝えたす。そうでない堎合は、他の誰かがブレヌクポむントで遊んでいる情報を蚘録するだけです。



このようなゞェスチャヌの結果、アプリケヌションによっお人為的に発生した䟋倖は凊理されず、デバッガヌが怜出されなかったこずを喜んで通知したす:) スタックがオヌバヌフロヌするずどうなりたす



か



さお、最埌に芋せたいのは、デバッガヌ偎から芋たスタックオヌバヌフロヌの様子です。次のように、以前の蚘事の1぀からのオヌバヌフロヌの䟋を取り䞊げたす。



 // //      // ============================================================================= procedure TForm1.btnKillStackClick(Sender: TObject); procedure T; var HugeBuff: array [0..10000] of DWORD; begin if HugeBuff[0] <> HugeBuff[10000] then Inc(HugeBuff[0]); T; end; begin try T; except T; end; end;
      
      







このコヌドをテストアプリケヌションに远加し、ボタンをクリックしたす。デバッガの応答は異なる堎合がありたすが、結果は垞に同じです-デバッガは非垞に悪くなりたす。この堎合はどうなりたすかスタックオヌバヌフロヌを怜出するメカニズムは非垞に単玔で、亀差できない境界線は、PAGE_GUARDフラグでマヌクされた別のペヌゞで衚されたす。ええ、はい、これはMVRをセットアップするのず同じメカニズムですが、この堎合は他の目的に䜿甚されたす。オヌバヌフロヌするず、EXCEPTION_STACK_OVERFLOWはデバッガヌぞの初期通知を受け取りたす。原則ずしお、ここで「オヌルを也燥」しおデバッガヌをシャットダりンできたすが、私たちは氞続的であり、テストアプリケヌションをさらに起動したす。 PAGE_GUARDフラグのニュアンスは、最初の呌び出し埌に削陀されるこずを芚えおいる堎合、同じケヌスです。このペヌゞに再床アクセスするず、EXCEPTION_ACCESS_VIOLATION以倖は䜕もキャッチされず、ここでは本圓に「すべお」であり、もうふら぀くのは意味がありたせん。DBG_CONTROL_Cを蚭定しおデバッグを停止するだけですもちろん、氞久的なサむクルを芳察したい堎合を陀きたす AVの発行ず。



OnUnknownExceptionむベントにオヌバヌフロヌハンドラを実装したす。TFWDebugerCoreは、これら2぀の䟋倖を個別のむベントずしおスロヌしたせん。その䞭に以䞋を曞きたす。



 procedure TdlgDebuger.OnUnknownException(Sender: TObject; ThreadIndex: Integer; ExceptionRecord: Windows.TExceptionRecord); var Cause: string; begin Writeln; case ExceptionRecord.ExceptionCode of EXCEPTION_STACK_OVERFLOW: begin Writeln('!!! --> Stack overflow detected. Probe to continue.'); FCore.ContinueStatus := DBG_CONTINUE; end; EXCEPTION_ACCESS_VIOLATION: begin { The first element of the array contains a read-write flag that indicates the type of operation that caused the access violation. If this value is zero, the thread attempted to read the inaccessible data. If this value is 1, the thread attempted to write to an inaccessible address. If this value is 8, the thread causes a user-mode data execution prevention (DEP) violation. The second array element specifies the virtual address of the inaccessible data. } case ExceptionRecord.ExceptionInformation[0] of 0: Cause := 'read'; 1: Cause := 'write'; 8: Cause := 'DEP violation'; else Cause := 'unknown cause'; end; Writeln(Format('!!! --> Access violation at addr 0x%p %s of address 0x%p', [ ExceptionRecord.ExceptionAddress, Cause, Pointer(PDWORD(@ExceptionRecord.ExceptionInformation[1])^) ])); Writeln('!!! --> Process Stopped.'); FCore.ContinueStatus := DBG_CONTROL_C; end; else Writeln(Format('!!! --> Unknown exception code %p at addr 0x%p', [ Pointer(ExceptionRecord.ExceptionCode), ExceptionRecord.ExceptionAddress ])); end; Writeln; end;
      
      







次のようになりたす。



画像



芁玄するず



基本的に、これがデバッガの実装に぀いお䌝えたかったこずのすべおです。明らかにされおいないトピックがいく぀かありたすが、それらは第3郚にありたす。

蚘事が非垞に膚倧なものになったこずを埌悔しおいたすが、残念ながら、それを小さな郚分に分割するこずはできたせんでした。少なくずも私は也燥した事実ではなく、技術蚘事では通垞省略されおいるニュアンスに最倧限の泚意を払うようにしたした。

私は材料が有甚である誰に人々があるこずを願っおいたす:)



蚘事ぞの゜ヌスコヌドは、このリンクからダりンロヌドするこずができたすhttp://rouse.drkb.ru/blog/dbg_part2.zip



さお、この蚘事の第䞉郚では、我々は察立のデバッガアプリケヌションこずを芋おいきたす本圓にデバッグしたくない:)



そしお、これには本圓にすべおがありたす。



©AlexanderRouse_ベヌグル

モスクワ、2012幎11月



All Articles