客戶端程序無響應,卡死,卡頓了?抓個dump看下卡在哪裏了。

客戶端程序無響應,卡死,卡頓了?抓個dump看下卡在哪裏了。

原文連接windows

工具下載連接api

爲何會無響應

windows的客戶端程序通常都有一個主線程來處理消息,通常都在winmain函數中實現。主線程根據不一樣的消息作不一樣的任務,能夠打開一個新的對話框,建立一個新線程等。用戶的任何點擊(鼠標),滾動(滾動條)操做,都會封裝成一個消息,最終傳給這個線程來處理。若是,這個線程在某個消息響應中,調用了一個阻塞或很耗時的函數,那麼就無法及時獲取並處理下一個消息了(由於還在那個函數裏耗着呢),這個時候表現出來的就是無響應markdown

若是在程序無響應時,抓一個dump,用windbg打印出堆棧來,卡在哪裏會一目瞭然。session

然而,無響應一般時轉瞬即逝的。測試同窗在測試時,若是出現了無響應,會叫你過去,若是運氣好的話,你能夠用任務管理器或其餘的抓dump的工具生成一個dump;運氣很差時,你看到的是程序運行正常。這樣的抓dump,抓到了,很麻煩;抓不到,很抓狂。app

本身寫一個自動檢測並抓dump工具吧

取個名字:dumphungwindow.exe
想一想,工具應知足下面幾個條件:ide

  • 好用,好上手
  • 靜靜的等待無響應出現,抓dump
  • 能夠設定一個dump數量,抓夠了就中止

有了上面的條件,工具如何工做基本就定下來了:
工具應該跑在一個無限循環中,每隔一段時間,就獲取全部的top most window。遍歷這些窗口,用 SendMessageTimeout 給窗口發一個消息,若是超時了(多久超時能夠本身設置),就抓dump。若是dump數量夠了,就退出循環,結束程序。函數

源代碼

(原文裏的代碼,命令行的,本身能夠寫個帶界面的,測試同窗用起來比較方便)工具

/******************************************************************************************************************** Warranty Disclaimer -------------------------- This sample code, utilities, and documentation are provided as is, without warranty of any kind. Microsoft further disclaims all implied warranties including without limitation any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the product and documentation remains with you. In no event shall Microsoft be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample code, utilities, or documentation, even if Microsoft has been advised of the possibility of such damages. Because some states do not allow the exclusion or limitation of liability for consequential or incidental damages, the above limitation may not apply to you. ********************************************************************************************************************/

#include <stdio.h>
#include <windows.h>
#include <dbghelp.h>
#include <psapi.h>

// don't warn about old school strcpy etc.
#pragma warning( disable : 4996 )

int iMaxDump=5;
int iDumpsTaken=0;
int iHangTime=5000;
int iDumpPause=1;
int iScanRate=5000;
HANDLE hEventLog;
char * szDumpLocation;
int FindHungWindows(void);
char * szDumpFileName = 0;
char * szEventInfo = 0;
char * szDumpFinalTarget = 0;
char * szModName = 0;
char * szAppname = 0;
DWORD dwExecOnHang = 0;

#define MAXDUMPFILENAME 1000
#define MAXEVENTINFO 5000
#define MAXDUMPFINALTARGET 2000
#define MAXDUMPLOCATION 1000
#define MAXAPPPATH 1000
#define MAXMODFILENAME 500
#define HMODSIZE 255

int main(int argc, char * argv[])
{
      int i;
      int z;
      size_t j;
      char scan;

      // check to make sure we have dbghelp.dll on the machine.
      if(!LoadLibrary("dbghelp.dll"))
      {
            printf("dbghelp.dll not found please install the debugger tools and place this tool in \r\nthe debugging tools directory or a copy of dbghelp.dll in this tools directory\r\n");
            return 0;
      }

      // Allocate a buffer for our dump location
      szDumpLocation = (char *)malloc(MAXDUMPLOCATION);
      {
            if(!szDumpLocation)
            {
            printf("Failed to alloc buffer for szdumplocation %d",GetLastError());
            return 0;
            }
      }

      szAppname = (char *)malloc(MAXAPPPATH);
      {
            if(!szAppname)
            {
            printf("Failed to alloc buffer for szAppname %d",GetLastError());
            return 0;
            }
      }

      // We use temp path because if we are running under terminal server sessions we want the dump to go to each
      // users secure location, ie. there private temp dir. 
      GetTempPath(MAXDUMPLOCATION, szDumpLocation );

      for (z=0;z<argc;z++)
      {
            switch(argv[z][1])
            {
            case '?':
                  {
                  printf("\n This sample application shows you how to use the debugger \r\n help api to dump an application if it stop responding.\r\n\r\n");
                  printf("\n This tool depends on dbghelp.dll, this comes with the Microsoft debugger tools on www.microsoft.com");
                  printf("\n Please make sure you have the debugger tools installed before running this tool.");
                  printf("\n This tool is based on sample source code and is provided as is without warranty.");
                  printf("\n feel free to contact jeffda@microsoft.com to provide feedback on this sample application\r\n\r\n");
                  printf(" /m[Number] Default is 5 dumps\r\n The max number of dumps to take of hung windows before exiting.\r\n\r\n");
                  printf(" /t[Seconds] Default is 5 seconds\r\n The number of seconds a window must hang before dumping it. \r\n\r\n");
                  printf(" /p[Seconds] Default is 0 seconds\r\n The number of seconds to pause when dumping before continuing scan. \r\n\r\n");
                  printf(" /s[Seconds] Default is 5 seconds.\r\n The scan interval in seconds to wait before rescanning all windows.\r\n\r\n");
                  printf(" /d[DUMP_FILE_PATH] The default is the SystemRoot folder\r\n The path or location to place the dump files. \r\n\r\n");
                  printf(" /e[EXECUTABLE NAME] This allows you to start another program if an application hangs\r\n\r\n");

                  return 0;
                  }
            case 'm':
            case 'M':
                  {
                        iMaxDump = atoi(&argv[z][2]);
                        break;
                  }
            case 't':
            case 'T':
                  {
                        iHangTime= atoi(&argv[z][2]);
                        iHangTime*=1000;
                        break;
                  }
            case 'p':
            case 'P':
                  {
                        iDumpPause= atoi(&argv[z][2]);
                        iDumpPause*=1000;
                        break;           
                  }
            case 's':
            case 'S':
                  {
                        iScanRate = atoi(&argv[z][2]);
                        iScanRate*=1000;             
                        break;
                  }
            case 'd':
            case 'D':
                  { // Dump file directory path
                        strcpy(szDumpLocation,&argv[z][2]);
                        j = strlen(szDumpLocation);

                        if (szDumpLocation[j-1]!='\\')
                        {
                              szDumpLocation[j]='\\';
                              szDumpLocation[j+1]=NULL;
                        }
                        break;
                  }
            case 'e':
            case 'E':
                  { // applicaiton path to exec if hang happens
                        strcpy(szAppname,&argv[z][2]);
                        dwExecOnHang = 1;
                        break;
                  }
            }
      }


      printf("Dumps will be saved in %s\r\n",szDumpLocation);
      puts("scanning for hung windows\n");

      hEventLog = OpenEventLog(NULL, "HungWindowDump");

      i=0;
      scan='*';
      while(1)
      {
            if(i>20)
            {
                  if ('*'==scan)
                  {
                  scan='.';
            }
                  else
                  {
                  scan='*';
            }
                  printf("\r");
            i=0;
            }
            i++;
            putchar(scan);
            if(!FindHungWindows())
            {
                  return 0;
            }
            if (iMaxDump == iDumpsTaken)
            {
                  printf("\r\n%d Dumps taken, exiting\r\n",iDumpsTaken);
                  return 0;
            }
            Sleep(iScanRate);
      }

      free(szDumpLocation);
      return 0;
}

int FindHungWindows(void)
{
DWORD dwResult = 0;
DWORD ProcessId = 0;
DWORD tid = 0;
DWORD dwEventInfoSize = 0;

// Handles
HWND hwnd = 0;
HANDLE hDumpFile = 0;
HANDLE hProcess = 0;
HRESULT hdDump = 0;

SYSTEMTIME SystemTime;
MINIDUMP_TYPE dumptype = (MINIDUMP_TYPE) (MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithUnloadedModules | MiniDumpWithProcessThreadData);

// These buffers are presistant.

// security stuff to report the SID of the dumper to the event log.
PTOKEN_USER pInstTokenUser;
HANDLE ProcessToken;
TOKEN_INFORMATION_CLASS TokenInformationClass = TokenUser;
DWORD ReturnLength =0;

// This allows us to get the first window in the chain of top windows.
hwnd = GetTopWindow(NULL);
if(!hwnd)
{
      printf("Could not GetTopWindow\r\n");
      return 0;
}

// We will iterate through all windows until we get to the end of the list.
while(hwnd)
{
      // Get the process ID for the current window 
      tid = GetWindowThreadProcessId(hwnd, &ProcessId);

      // Sent a message to this window with our timeout. 
      // If it times out we consider the window hung
      if (!SendMessageTimeout(hwnd, WM_NULL, 0, 0, SMTO_BLOCK, iHangTime, &dwResult))
      {
            // SentMessageTimeout can fail for other reasons, 
            // if it's not a timeout we exit try again later
            if(ERROR_TIMEOUT != GetLastError())
            {
                  printf("SendMessageTimeout has failed with error %d\r\n",GetLastError());
                  return 1;
            }
                  // Iint our static buffers points.
                  // On our first trip through if we have not
                  // malloced memory for our buffers do so now.
                  if(!szModName)
                  {
                        szModName = (char *)malloc(MAXMODFILENAME);
                        {
                              if(!szModName)
                              {
                              printf("Failed to alloc buffer for szModName %d",GetLastError());
                              return 0;
                              }
                        }
                  }
                  if(!szDumpFileName)// first time through malloc a buffer.
                  {
                        szDumpFileName = (char *)malloc(MAXDUMPFINALTARGET);
                        {
                              if(!szDumpFileName)
                              {
                                    printf("Failed to alloc buffer for dumpfilename %d",GetLastError());
                                    return 0;
                              }
                        }
                  }
                  if(!szDumpFinalTarget)// first time through malloc a buffer.
                  {
                        szDumpFinalTarget= (char *)malloc(MAXDUMPFINALTARGET);
                        {
                              if(!szDumpFinalTarget)
                              {
                              printf("Failed to alloc buffer for dumpfiledirectory %d",GetLastError());
                              return 0;
                              }
                        }
                  }
                  if(!szEventInfo)
                  {
                        szEventInfo= (char *)malloc(MAXEVENTINFO);
                        {
                              if(!szEventInfo)
                              {
                              printf("Failed to alloc buffer for szEventInfo %d",GetLastError());
                              return 0;
                              }
                        }
                  }
                  // End of initial buffer allocations.

            GetLocalTime (&SystemTime);

            // Using the process id we open the process for various tasks.
            hProcess = OpenProcess(PROCESS_ALL_ACCESS,NULL,ProcessId);
            if(!hProcess )
            {
                  printf("Open process of hung window failed with error %d\r\n",GetLastError());
                  return 1;
            }
            // What is the name of the executable?
            GetModuleBaseName( hProcess, NULL, szModName,MAXMODFILENAME);

            printf("\r\n\r\nHung Window found dumping process (%d) %s\n",ProcessId,szModName);

            // Here we build the dump file name time, date, pid and binary name
            sprintf(szDumpFileName,"HWNDDump_Day%d_%d_%d_Time%d_%d_%d_Pid%d_%s.dmp",SystemTime.wMonth,SystemTime.wDay,SystemTime.wYear,SystemTime.wHour,SystemTime.wMinute,SystemTime.wSecond,ProcessId,szModName);
            strcpy(szDumpFinalTarget,szDumpLocation);
            strcat(szDumpFinalTarget,szDumpFileName);

            // We have to create the file and then pass it's handle to the dump api
            hDumpFile = CreateFile(szDumpFinalTarget,FILE_ALL_ACCESS,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
            if(!hDumpFile)
            {
                  printf("CreateFile failed to open dump file at location %s, with error %d\r\n",szDumpLocation,GetLastError());
                  return 0;
            }

            printf("Dumping unresponsive process\r\n%s",szDumpFinalTarget);

            // This dump api will halt the target process while it writes it's
            // image to disk in the form a dump file.
            // this can be opened later by windbg or cdb for debugging.
            if(!MiniDumpWriteDump(hProcess,ProcessId,hDumpFile,dumptype ,NULL,NULL,NULL))
            {
                  // We do this on failure
                  hdDump = HRESULT_FROM_WIN32(GetLastError());
                  printf("MiniDumpWriteDump failed with a hresult of %d last error %d\r\n",hdDump,GetLastError());
                  CloseHandle (hDumpFile);
                  return 0;
            }
            else
            {
                  // If we are here the dump worked. Now we need to notify the machine admin by putting a event in
                  // the application event log so someone knows a dump was taken and where it is stored.
                  sprintf(szEventInfo,"An application hang was caught by findhungwind.exe, the process was dumped to %s",szDumpFinalTarget);

                  // We need to get the process token so we can get the user sit so ReportEvent will have the
                  // User name / account in the event log.
                  if (OpenProcessToken(hProcess,      TOKEN_QUERY,&ProcessToken ) )
                  {
                        // Make the firt call to findout how big the sid needs to be. 
                        GetTokenInformation(ProcessToken,TokenInformationClass, NULL,NULL,&ReturnLength);
                        pInstTokenUser = (PTOKEN_USER) malloc(ReturnLength);
                        if(!pInstTokenUser)
                        {
                              printf("Failed to malloc buffer for InstTokenUser exiting error %d\r\n",GetLastError());
                              return 0;
                        }
                        if(!GetTokenInformation(ProcessToken,TokenInformationClass, (VOID *)pInstTokenUser,ReturnLength,&ReturnLength))
                        {
                              printf("GetTokenInformation failed with error %d\r\n",GetLastError());
                              return 0;
                        }
                  }
                  // write the application event log message. 
                  // This will show up as source DumpHungWindow
                  dwEventInfoSize=(DWORD)strlen(szEventInfo);

                  ReportEvent(hEventLog,EVENTLOG_WARNING_TYPE,1,1,pInstTokenUser->User.Sid,NULL,dwEventInfoSize,NULL,szEventInfo);

                  // Free to token buffer, we don't want to leak anything.
                  free(pInstTokenUser);

                  // In additon to leaking a handle if you don't close the handle
                  // you may not get the dump to flush to the hard drive.
                  CloseHandle (hDumpFile);
                  printf("\r\nDump complete");

                  // This allows you to execute something if you get a hang like crash.exe
                  if (dwExecOnHang)
                  {
                        system(szAppname);
                  }

                  // The Sleep is here so in the event you want to wait N seconds
                  // before collecting another dump
                  // you can pause. This is helpful if you want to see if any
                  // forward progress is happening over time

                  Sleep(iDumpPause);
            }
            // Once we are at our threadshold for max dumps
            // we exit so we do not fill up the hard drive.
            iDumpsTaken++;
            if (iMaxDump == iDumpsTaken)
            {
                  return 0;
            }
        }
        // This is where we traverse to the next window.
            hwnd = GetNextWindow(hwnd, GW_HWNDNEXT);
      }
      return 1;
}
相關文章
相關標籤/搜索