日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

软件调试学习笔记(七)—— 单步步入单步步过

發布時間:2025/3/21 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 软件调试学习笔记(七)—— 单步步入单步步过 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

軟件調試學習筆記(七)—— 單步步入&單步步過

  • 單步步入
    • 設置單步異常
    • 處理單步異常
    • 實驗1:單步異常的設置與處理
  • 單步步過
    • 實現思路
    • 實驗2:實現單步步過

單步步入

描述

  • 單步步入的實現依賴于單步異常
  • 當我們需要觀察每一行代碼(包括函數內部的代碼)執行之后寄存器與內存的變化,通常會采用單步步入。
  • 當使用單步步入時,可采用在下一行代碼的首字節設置INT 3斷點的方式實現。
  • CPU為我們提供了一種更為方便的方法,即使用陷阱標志位(TF位)
  • 設置單步異常

    將TF位置1

    處理單步異常

    單步產生的異常與硬件斷點產生的異常一致,都是STATUS_SINGLE_STEP(單步異常)

    實驗1:單步異常的設置與處理

    1)編譯并運行以下代碼:

    #include <stdio.h> #include <windows.h> #include <tlhelp32.h>#define DEBUGGEE "C:\\helloworld.exe"//被調試進程ID,進程句柄,OEP DWORD dwDebuggeePID = 0;//被調試線程句柄 HANDLE hDebuggeeThread = NULL; HANDLE hDebuggeeProcess = NULL;//系統斷點 BOOL bIsSystemInt3 = TRUE;//被INT 3覆蓋的數據 CHAR OriginalCode = 0;//線程上下文 CONTEXT Context;typedef HANDLE (__stdcall *FnOpenThread) (DWORD, BOOL, DWORD);VOID InitDebuggeeInfo(DWORD dwPID, HANDLE hProcess) {dwDebuggeePID = dwPID;hDebuggeeProcess = hProcess; }DWORD GetProcessId(LPTSTR lpProcessName) {HANDLE hProcessSnap = NULL;PROCESSENTRY32 pe32 = {0};hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);if(hProcessSnap == (HANDLE)-1){return 0;}pe32.dwSize = sizeof(PROCESSENTRY32);if(Process32First(hProcessSnap, &pe32)){do {if(!strcmp(lpProcessName, pe32.szExeFile))return (int)pe32.th32ProcessID;} while (Process32Next(hProcessSnap, &pe32));}else{CloseHandle(hProcessSnap);}return 0; }BOOL WaitForUserCommand() {BOOL bRet = FALSE;CHAR command;printf("COMMAND>");command = getchar();switch(command){case 't':bRet = TRUE;//1. 獲取線程上下文Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;GetThreadContext(hDebuggeeThread, &Context);//2. 設置陷阱標志位Context.EFlags |= 0x100;//3. 設置線程上下文SetThreadContext(hDebuggeeThread, &Context);break;case 'p':bRet = TRUE;break;case 'g':bRet = TRUE;break;}getchar();return bRet; }VOID SetHardBreakPoint(PVOID pAddress) {//1. 獲取線程上下文Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;GetThreadContext(hDebuggeeThread, &Context);//2. 設置斷點位置Context.Dr0 = (DWORD)pAddress;Context.Dr7 |= 1;//3. 設置斷點長度和類型Context.Dr7 &= 0xfff0ffff; //執行斷點(16、17位 置0) 1字節(18、19位 置0)//5. 設置線程上下文SetThreadContext(hDebuggeeThread, &Context); }BOOL Int3ExceptionProc(EXCEPTION_DEBUG_INFO *pExceptionInfo) {BOOL bRet = FALSE;//1. 將INT 3修復為原來的數據(如果是系統斷點,不用修復)if(bIsSystemInt3){bIsSystemInt3 = FALSE;return TRUE;}else{WriteProcessMemory(hDebuggeeProcess, pExceptionInfo->ExceptionRecord.ExceptionAddress, &OriginalCode, 1, NULL);}//2. 顯示斷點位置printf("Int 3斷點:0x%p \r\n", pExceptionInfo->ExceptionRecord.ExceptionAddress);//3. 獲取線程上下文Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;GetThreadContext(hDebuggeeThread, &Context);//4. 修正EIPContext.Eip--;SetThreadContext(hDebuggeeThread, &Context);//5. 顯示反匯編代碼、寄存器等/*硬件斷點需要設置在被調試進程的的線程上下文中。因此當被調試程序觸發調試器設置的INT 3斷點時,此時設置硬件斷點較為合理。*///SetHardBreakPoint((PVOID)((DWORD)pExceptionInfo->ExceptionRecord.ExceptionAddress+1));//6. 等待用戶命令while(bRet == FALSE){bRet = WaitForUserCommand();}return bRet; }BOOL AccessExceptionProc(EXCEPTION_DEBUG_INFO *pExceptionInfo) {BOOL bRet = TRUE;return bRet; }BOOL SingleStepExceptionProc(EXCEPTION_DEBUG_INFO *pExceptionInfo) {BOOL bRet = FALSE;//1. 獲取線程上下文Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;GetThreadContext(hDebuggeeThread, &Context);//2. 判斷是否是硬件斷點導致的異常if(Context.Dr6 & 0xF) //B0~B3不為空 硬件斷點{//2.1 顯示斷點信息printf("硬件斷點:%x 0x%p \n", Context.Dr7&0x00030000, Context.Dr0);//2.2 將斷點去除Context.Dr0 = 0;Context.Dr7 &= 0xfffffffe;}else //單步異常{//2.1 顯示斷點信息printf("單步異常:0x%p \n", Context.Eip);//2.2 將斷點去除Context.Dr7 &= 0xfffffeff;}//3.設置線程上下文SetThreadContext(hDebuggeeThread, &Context);//4. 顯示寄存器信息/反匯編代碼//...略//5. 等待用戶命令while(bRet == FALSE){bRet = WaitForUserCommand();}return bRet; }BOOL ExceptionHandler(DEBUG_EVENT *pDebugEvent) { BOOL bRet = TRUE;EXCEPTION_DEBUG_INFO *pExceptionInfo = NULL;pExceptionInfo = &pDebugEvent->u.Exception;//得到線程句柄,后面要用FnOpenThread MyOpenThread = (FnOpenThread)GetProcAddress(LoadLibrary("kernel32.dll"), "OpenThread");hDebuggeeThread = MyOpenThread(THREAD_ALL_ACCESS, FALSE, pDebugEvent->dwThreadId);switch(pExceptionInfo->ExceptionRecord.ExceptionCode){//INT 3異常case EXCEPTION_BREAKPOINT:bRet = Int3ExceptionProc(pExceptionInfo);break;//訪問異常case EXCEPTION_ACCESS_VIOLATION:bRet = AccessExceptionProc(pExceptionInfo);break;//單步執行case EXCEPTION_SINGLE_STEP:bRet = SingleStepExceptionProc(pExceptionInfo);break;}return bRet; }VOID SetInt3BreakPoint(LPVOID addr) {CHAR int3 = 0xCC;//1. 備份ReadProcessMemory(hDebuggeeProcess, addr, &OriginalCode, 1, NULL);//2. 修改WriteProcessMemory(hDebuggeeProcess, addr, &int3, 1, NULL); }int main(int argc, char* argv[]) {BOOL nIsContinue = TRUE;DEBUG_EVENT debugEvent = {0};BOOL bRet = TRUE;DWORD dwContinue = DBG_CONTINUE;//1.創建調試進程STARTUPINFO startupInfo = {0};PROCESS_INFORMATION pInfo = {0};GetStartupInfo(&startupInfo);bRet = CreateProcess(DEBUGGEE, NULL, NULL, NULL, TRUE, DEBUG_PROCESS || DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &startupInfo, &pInfo);if(!bRet){printf("CreateProcess error: %d \n", GetLastError());return 0;}hDebuggeeProcess = pInfo.hProcess;//2.調試循環while(nIsContinue){bRet = WaitForDebugEvent(&debugEvent, INFINITE);if(!bRet){printf("WaitForDebugEvent error: %d \n", GetLastError());return 0;}switch(debugEvent.dwDebugEventCode){//1.異常case EXCEPTION_DEBUG_EVENT:bRet = ExceptionHandler(&debugEvent);if(!bRet)dwContinue = DBG_EXCEPTION_NOT_HANDLED;break;//2.case CREATE_THREAD_DEBUG_EVENT:break;//3.創建進程case CREATE_PROCESS_DEBUG_EVENT://設置INT 3斷點SetInt3BreakPoint((PCHAR)debugEvent.u.CreateProcessInfo.lpStartAddress);break;//4.case EXIT_THREAD_DEBUG_EVENT:break;//5.case EXIT_PROCESS_DEBUG_EVENT:break;//6.case LOAD_DLL_DEBUG_EVENT:break;//7.case UNLOAD_DLL_DEBUG_EVENT:break;//8.case OUTPUT_DEBUG_STRING_EVENT:break;}bRet = ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);}return 0; }

    運行結果:

    2)多次輸入t并回車,單步執行

    3)輸入g并回車,程序繼續運行

    單步步過

  • 當遇到CALL指令時,若無需進入函數內部進行調試,可以使用單步步過
  • 與單步步入不同的是,單步步過的實現依賴于軟件斷點硬件斷點
  • 實現思路

  • 判斷當前指令是否為CALL指令
  • 不是CALL指令,設置TF為1觸發單步異常
  • CALL指令,判斷OPCODEE8還是FF15
  • 若OPCODE是E8,在當前地址之后的第5個字節設置軟件斷點(E8指令占5個字節)
  • 若OPCODE是FF15,在當前地址之后的第6個字節設置軟件斷點(FF15指令占6個字節)
  • 實驗2:實現單步步過

    1)編譯并運行以下代碼:

    #include <stdio.h> #include <windows.h> #include <tlhelp32.h>#define DEBUGGEE "C:\\helloworld.exe"//被調試進程ID,進程句柄,OEP DWORD dwDebuggeePID = 0;//被調試線程句柄 HANDLE hDebuggeeThread = NULL; HANDLE hDebuggeeProcess = NULL;//系統斷點 BOOL bIsSystemInt3 = TRUE;//被INT 3覆蓋的數據 CHAR cOriginalCode = 0;//線程上下文 CONTEXT Context;typedef HANDLE (__stdcall *FnOpenThread) (DWORD, BOOL, DWORD);VOID InitDebuggeeInfo(DWORD dwPID, HANDLE hProcess) {dwDebuggeePID = dwPID;hDebuggeeProcess = hProcess; }DWORD GetProcessId(LPTSTR lpProcessName) {HANDLE hProcessSnap = NULL;PROCESSENTRY32 pe32 = {0};hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);if(hProcessSnap == (HANDLE)-1){return 0;}pe32.dwSize = sizeof(PROCESSENTRY32);if(Process32First(hProcessSnap, &pe32)){do {if(!strcmp(lpProcessName, pe32.szExeFile))return (int)pe32.th32ProcessID;} while (Process32Next(hProcessSnap, &pe32));}else{CloseHandle(hProcessSnap);}return 0; }VOID SetInt3BreakPoint(LPVOID addr) {CHAR int3 = 0xCC;//1. 備份ReadProcessMemory(hDebuggeeProcess, addr, &cOriginalCode, 1, NULL);//2. 修改WriteProcessMemory(hDebuggeeProcess, addr, &int3, 1, NULL); }BOOL WaitForUserCommand() {BOOL bRet = FALSE;CHAR command;WORD wBuffer;printf("COMMAND>");command = getchar();switch(command){case 't':bRet = TRUE;//1. 獲取線程上下文Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;GetThreadContext(hDebuggeeThread, &Context);//2. 設置陷阱標志位Context.EFlags |= 0x100;//3. 設置線程上下文SetThreadContext(hDebuggeeThread, &Context);break;case 'p':bRet = TRUE;bRet = TRUE;//1. 獲取線程上下文Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;GetThreadContext(hDebuggeeThread, &Context);//2. 讀取當前EIP指向的機器碼ReadProcessMemory(hDebuggeeProcess, (LPVOID)Context.Eip, &wBuffer, 2, NULL);if((wBuffer & 0xFF) == 0xE8){//3. 在當前地址之后的第5個字節設置軟件斷點(E8指令占5個字節)SetInt3BreakPoint((LPVOID)(Context.Eip+5));}else if(wBuffer == 0x15FF){//3. 在當前地址之后的第6個字節設置軟件斷點(FF15指令占6個字節)SetInt3BreakPoint((LPVOID)(Context.Eip+6));}else{//3. 不是CALL指令,設置陷阱標志位觸發單步異常即可Context.EFlags |= 0x100;//4. 設置線程上下文SetThreadContext(hDebuggeeThread, &Context);}break;case 'g':bRet = TRUE;break;}getchar();return bRet; }VOID SetHardBreakPoint(PVOID pAddress) {//1. 獲取線程上下文Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;GetThreadContext(hDebuggeeThread, &Context);//2. 設置斷點位置Context.Dr0 = (DWORD)pAddress;Context.Dr7 |= 1;//3. 設置斷點長度和類型Context.Dr7 &= 0xfff0ffff; //執行斷點(16、17位 置0) 1字節(18、19位 置0)//5. 設置線程上下文SetThreadContext(hDebuggeeThread, &Context); }BOOL Int3ExceptionProc(EXCEPTION_DEBUG_INFO *pExceptionInfo) {BOOL bRet = FALSE;//1. 將INT 3修復為原來的數據(如果是系統斷點,不用修復)if(bIsSystemInt3){bIsSystemInt3 = FALSE;return TRUE;}else{WriteProcessMemory(hDebuggeeProcess, pExceptionInfo->ExceptionRecord.ExceptionAddress, &cOriginalCode, 1, NULL);}//2. 顯示斷點位置printf("Int 3斷點:0x%p \r\n", pExceptionInfo->ExceptionRecord.ExceptionAddress);//3. 獲取線程上下文Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;GetThreadContext(hDebuggeeThread, &Context);//4. 修正EIPContext.Eip--;SetThreadContext(hDebuggeeThread, &Context);//5. 顯示反匯編代碼、寄存器等/*硬件斷點需要設置在被調試進程的的線程上下文中。因此當被調試程序觸發調試器設置的INT 3斷點時,此時設置硬件斷點較為合理。*///SetHardBreakPoint((PVOID)((DWORD)pExceptionInfo->ExceptionRecord.ExceptionAddress+1));//6. 等待用戶命令while(bRet == FALSE){bRet = WaitForUserCommand();}return bRet; }BOOL AccessExceptionProc(EXCEPTION_DEBUG_INFO *pExceptionInfo) {BOOL bRet = TRUE;return bRet; }BOOL SingleStepExceptionProc(EXCEPTION_DEBUG_INFO *pExceptionInfo) {BOOL bRet = FALSE;//1. 獲取線程上下文Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;GetThreadContext(hDebuggeeThread, &Context);//2. 判斷是否是硬件斷點導致的異常if(Context.Dr6 & 0xF) //B0~B3不為空 硬件斷點{//2.1 顯示斷點信息printf("硬件斷點:%x 0x%p \n", Context.Dr7&0x00030000, Context.Dr0);//2.2 將斷點去除Context.Dr0 = 0;Context.Dr7 &= 0xfffffffe;}else //單步異常{//2.1 顯示斷點信息printf("單步異常:0x%p \n", Context.Eip);//2.2 將斷點去除Context.Dr7 &= 0xfffffeff;}//3.設置線程上下文SetThreadContext(hDebuggeeThread, &Context);//4. 顯示寄存器信息/反匯編代碼//...略//5. 等待用戶命令while(bRet == FALSE){bRet = WaitForUserCommand();}return bRet; }BOOL ExceptionHandler(DEBUG_EVENT *pDebugEvent) { BOOL bRet = TRUE;EXCEPTION_DEBUG_INFO *pExceptionInfo = NULL;pExceptionInfo = &pDebugEvent->u.Exception;//得到線程句柄,后面要用FnOpenThread MyOpenThread = (FnOpenThread)GetProcAddress(LoadLibrary("kernel32.dll"), "OpenThread");hDebuggeeThread = MyOpenThread(THREAD_ALL_ACCESS, FALSE, pDebugEvent->dwThreadId);switch(pExceptionInfo->ExceptionRecord.ExceptionCode){//INT 3異常case EXCEPTION_BREAKPOINT:bRet = Int3ExceptionProc(pExceptionInfo);break;//訪問異常case EXCEPTION_ACCESS_VIOLATION:bRet = AccessExceptionProc(pExceptionInfo);break;//單步執行case EXCEPTION_SINGLE_STEP:bRet = SingleStepExceptionProc(pExceptionInfo);break;}return bRet; }int main(int argc, char* argv[]) {BOOL nIsContinue = TRUE;DEBUG_EVENT debugEvent = {0};BOOL bRet = TRUE;DWORD dwContinue = DBG_CONTINUE;//1.創建調試進程STARTUPINFO startupInfo = {0};PROCESS_INFORMATION pInfo = {0};GetStartupInfo(&startupInfo);bRet = CreateProcess(DEBUGGEE, NULL, NULL, NULL, TRUE, DEBUG_PROCESS || DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &startupInfo, &pInfo);if(!bRet){printf("CreateProcess error: %d \n", GetLastError());return 0;}hDebuggeeProcess = pInfo.hProcess;//2.調試循環while(nIsContinue){bRet = WaitForDebugEvent(&debugEvent, INFINITE);if(!bRet){printf("WaitForDebugEvent error: %d \n", GetLastError());return 0;}switch(debugEvent.dwDebugEventCode){//1.異常case EXCEPTION_DEBUG_EVENT:bRet = ExceptionHandler(&debugEvent);if(!bRet)dwContinue = DBG_EXCEPTION_NOT_HANDLED;break;//2.case CREATE_THREAD_DEBUG_EVENT:break;//3.創建進程case CREATE_PROCESS_DEBUG_EVENT://設置INT 3斷點SetInt3BreakPoint((PCHAR)debugEvent.u.CreateProcessInfo.lpStartAddress);break;//4.case EXIT_THREAD_DEBUG_EVENT:break;//5.case EXIT_PROCESS_DEBUG_EVENT:break;//6.case LOAD_DLL_DEBUG_EVENT:break;//7.case UNLOAD_DLL_DEBUG_EVENT:break;//8.case OUTPUT_DEBUG_STRING_EVENT:break;}bRet = ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);}return 0; }

    運行結果:

    2)多次輸入p并回車單步執行,若執行CALL則會觸發INT 3異常

    3)對照OD查看是否相符

    總結

    以上是生活随笔為你收集整理的软件调试学习笔记(七)—— 单步步入单步步过的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。