6

I have a problem that may be fairly unique. I have an application that runs on a headless box for long hours when I am not present, but is not critical. I would like to be able to debug this application remotely using Visual Studio. In order to do so, I have code that looks like this:

// Suspend all other threads to prevent loss
// of state while we investigate the issue.
SuspendAllButCurrentThread();
var remoteDebuggerProcess = new Process
    {
        StartInfo =
            {
                UseShellExecute = true,
                FileName = MsVsMonPath;
            }
    };
// Exception handling and early return removed here for brevity.
remoteDebuggerProcess.Start();

// Wait for a debugger attach.
while (!Debugger.IsAttached)
{
    Thread.Sleep(500);
}
Debugger.Break();

// Once we get here, we've hit continue in the debugger. Restore all of our threads,
// then get rid of the remote debugging tools.
ResumeAllButCurrentThread();

remoteDebuggerProcess.CloseMainWindow();
remoteDebuggerProcess.WaitForExit();

The idea being that this way, I hit an error while I am away, and the application effectively pauses itself and waits for a remote debugger attach, which after the first continue automatically gets the right context thanks to the Debugger.Break call.

Here is the problem: Implementing SuspendAllButCurrentThread turns out to be nontrivial. Thread.Suspend is deprecated, and I can't P/Invoke down to SuspendThread because there's no one-to-one mapping between managed threads and native threads (since I need to keep the current thread alive). I don't want to install Visual Studio on the machine in question if it can possibly be avoided. How can I make this work?

Octavianus
  • 421
  • 4
  • 12
  • Stupid question probably, but doesn't VS allow remote debugging without you needing to jump through these hoops? I always assumed you could remotely debug without code changes, but i must admit i've only ever debugged locally, – Surfbutler Aug 17 '13 at 09:14

2 Answers2

5

I can't P/Invoke down to SuspendThread because there's no one-to-one mapping between managed threads and native threads

You can't enumerate managed threads either, only unmanaged threads. There actually is a one-to-one mapping between them, they just made it hard to find it. The original intent was to allow creating a custom CLR host that didn't use operating system threads to implement Thread, a request by the SQL Server group that wanted to use fibers instead. That never worked out, they could not get it reliable enough. No actual CLR host exists that doesn't use real operating system threads.

So you can actually use Process.GetCurrentProcess().Threads to enumerate all your threads. And avoid suspending your own by pinvoking GetCurrentThreadId(), comparing it to ProcessThread.Id

How reliable that's going to be is a guess, don't try to do anything drastic like sending an alert to remind you that it is time to attach the debugger. You may have well suspended a thread that was executing code inside Windows and acquired a global lock. As well as a CLR worker thread, like the finalizer thread or the background GC thread.

The better approach is to use a separate guard process that does all this, just like a debugger will. Use a named EventWaitHandle that you create in the guard program and OpenExisting() in your main program. The guard program needs to WaitAny() on that wait handle as well as the process. Your main program can now simply call Set() to wake up the guard program. Which can now safely suspend all threads.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Suppose that I do suspend something inside Windows with a global lock - how does a debugger avoid hitting this problem? Secondly, in this instance, I would P/Invoke down the `SuspendThread` as well, rather than using the deprecated `Thread.Suspend`, right? – Octavianus Aug 17 '13 at 14:53
  • It doesn't avoid it. Which is why a debugger must always be a separate process. Yes, pinvoke it. – Hans Passant Aug 17 '13 at 15:02
  • Okay, so you mean a global lock specific to the process? Alternatively to this, is there a way as a debugger to register for notification of another debugger attempting to attach? – Octavianus Aug 17 '13 at 15:06
  • Locks are always specific to a process, including the Windows global locks. You wouldn't be able to Kill() a process if that wouldn't work that way. Debuggers cannot interact. I added another paragraph with an alternative. – Hans Passant Aug 17 '13 at 15:09
1

The main problem with Thread.Suspend is that it can leave some object in unusable state.

From the documentation:

Do not use the Suspend and Resume methods to synchronize the activities of threads. You have no way of knowing what code a thread is executing when you suspend it. If you suspend a thread while it holds locks during a security permission evaluation, other threads in the AppDomain might be blocked. If you suspend a thread while it is executing a class constructor, other threads in the AppDomain that attempt to use that class are blocked. Deadlocks can occur very easily.

So when you will try to view contents of such yet unusable object you'll probably get locked too. Therefore, regardless of what you use to suspend other threads you could end up in the same scenario. And so, the only possibility is to modify the implementation of other threads to be able to ask them to suspend themselves: Is there a way to indefinitely pause a thread?.

Community
  • 1
  • 1
BartoszKP
  • 34,786
  • 15
  • 102
  • 130