In earlier posts, I developed a function that uses ToolHelpAPI to list the currently running processes.  That was just the beginning of what can be done with the ToolHelpAPI, so in this post I will extend that to create a Process Viewer.

The first thing that I will do is modify my Serial Debug Menu to remove the ShowRunningProcesses() call and replace it with a call to a sub-menu named ProcessViewerMenu().


void MainMenu()
{
 TCHAR *MenuStrings[] = {
   TEXT( "Get Tick Count"),
   TEXT( "Process Viewer"),
   NULL
  };
 int Done = FALSE;

 while( ! Done )
 {
  switch( MenuHandler( MenuStrings ) )
  {
   case 0:
    RETAILMSG(1,(TEXT("Tick Count %X\n"), GetTickCount() ));
    break;
   case 1:
    ProcessViewerMenu();
    break;
   default:
    Done = TRUE;
    break;
  }
 }
}

In ProcessViewerMenu(), I will add three new menu items to the original "Show Running Processes" that I removed from the main menu.  These are:

 TCHAR *MenuStrings[] = {
   TEXT( "Show Running Processes"),
   TEXT( "Show Running Processes and Modules"),
   TEXT( "Show Running Processes and Heaps"),
   TEXT( "Show Running Processes and Threads"),
   NULL
  };

So I will add output for Modules (loaded DLLs), heaps and threads to the process viewer.  For these to be of value, they should be listed with the process, so I will extend ShowRunningProcesses() to add the information with the process list as an option.  To do that, I will add a parameter to the function which will be a set of flags.  So the new function signature is:

void ShowRunningProcesses(DWORD Flags)

I will use the same flags that ToolHelpAPi uses for CreateToolhelp32Snapshot(), but I will only support the following; TH32CS_SNAPMODULE, TH32CS_SNAPHEAPLIST and TH32CS_SNAPTHREAD.  They can be used individually, or combined by or'ing them together.  But you will see that each one creates quite a bit of output, so combining them may not make sense.  The new ShowRunningProcesses() is:

void ShowRunningProcesses(DWORD Flags)
{
 PROCESSENTRY32 *CurrentProcess;
 PROCESSENTRY32 Process[ MAX_PROCESSES ];
 DWORD ProcessCount;
 DWORD index ;
 DWORD MaxProcessNameLength;

 ProcessCount = GetRunningProcesses( Process );
 MaxProcessNameLength = GetMaxProcessNameLength( Process, ProcessCount );

 RETAILMSG( 1,(TEXT("%-*s %8s %13s %9s %9s %10s\n"),
     MaxProcessNameLength, TEXT("Process"),
     TEXT("PID"),
     TEXT("Base Priority"),
     TEXT("# Threads"),
     TEXT("Base Addr"),
     TEXT("Access Key")
     )) ;
 for( index = 0; index < ProcessCount; index++ )
 {
  CurrentProcess = &(Process[ index ] );
  RETAILMSG(1, ( TEXT("%-*s %8X %13d %9d %9X %10X\n"),
    MaxProcessNameLength, CurrentProcess->szExeFile,
    CurrentProcess->th32ProcessID,
    CurrentProcess->pcPriClassBase,
    CurrentProcess->cntThreads,
    CurrentProcess->th32MemoryBase,
    CurrentProcess->th32AccessKey
    ));
  if( Flags & TH32CS_SNAPMODULE )
   OutputModules( CurrentProcess->th32ProcessID );
  if( Flags & TH32CS_SNAPHEAPLIST )
   OutputHeap( CurrentProcess->th32ProcessID );
  if( Flags & TH32CS_SNAPTHREAD )
   OutputThreads( CurrentProcess->th32ProcessID );
 }
}

For each processes, if one of the flags is set call on the appropriate function to display the information.

The first flag is TH32CS_SNAPMODULE which causes OutputModules() to be called.  OutputModules will use the Module32First() and Module32Next() to enumerate the loaded modules.  This function looks a lot like GetRunningProcesses() that I wrote in a previous post.  The difference is that outputs the modules as it finds them.  There are more members of MODULEENTRY32 than the ones that I am outputting, but I didn't find much value in them.

static void OutputModules( DWORD ProcessID)
{
 HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, ProcessID);
 MODULEENTRY32 CurrentModule;

 if(hSnapShot == (HANDLE)-1)
 {
  RETAILMSG( 1,
   (TEXT("OutputModules: Failed CreateToolhelp32Snapshot Error: %d\n"),
   GetLastError()));
  return;
 }

 memset(&CurrentModule,0,sizeof(MODULEENTRY32));

 CurrentModule.dwSize = sizeof(MODULEENTRY32);
 if(Module32First(hSnapShot, &CurrentModule))
 {
  // Section header
  RETAILMSG(1, ( TEXT("\t%8s %8s %8s %8s %s\n"),
   TEXT("Mod ID"),
   TEXT("Base Add"),
   TEXT("Size"),
   TEXT("HANDLE"),
   TEXT("Module Name")
   ));
  while(TRUE)
  {
   
   RETAILMSG(1, ( TEXT("\t%8X %8X %8X %8X %s\n"),
    CurrentModule.th32ModuleID,
    CurrentModule.modBaseAddr,
    CurrentModule.modBaseSize,
    CurrentModule.hModule,
    CurrentModule.szModule
     )) ;
   if(!Module32Next(hSnapShot, &CurrentModule))
   {
    break;
   }
  }
 }
 CloseToolhelp32Snapshot (hSnapShot);
}

The next flag is TH32CS_SNAPHEAPLIST, which is more involved to handle.  The reason is that we must first enumerate the heaps using Heap32ListFirst() and Heap32ListNext(), then enumerate the allocations using Heap32First() and Heap32Next().  This creates a lot of output and in its raw form may not be much value.  I will leave it to you to process the data to analyze it.  OutputHeap() is:

static void OutputHeap( DWORD ProcessID)
{
 HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST, ProcessID);
 HEAPLIST32 CurrentHeap;

 if(hSnapShot == (HANDLE)-1)
 {
  RETAILMSG( 1,
   (TEXT("OutputThreads: Failed CreateToolhelp32Snapshot Error: %d\n"),
   GetLastError()));
  return;
 }

 memset(&CurrentHeap,0,sizeof(HEAPLIST32));

 CurrentHeap.dwSize = sizeof(HEAPLIST32);
 if(Heap32ListFirst(hSnapShot, &CurrentHeap))
 {
   RETAILMSG(1, ( TEXT("\t%8s\n"),
    TEXT("Heap ID")
    ));
  while(TRUE)
  {
   HEAPENTRY32 CurrentHeapEntry;

   memset(&CurrentHeapEntry,0,sizeof(HEAPENTRY32));

   CurrentHeapEntry.dwSize = sizeof(CurrentHeapEntry);
   
   RETAILMSG(1, ( TEXT("\t%8X\n"),
    CurrentHeap.th32HeapID
     )) ;

   if(Heap32First( hSnapShot, &CurrentHeapEntry, ProcessID, CurrentHeap.th32HeapID ) )
   {
    // Section header
    RETAILMSG(1, ( TEXT("\t\t%8s %8s %8s %8s %8s %8s\n"),
     TEXT("Handle"),
     TEXT("Address"),
     TEXT("Size"),
     TEXT("Flags"),
     TEXT("Lock Cnt") ,
     TEXT("ProcessID")
    )) ;

    while( TRUE )
    {
     RETAILMSG(1, ( TEXT("\t\t%8X %8X %8X %8X %8X %8X\n"),
      CurrentHeapEntry.hHandle,
      CurrentHeapEntry.dwAddress,
      CurrentHeapEntry.dwBlockSize,
      CurrentHeapEntry.dwFlags,
      CurrentHeapEntry.dwLockCount ,
      CurrentHeapEntry.th32ProcessID
     )) ;
     
     if(!Heap32Next( hSnapShot, &CurrentHeapEntry))
     {
      break;
     }
    }
   }
   if(!Heap32ListNext(hSnapShot, &CurrentHeap))
   {
    break;
   }
  }
 }
 CloseToolhelp32Snapshot (hSnapShot);
}

The last flag is TH32CS_SNAPTHREAD, which is similar to handling processes and modules, except that the snapshot includes threads for all processes, so we need to filter them.

static void OutputThreads( DWORD ProcessID)
{
 // Passing in the ProcessID, but it is ignored by CreateToolhelp32Snapshot()
 HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, ProcessID);
 THREADENTRY32 CurrentThread;

 if(hSnapShot == (HANDLE)-1)
 {
  RETAILMSG( 1,
   (TEXT("OutputThreads: Failed CreateToolhelp32Snapshot Error: %d\n"),
   GetLastError()));
  return;
 }

 memset(&CurrentThread,0,sizeof(THREADENTRY32));

 CurrentThread.dwSize = sizeof(THREADENTRY32);
 if(Thread32First(hSnapShot, &CurrentThread))
 {
   // Section header on two lines
   RETAILMSG(1, ( TEXT("\t%8s %8s %8s %8s %8s %8s %8s %8s\n"),
    TEXT("Usage"),
    TEXT("Thread"),
    TEXT("Owner"),
    TEXT("Base"),
    TEXT("Delta"),
    TEXT("Flags"),
    TEXT("Access"),
    TEXT("Current")
    ));
   RETAILMSG(1, ( TEXT("\t%8s %8s %8s %8s %8s %8s %8s %s\n"),
    TEXT("Count"),
    TEXT("ID"),
    TEXT(" "),
    TEXT("Priority"),
    TEXT("Priority"),
    TEXT(" "),
    TEXT("Key"),
    TEXT("Thread ID")
    ));
  while(TRUE)
  {
   // Filter the threads for just this process
   if( CurrentThread.th32OwnerProcessID == ProcessID )
   { 
    RETAILMSG(1, ( TEXT("\t%8X %8X %8X %8d %8d %8X %8X %8X\n"),
     CurrentThread.cntUsage,
     CurrentThread.th32ThreadID,
     CurrentThread.th32OwnerProcessID,
     CurrentThread.tpBasePri,
     CurrentThread.tpDeltaPri,
     CurrentThread.dwFlags,
     CurrentThread.th32AccessKey,
     CurrentThread.th32CurrentProcessID
     )) ;
   }
   if(!Thread32Next(hSnapShot, &CurrentThread))
   {
    break;
   }
  }
 }
 CloseToolhelp32Snapshot (hSnapShot);
}


Finally, the body of ProcessViewerMenu() is:

void ProcessViewerMenu()
{
 TCHAR *MenuStrings[] = {
   TEXT( "Show Running Processes"),
   TEXT( "Show Running Processes and Modules"),
   TEXT( "Show Running Processes and Heaps"),
   TEXT( "Show Running Processes and Threads"),
   NULL
  };
 int Done = FALSE;

 while( ! Done )
 {
  switch( MenuHandler( MenuStrings ) )
  {
   case 0:
    ShowRunningProcesses(0);
    break;
   case 1:
    ShowRunningProcesses(TH32CS_SNAPMODULE);
    break;
   case 2:
    ShowRunningProcesses(TH32CS_SNAPHEAPLIST);
    break;
   case 3:
    ShowRunningProcesses(TH32CS_SNAPTHREAD);
    break;
   default:
    Done = TRUE;
    break;
  }
 }
}

Copyright © 2008 – Bruce Eitman
All Rights Reserved