Puncte de intersecție și vizite

Programe de depanare

Majoritatea programatorilor nu înțeleg că debuggerii utilizează pe scară largă "spatele scenei" întrerupe pentru a permite debuggerului principal să gestioneze subordonatul. Deși puteți seta puncte de întrerupere nu direct, debuggerul le va instala, permițându-vă să gestionați sarcini cum ar fi trecerea peste funcția apelată. Depanatorul utilizează, de asemenea, puncte de întrerupere atunci când trebuie să executați programul pe linia specificată a fișierului sursă și să se oprească. În cele din urmă, debuggerul stabilește puncte de întrerupere pentru a comuta la depanator prin comandă (de exemplu, selectând Debug Break în WDBG).







Listele 4-4 arată codul funcției SetBreakpoint. Citirea codului, rețineți că funcția DBG_ * LOCALASSIST.DLL aparțin bibliotecii și a ajuta la izolarea diferitelor rutine de manipulare a procesului, ceea ce face mai ușor pentru a adăuga WDBG funcții de depanare la distanță. Funcția SetBreakpoint ilustrează procesarea (descrisă mai devreme în acest capitol) necesară pentru modificarea protecției memoriei când scrieți la ea.

Listele 4-4. Funcția SetBreakepoint de la 1386CPUHELP.C

int CPUHELP_DLLINTERFACE _stdcall

SetBreakpoint (PDEBUGPACKET dp.

DWORD dwReadWrite = 0;

BYTE bTempOp = BREAK_OPCODE;

ASSERT (FALSE == IsBadReadPtr (dp, sizeof (DEBUGPACKET))));

ASSERT (FALSE == IsBadWritePtr (pOpCode, sizeof (OPCODE)));

dacă ((TRUE == IsBadReadPtr (dp, sizeof (DEBUGPACKET))) ||

(TRUE == IsBadWritePtr (pOpCode, sizeof (OPCODE))))

TRACE0 ("SetBreakpoint Parametri nevalabili \ n");

// mai mult de 2 GB, apoi reveniți,

dacă ((FALSE = IsNT ()) (ulAddr> = 0x80000000))

bReadMem = DBG_ReadProcessMemory (dp-> hProcess.

(LPCVOID) ulAddr, SbTempOp. sizeof (BYTE), SdwReadWrite);

ASSERT (FALSE! = BReadMem);

ASSERT (dimensiunea (BYTE) = dwReadWrite);

dacă ((FALSE = bReadMem) ||

(dimensiunea (BYTE)! = dwReadWrite))

// Este redenumită această nouă punct de întrerupere pentru a fi rescrisă

// codul de funcționare al unui punct de întrerupere existent?

dacă (BREAKJDPCODE = bTempOp)

// Obțineți proprietățile paginii pentru depanatorul subordonat.

// Traduceți depanatorul subordonat în modul

dacă (FALSE == DBG_VirtualProtectEx (dp-> hProcess.

ASSERT ("VirtualProtectEx.");

// Salvați codul operației care urmează să fie înlocuită.

// Codul de funcționare a fost salvat, deci acum

// trebuie să setați punctul de întrerupere.

bWriteMem = DBG_WriteProcessMemory (dp-> hProcess.

ASSERT (FALSE! = BWriteMem);

ASSERT (dimensiunea (BYTE) == dwReadWrite);

dacă ((FALSE == bWriteMem) ||

(dimensiunea (BYTE)! = dwReadWrite))

// Returnați protecția la starea care a precedat

// setați punctul de întrerupere

// Schimbați protecția înapoi la ceea ce a fost înainte

/ / I-am distrus punctul de trecere în.

Verificați (DBG_VirtualProtectEx (dp-> hProcess.

// Resetați cache-ul de instrucțiuni în cazul în care această memorie se afla în memoria cache a procesorului

bFlush = DBG_FlushInstructionCache (dp-> hProcess.

ASSERT (TRUE = bFlush);

Întrebare: Cum pot reseta punctul de întrerupere pentru a putea opri din nou în acest loc? Dacă CPU acceptă execuția pas-cu-pas, resetarea punctului de întrerupere este trivială. În modul pas cu pas, CPU execută o singură instrucțiune și generează un alt tip de excepție - EXCEPTION_SINGLE_STEP (0x80000004). Din fericire, toate procesoarele care rulează Windows pe 32 de biți acceptă execuția pas-cu-pas. Pentru a trece la modul de execuție pas cu pas al procesoarelor Intel Pentium, este necesar să setați (în stare unică) bitul 8 al registrului de steaguri. Ghidul de referință Intel numește un bit de capcane - Trap Rag (TF sau flag de urmărire). Listele 4-5 arată funcția Setsingiestep și pașii necesari pentru a instala bitul TF. După înlocuirea punctului de întrerupere cu codul sursă al operației, debuggerul avertizează în starea internă că se așteaptă să execute pas cu pas, stabilește modul corespunzător în CPU și continuă procesul.

Listare 4-5. Funcția SetSingleStep a 1386CPUHELP. C

BOOL CPUHELP_DLLIMNTERFACE _stdcall

SetSingleStep (PDEBUGPACKET dp)

ASSERT (FALSE == IsBadReadPtr (dp, sizeof (DEBUGPACKET))));

dacă (TRUE = IsBadReadPtr (dp, sizeof (DEBUGPACKET)))

TRACED ("SetSingleStep. Parametri nevalabili \ n!");

// Pentru i386, setați doar bitul TF.

bSetContext = DBG_SetThreadContext (dp-> hThread,

ASSERT (FALSE! = BSetContext);

Odată ce debugger eliberează procesul principal care cauzează funcția ContinueDebugEvent, procesul după fiecare execuție a unei singure instrucțiuni generează imediat pas excepție. Pentru a vă asigura că aceasta este o excepție pas-cu-pas așteptată, debuggerul verifică starea sa internă. Deoarece debuggerul a așteptat o astfel de excepție, știe că punctul de întrerupere trebuie să fie resetat. La fiecare etapă individuală a acestui proces, indicatorul de instrucțiuni avansează într-o poziție care precedă punctul inițial de întrerupere. Prin urmare, debuggerul poate seta codul de operare a punctului de revizie înapoi în poziția inițială. De fiecare dată când apare o excepție de tipul EXCEPTION_ SINGLE_STEP. sistemul de operare resetează automat bitul TF, deci nu este necesar să îl resetați cu un debugger. După setarea punctului de întrerupere, debuggerul principal deblochează slave-ul, care continuă să ruleze.

Toată prelucrarea punctului de întrerupere este implementată prin metoda CWDBGProjDOC. -andieBreakpoint. care poate fi găsit în fișierul WDBGPROJDOC.CPP de pe CD-ROM-ul însoțitor. Punctele de intersecție sunt definite în fișierele BREAKPOINTS și BREAKPOINT.CPP. Aceste fișiere conțin o pereche de clase care gestionează punctele de întrerupere a diferitelor stiluri. Caseta de dialog WDBG Breakpoints vă permite să setați puncte de întrerupere atunci când executați un sub debugger în același mod ca și în programul de depanare Visual C ++. Abilitatea de a seta puncte de oprire "în zbor" înseamnă că trebuie să urmăriți cu atenție starea debuggerului secundar și starea punctelor de întrerupere. Detalii privind prelucrarea și oprirea breakpoints în debugger în conformitate cu un statut subordonat poate fi găsit în descrierea metodei CBreakpointsDig :: OnOk în fișierul BREAKPOINTSDLG.CPP pe CD-ROM-ul de însoțire.

Una dintre cele mai elegante proprietăți implementate în WDBG este legată de elementul de meniu Debug Break. Ideea este că, în timp ce un debugger subordonat se execută, puteți intra rapid în depanatorul principal în orice moment.

Punctele de întrerupere stabilite în timpul implementării elementului Debug Break sunt ușor diferite de cele utilizate de WDBG. Astfel de puncte sunt numite punct de întrerupere, deoarece sunt șterse de îndată ce acestea sunt declanșate. Obținerea unui set de astfel de puncte de întrerupere este de interes. O reprezentare completă poate fi obținută prin analiza funcției CWDBGProj Doc. OnDebugBreak de la WDBGPROJDOC.CPP. dar aici oferim doar câteva detalii instructive. Listele 4-6 prezintă funcția CWDBGProj Doc. OnDebugBreak de la WDBGPROJDOC.CPP. Pentru mai multe informații despre punctele de întrerupere de o dată, consultați "Pasul în pas, pasul peste pasul u" din acest capitol.







Listare 4-5. Manipularea breșei de depanare în WDBGPROJDOC.CPP

void CWDBGProjDoc. OnDebugBreak ()

ASSERT (m_vDbgThreads.size ()> 0);

// Ideea aici este de a suspenda toate firele

// sub debugger și setați indicatorul la instrucțiunea curentă

// pentru fiecare dintre ele la punctul de întrerupere. Deci, pot

// asigurați-vă că va fi cel puțin una dintre fire

// capturați punctele de întrerupere de o dată. Una dintre situații,

// la care nu va fi setarea punctului de rupere pe fiecare fir

// work, se întâmplă când aplicația "se blochează". Din moment ce

// nu există subiecte, nu sunt chemați puncte de întrerupere.

// Pentru a face munca într-un astfel de impas, am fost forțat să

// folosiți următorul algoritm: '

// 1. Setați punctele de întrerupere cu această funcție.

// 2. Setați caseta de selectare a stării pentru a indica ceea ce aștept

// pe punctul de întrerupere Debug Break.

// 3. Setați cronometrul de fundal pentru a aștepta punctul de întrerupere.

// 4. Dacă una dintre punctele de întrerupere dispare, resetați cronometrul.

// 5. Dacă timerul este resetat, aplicația "se blochează".

6. După cronometru, setați indicatorul de instrucțiuni al unuia dintre

7. Reporniți fluxul.

// 8. Atunci când aceste puncte speciale funcționează, clar

// Breakpoint și resetați pointerul de comandă

// înapoi la poziția inițială.

// Măriți prioritatea acestui thread,

// pentru a trece prin stabilirea acestor puncte de întrerupere

// mai repede și pentru a proteja orice fir de la depanatorul subordonat de la

MANUAL hThisThread = GetCurrentThread ();

int iOldPriority = GetThreadPrioritate (hThisThread);

SetThreadPriority (hThisThread, THREAD_BASE_PRIORITY_LOWRT);

MANUAL hProc = GetDebuggeeProcessHandle ();

DBGTHREADVECT :: iterator i; pentru (i = m_vDbgThreads.begin ();

// Întrerupeți acest thread. Dacă are deja un contor

// Suspendări, într-adevăr nu mă deranjează. A fost

// astfel încât punctele de intersecție au fost stabilite pe fiecare fir

// debuggerul. Mi se pare un flux activ

// În cele din urmă accidental.

// Firul este suspendat, puteți obține contextul.

// Pentru că, dacă se utilizează ASSERT, prioritatea acestui fir

// instalat în timp real, iar computerul poate

// "atârnă" pe panoul de mesaje, deci în instrucțiunea if, puteți

// specificați eroarea numai cu ajutorul operatorului de urmărire.

dacă (FALSE! = DBG_GetThreadContext (i-> m_hThread, ctx))

DWORD dwAddr = ReturnInstructionPointer ( ctx);

// Setați punctul de întrerupere.

dacă (TRUE == cBP.ArmBreakpoint (hProc))

// Adăugați acest breakpoint la lista Debug Break,

// numai dacă punctul de întrerupere a reușit

// activat. Aplicatorul de depanare poate fi ușor

// au mai multe fire asociate aceluiași fir

// doar un punct de întrerupere. m_aDebugBreakBPs.Add (cBP);

TRACE ("GetThreadContext a eșuat! Ultima eroare = Ox% 08X \ n",

// Deoarece funcția GetThreadContext a eșuat,

// probabil ar trebui să vedem ce sa întâmplat. prin urmare

// intrați în depanator depanând depanatorul WDBG.

// Chiar dacă firul WDBG rulează la

// Priorități în timp real, sunați la DebugBreak

// elimină imediat acest fir din planificatorul sistemului de operare

// sistem, deci prioritatea sa este redusă. DebugBreak ();

// Toate firele au stabilit puncte de întrerupere. Acum o vom face

// Reporniți-le pe toate și trimiteți fiecărui fir un mesaj.

// Motivul pentru trimiterea mesajelor este simplu. În cazul în care subordonat

// debuggerul va răspunde la mesaje sau alte procese, va face

// imediat avortat. Cu toate acestea, dacă este pur și simplu inactiv într-un ciclu

// mesaje, este necesar să-l forțeze acțiunii.

// Deoarece există un ID al fluxului, vom trimite pur și simplu

// introduceți mesajul WM_NULL. Se presupune că acest mod nemaipomenit

// mesaj, deci nu ar trebui să strică depanatorul subordonat.

// Dacă firul nu are o coadă de mesaje, această funcție va tolera pur și simplu

// eșua pentru un astfel de fir, fără a provoca nici un rău,

pentru (i = m_vDbgThreads.begin ();

// Permiteți continuarea execuției acestui fir

// la următoarea punct de întrerupere

PostThreadMessage (i-> m_dwTID, WM_NULL, 0, 0);

// Acum micșorați prioritatea la valoarea veche.

SetThreadPrioritate (hThisThread, iOldPriority);

Pentru a opri programul de depanare subordonat, trebuie să "spargeți" punctul de întrerupere în fluxul de instrucțiuni al procesorului, astfel încât să puteți opri depanatorul. Dacă firul rulează, puteți ajunge la punctul cunoscut utilizând funcția API suspendThread. suspendând-o. Apoi, sunând la API-ul GetThreadContext. determina pointerul comenzii curente. Având un astfel de indicator, puteți reveni la instalarea punctelor de întrerupere simple. După ce ați setat punctul de întrerupere, trebuie să apelați funcția ResumeThread API. Pentru a permite thread-ului să continue executarea și să-l facă să vină peste acest punct.

Deși este destul de ușor să intervii în depanator, trebuie să te gândești la câteva probleme. Primul este că punctul dvs. de întrerupere poate să nu funcționeze. Dacă aplicația de depanare subordonată procesează mesajul sau face altceva, va fi întreruptă. Cu toate acestea, dacă debuggerul subordonat, în timp ce se află în această stare, așteaptă să primească mesajul, punctul de întrerupere nu va funcționa până când debuggerul subordonat nu primește mesajul. Deși ați putea solicita utilizatorului să deplaseze mouse-ul peste depanatorul subordonat pentru a genera un mesaj. _MOUSEMOVE. Dar utilizatorul însuși nu poate fi încântat de o astfel de cerință.

Pentru a vă asigura că depanatorul de sclave atinge punctul de întrerupere, trebuie să trimiteți un mesaj la acesta. Dacă tot ce aveți este un descriptor de fir emis de un API de depanare, atunci este neclar cum să transformați acest mâner în mânerul corespunzător al ferestrei (HWND)? Din nefericire, acest lucru nu se poate face. Cu toate acestea, cu un descriptor de fir, puteți apela întotdeauna funcția PostThreadMessage. care va trimite mesajul la coada mesajelor de fir. Deoarece procesarea mesajului HWND este suprapusă peste partea de sus a coadă de mesaje de flux, apelul PostThreadMessage face exact ceea ce este necesar.

Rămâne să înțelegem ce mesaj trebuie trimis? Nu puteți trimite un mesaj care ar putea determina aplicația de depanare subordonată să facă orice prelucrare reală, permițând astfel debuggerului principal să schimbe comportamentul depanatorului subordonat. De exemplu, trimiterea unui mesaj WM_CREATE. probabil că nu ar fi o idee bună. Din fericire, există un mesaj mai potrivit - WM_NULL. pe care probabil îl folosiți ca instrument de depanare la schimbarea mesajelor. Trimiterea unui mesaj WM_NULL utilizând PostThreadMessage nu provoacă nici un rău, chiar dacă firul nu are o coadă de mesaje, iar aplicația este consolă. Deoarece aplicațiile console sunt întotdeauna în starea de execuție, chiar dacă așteptați o comandă cheie, setarea punctului de întrerupere în comanda curentă va duce la o întrerupere.

O altă problemă este legată de multithreading. Dacă intenționați să suspendați numai un fir și aplicația este multietajată, trebuie să știți care fir trebuie să fie întrerupt? În cazul în care, întrerupând executarea unei cereri, a stabilit un punct de întrerupere în firul greșit, să spunem că este blocat într-o stare de așteptare pentru un eveniment, semnalul care este furnizat numai, cum ar fi în timpul imprimării de fundal, punctul dumneavoastră nu funcționează întrerupere, până când utilizatorul alege ceva apoi imprimați. Singurul mod sigur de a întrerupe o aplicație cu mai multe fire este suspendarea tuturor firelor și setarea unui punct de întrerupere în fiecare dintre acestea.

Această tehnică funcționează bine cu o aplicație care are doar două fire. Cu toate acestea, dacă există multe fluxuri, problema rămâne deschisă. Prin suspendarea fiecăruia dintre firele depanatorului subordonat, schimbați starea aplicației astfel încât să existe pericolul de a-l conduce într-un final. Pentru a opri toate firele, pentru a seta puncte de întrerupere și pentru a relua firele fără probleme, depanatorul trebuie să mărească prioritatea thread-ului propriu. Creșterea priorității la THREAD_BASE_PRIORITY_LOWRT. debuggerul poate planifica astfel fluxul, astfel încât firele depanatorului să nu fie executate atunci când debuggerul de bază le manipulează.

În timp ce algoritmul pentru întreruperea aplicațiilor multi-threaded pare rezonabil. Cu toate acestea, pentru ca punctul de debug Debug să devină complet operațional, este necesar să rezolvăm încă o problemă. Dacă întregul set de puncte de întrerupere este setat în toate firele și aceste fluxuri sunt reluate, este încă posibilă o situație în care nu se vor produce încă întreruperi. Prin setarea punctelor de intersecție, se bazează pe executarea a cel puțin unuia dintre firele care provoacă o excepție de la punctul de întrerupere. Și ce se întâmplă dacă procesul se află într-o situație de impas? Nimic nu se va întâmpla - nu se execută fire și punctele de întrerupere cu atenție nu vor face niciodată excepții.

Știați că observatorii sunt operații care folosesc obiecte de tipul corespunzător ca argument și returnează un element de alt tip, sunt folosite pentru a obține informații despre obiect. Aceasta include, de exemplu, operațiuni de dimensiune de tip.

ȘTIRI ALE FORUMULUI
Cavalerii teoriei eterului







Trimiteți-le prietenilor: