We help IT Professionals succeed at work.

Windows 98 / DebugActiveProcess / Suspending threads

helpmealot
helpmealot asked
on
Greetings!

Background:
Windows 98 SE
Visual C++ 6.0 using MFC w/ SP5

I'm in the process of writing a program that:
- Starts another program via CreateProcess (the program is started with the main thread in a suspended state using "CREATE_SUSPENDED").
- Attaches to this program using DebugActiveProcess.  It attaches in a separate thread, thus, one thread is for the UI of my program, the other is for receiving and dealing with debug events.
- My program then can suspend/resume ALL threads running in the process that my program has attached to.
- The user interface provides buttons for: Beginning program execution (in a suspended state), resuming the program, suspending the program, quitting.  It also supplies edit controls for input of the program path, command line, and folder to start in.

This is the idea anyway.  The program does not seem to work quite right though.  I think I've almost got it, but not quite.

Here is most of the code for the program:

#define WM_THREADQUIT (WM_USER + 1)
struct THREAD_PARAM
{ PROCESS_INFORMATION *m_BWPI;
  HWND m_Wnd;

  int m_NumThreads;
  HANDLE m_Threads[32];
};

void CBWFreezeDlg::OnButtonCreate()
{ UpdateData (TRUE);

  STARTUPINFO si;
  memset (&si, 0, sizeof (STARTUPINFO));

  BOOL ret = CreateProcess (m_Exe, (char *) m_CLine.operator LPCTSTR (), NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, m_CurDir, &si, &m_BWPI);
  if (ret == FALSE)
  { MessageBox ("Executing the program failed :(");
    return;
  }

  m_Param.m_BWPI = &m_BWPI;
  m_Param.m_Wnd = m_hWnd;
  m_Param.m_Threads[0] = m_BWPI.hThread;
  m_Param.m_NumThreads = 1;
  m_Thread = AfxBeginThread ((AFX_THREADPROC) ThreadProc, &m_Param);

  // Set button state
  m_BtnCreate.EnableWindow (FALSE);
  m_BtnResume.EnableWindow (TRUE);
  m_BtnSuspend.EnableWindow (FALSE);
}

void CBWFreezeDlg::OnButtonResume()
{ int i;
  for (i = 0; i < m_Param.m_NumThreads; i++)
  { ResumeThread (m_Param.m_Threads[i]); }

  // Set button state
  m_BtnCreate.EnableWindow (FALSE);
  m_BtnResume.EnableWindow (FALSE);
  m_BtnSuspend.EnableWindow (TRUE);
}

void CBWFreezeDlg::OnButtonSuspend()
{ int i;
  for (i = 0; i < m_Param.m_NumThreads; i++)
  { SuspendThread (m_Param.m_Threads[i]); }

  // Set button state
  m_BtnCreate.EnableWindow (FALSE);
  m_BtnResume.EnableWindow (TRUE);
  m_BtnSuspend.EnableWindow (FALSE);
}

void CBWFreezeDlg::OnThreadQuit (WPARAM, LPARAM)
{ Sleep (1000);     // TODO replace with WaitForSingleObject
  m_Thread = NULL;
}

int ThreadProc (LPVOID p)
{ THREAD_PARAM *param = (THREAD_PARAM *) p;
  DebugActiveProcess (param->m_BWPI->dwProcessId);
 
  DEBUG_EVENT DebugEv;                   // debugging event information
  DWORD dwContinueStatus = DBG_CONTINUE; // exception continuation

  int i;
  bool quit = false;

  HANDLE Process = NULL;

  HANDLE DLLs[128];
  LPVOID DLLsBase[128];
  int idlls = 0;
  for (i = 0; i < 128; i++)
  { DLLsBase[i] = 0; }

  for (; quit == false;)
  { // Wait for a debugging event to occur. The second parameter indicates
    // that the function does not return until a debugging event occurs.
    WaitForDebugEvent (&DebugEv, INFINITE);

    // Process the debugging event code.
    switch (DebugEv.dwDebugEventCode)
    { case CREATE_PROCESS_DEBUG_EVENT:
      // As needed, examine or change the registers of the
      // process's initial thread with the GetThreadContext and
      // SetThreadContext functions; read from and write to the
      // process's virtual memory with the ReadProcessMemory and
      // WriteProcessMemory functions; and suspend and resume
      // thread execution with the SuspendThread and ResumeThread
      // functions.
      if (Process == NULL)
      { Process = DebugEv.u.CreateProcessInfo.hProcess; }
      break;

      case EXIT_PROCESS_DEBUG_EVENT:
      // Display the process's exit code.
      quit = true;
      break;

      case CREATE_THREAD_DEBUG_EVENT:
      // As needed, examine or change the thread's registers
      // with the GetThreadContext and SetThreadContext functions;
      // and suspend and resume thread execution with the
      // SuspendThread and ResumeThread functions.
      param->m_Threads[param->m_NumThreads] = DebugEv.u.CreateThread.hThread;
      param->m_NumThreads++;
      break;

      case EXIT_THREAD_DEBUG_EVENT:
      // Display the thread's exit code.
      break;

      case LOAD_DLL_DEBUG_EVENT:
      // Read the debugging information included in the newly
      // loaded DLL.
      DLLs[idlls++] = DebugEv.u.LoadDll.hFile;
      break;

      case UNLOAD_DLL_DEBUG_EVENT:
      { int i;
        for (i = 0; i < 128; i++)
        { if (DLLsBase[i] == DebugEv.u.UnloadDll.lpBaseOfDll)
          { CloseHandle (DLLs[i]);
            break;
          }
        }

        break;
      }

      case OUTPUT_DEBUG_STRING_EVENT:
      // Display the output debugging string.
      break;
    }

    // Resume executing the thread that reported the debugging event.
    ContinueDebugEvent (DebugEv.dwProcessId, DebugEv.dwThreadId, dwContinueStatus);
  }

  PostMessage (param->m_Wnd, WM_THREADQUIT, 0, 0);
  return 0;
}

void CBWFreezeDlg::OnButtonExit()
{ if (m_Thread == NULL)
  { OnOK (); }
}

My question is: What is the matter with this code and what should I do to fix it?  It just doesn't seem to work as I described it.  If I start a program with it (suspended), and then click the "resume" button, the program might remain frozen even though this should not occur (it should simply run as if nothing were the matter).

Additional information (added sept. 29): The program seems to fail only when the program it is attempting to debug is multithreaded.  Single threaded programs start fine.  Multithreaded ones can only be closed by "End task".
Comment
Watch Question

CERTIFIED EXPERT
Author of the Year 2009

Commented:
Hmmm EE guildlines show 50 pts as standard price for an "Easy Question".  Like "How do i get printf to print on a new line" or "How can I get the sum of two integers.  The first Integer is 2 and the second integer is 2?"

This question is about stopping and starting threads and  subtlties of the DebugActiveProcess API -- which is typically used in WRITING A DEBUGGER.  Hello?  Hello?  Hello?  Are you there?  Hello?

-- Dan

Author

Commented:
I apologize.  You are correct.  It should be 100 points.  I hesitate to assign any more than that since I already have the code written out and the idea formulated.  It is just a matter of pointing out where the problem lies.

Author

Commented:
This program really is quite simple though, there is very little code in the entire thing as its purpose is very narrow-minded (I am only writing it for one specific reason).
CERTIFIED EXPERT
Author of the Year 2009
Commented:
I ran some tests.  This code just reveals each debug event in the treace window.  When I run it with NOTEPAD, everything iis copacetic.  But with Explorer, I get an EXIT_PROCESS_DEBUG_EVENT immediately.  Perhaps that is what's happening to you.
=-=-=-=-=-=-
Note that I create the process in the monitoring thread.  MDSN mentions that the DEBUG_PROCESS flag will only get valid WaitForDebugEvent actions when called from the creating thread.

"
If you create a process with this flag set, only the calling thread (the thread that called CreateProcess) can call the WaitForDebugEvent function.
"
=-=-=-=-=-=-=-=-=-=-=-=-=-=- My test code
HANDLE gahThreads[ 128];
int    gnThdCnt= 0;

int MyThreadProc (LPVOID p)
{
   STARTUPINFO si;
    memset (&si, 0, sizeof (STARTUPINFO));
    si.cb= sizeof(STARTUPINFO);

    char szProgFile[]= "c:\\windows\\notepad.exe";
//  char szProgFile[]= "c:\\windows\\explorer.exe";
    char szDir[]=      "c:\\temp";

    BOOL ret = CreateProcess (szProgFile, "", NULL, NULL, FALSE,  DEBUG_PROCESS, NULL, szDir, &si, &grPI);
//  BOOL ret = CreateProcess (szProgFile, "", NULL, NULL, FALSE,  CREATE_SUSPENDED, NULL, szDir, &si, &grPI);
//  BOOL ret = CreateProcess (szProgFile, "", NULL, NULL, FALSE,  0, NULL, szDir, &si, &grPI);
    if (ret == FALSE) {
        AfxMessageBox ("Executing the program failed :(");
        return(1);
    }
 
    DEBUG_EVENT DebugEv;                   // debugging event information
    DWORD dwContinueStatus = DBG_CONTINUE; // exception continuation
    bool quit = false;

    while( ! quit ) {
        BOOL fRet= WaitForDebugEvent (&DebugEv, INFINITE);
        ASSERT( fRet ); // with INFINITE, should always succeed
        switch (DebugEv.dwDebugEventCode) {
            case CREATE_PROCESS_DEBUG_EVENT:
                TRACE("CREATE_PROCESS_DEBUG_EVENT\n");
            break;
            case EXIT_PROCESS_DEBUG_EVENT:
                quit= true;
                TRACE("EXIT_PROCESS_DEBUG_EVENT\n");
            break;
            case CREATE_THREAD_DEBUG_EVENT:
                gahThreads[ gnThdCnt]= DebugEv.u.CreateThread.hThread;
                gnThdCnt++;
                TRACE("CREATE_THREAD_DEBUG_EVENT\n");
            break;

            case EXIT_THREAD_DEBUG_EVENT:
                TRACE("EXIT_THREAD_DEBUG_EVENT\n");
            break;

            case LOAD_DLL_DEBUG_EVENT:
                TRACE("LOAD_DLL_DEBUG_EVENT\n");
            break;

            case UNLOAD_DLL_DEBUG_EVENT:
                TRACE("UNLOAD_DLL_DEBUG_EVENT\n");
            break;

            case OUTPUT_DEBUG_STRING_EVENT:
                TRACE("OUTPUT_DEBUG_STRING_EVENT\n");
            break;
            case EXCEPTION_DEBUG_EVENT:
                TRACE("EXCEPTION_DEBUG_EVENT\n");
            break;
            case RIP_EVENT:
                TRACE("RIP_EVENT\n");
            break;
            default:
                TRACE("! Unknown DEBUG_EVENC value\n");
            break;
        } // switch
        ContinueDebugEvent (DebugEv.dwProcessId, DebugEv.dwThreadId, dwContinueStatus);
    } // whle ! quit
//    PostMessage (param->m_Wnd, WM_THREADQUIT, 0, 0);
    return 0;
}

=-=-=-=-=-=-=-
Here is a tip:
Check return codes from ALL API fns!  You were getting back FALSE from WaitForDebugEvent()
 
Another tip:
Place breakpoints in your code and examine variables.  It became obvious real quick that the DEBUG_EVENT structure was filled with garbage.

-- Dan

Author

Commented:
Thank you for your information, it has helped immensely.  I have not yet finished quite what I set out to do, but I will award you with the points since it has answered my question.  I may still have a few more small additional questions.

Author

Commented:
Nope, no further questions, it works perfectly now.  Thank you!