Fix CLI and MI/GDB protocols exit behavior in case debuggee process was attached,
will detach now instead of terminate attached debuggee process.
return true;
}
+bool ManagedCallback::CallbacksWorkerCreateProcess()
+{
+ m_debugger.NotifyProcessCreated();
+ return false;
+}
+
void ManagedCallback::CallbacksWorker()
{
std::unique_lock<std::mutex> lock(m_callbacksMutex);
case CallbackQueueCall::Exception:
m_stopEventInProcess = CallbacksWorkerException(c.iCorAppDomain, c.iCorThread, c.EventType, c.ExcModule);
break;
+ case CallbackQueueCall::CreateProcess:
+ m_stopEventInProcess = CallbacksWorkerCreateProcess();
+ break;
default:
// finish loop
// called from destructor only, don't need call pop()
// https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugmanagedcallback-createprocess-method
// Notifies the debugger when a process has been attached or started for the first time.
+// Remarks
+// This method is not called until the common language runtime is initialized. Most of the ICorDebug methods will return CORDBG_E_NOTREADY before the CreateProcess callback.
HRESULT STDMETHODCALLTYPE ManagedCallback::CreateProcess(ICorDebugProcess *pProcess)
{
LogFuncEntry();
- m_debugger.NotifyProcessCreated();
+
+ // Important! Care about callback queue before NotifyProcessCreated() call.
+ // In case of `attach`, NotifyProcessCreated() call will notify debugger that debuggee process attached and debugger
+ // should stop debuggee process by dirrect `Pause()` call. From another side, callback queue have bunch of asynchronous
+ // added entries and, for example, `CreateThread()` could be called after this callback and broke our debugger logic.
+ ToRelease<ICorDebugAppDomainEnum> domains;
+ ICorDebugAppDomain *pAppDomain;
+ ULONG domainsFetched;
+ if (SUCCEEDED(pProcess->EnumerateAppDomains(&domains)))
+ {
+ // At this point we have only one domain for sure.
+ if (SUCCEEDED(domains->Next(1, &pAppDomain, &domainsFetched)) && domainsFetched == 1)
+ {
+ // Don't AddRef() here for pAppDomain! We get it with AddRef() from Next() and will release in m_callbacksQueue by ToRelease destructor.
+ return AddCallbackToQueue(pAppDomain, [&]()
+ {
+ m_callbacksQueue.emplace_back(CallbackQueueCall::CreateProcess, pAppDomain, nullptr, nullptr, STEP_NORMAL, ExceptionCallbackType::FIRST_CHANCE);
+ });
+ }
+ }
+
return ContinueProcessWithCallbacksQueue(pProcess);
}
Breakpoint,
StepComplete,
Break,
- Exception
+ Exception,
+ CreateProcess
};
struct CallbackQueueEntry
bool CallbacksWorkerStepComplete(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, CorDebugStepReason reason);
bool CallbacksWorkerBreak(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread);
bool CallbacksWorkerException(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, ExceptionCallbackType eventType, const std::string &excModule);
+ bool CallbacksWorkerCreateProcess();
HRESULT AddCallbackToQueue(ICorDebugAppDomain *pAppDomain, std::function<void()> callback);
bool HasQueuedCallbacks(ICorDebugProcess *pProcess);
HRESULT ContinueAppDomainWithCallbacksQueue(ICorDebugAppDomain *pAppDomain);
namespace
{
+ const auto startupWaitTimeout = std::chrono::milliseconds(5000);
const std::string envDOTNET_STARTUP_HOOKS = "DOTNET_STARTUP_HOOKS";
#ifdef FEATURE_PAL
void ManagedDebugger::NotifyProcessCreated()
{
- std::lock_guard<std::mutex> lock(m_processAttachedMutex);
+ std::unique_lock<std::mutex> lock(m_processAttachedMutex);
m_processAttachedState = ProcessAttachedState::Attached;
+ lock.unlock();
+ m_processAttachedCV.notify_one();
}
void ManagedDebugger::NotifyProcessExited()
m_justMyCode(true),
m_stepFiltering(true),
m_hotReload(false),
- m_startupReady(false),
- m_startupResult(S_OK),
m_unregisterToken(nullptr),
m_processId(0),
m_ioredirect(
{
ManagedDebugger *self = static_cast<ManagedDebugger*>(parameter);
- std::unique_lock<std::mutex> lock(self->m_startupMutex);
-
- self->m_startupResult = FAILED(hr) ? hr : self->Startup(pCordb, self->m_processId);
- self->m_startupReady = true;
+ self->Startup(pCordb, self->m_processId);
if (self->m_unregisterToken)
{
self->m_dbgshim.UnregisterForRuntimeStartup(self->m_unregisterToken);
self->m_unregisterToken = nullptr;
}
-
- lock.unlock();
- self->m_startupCV.notify_one();
}
// From dbgshim.cpp
HRESULT ManagedDebugger::RunProcess(const std::string& fileExec, const std::vector<std::string>& execArgs)
{
- static const auto startupCallbackWaitTimeout = std::chrono::milliseconds(5000);
HRESULT Status;
IfFailRet(CheckNoProcess());
ss << " \"" << EscapeShellArg(arg) << "\"";
}
- m_startupReady = false;
m_clrPath.clear();
HANDLE resumeHandle = 0; // Fake thread handle for the process resume
IfFailRet(m_dbgshim.ResumeProcess(resumeHandle));
m_dbgshim.CloseResumeHandle(resumeHandle);
- // Wait for ManagedDebugger::StartupCallback to complete
-
- /// FIXME: if the process exits too soon the ManagedDebugger::StartupCallback()
- /// is never called (bug in dbgshim?).
- /// The workaround is to wait with timeout.
- const auto now = std::chrono::system_clock::now();
-
- std::unique_lock<std::mutex> lock(m_startupMutex);
- if (!m_startupCV.wait_until(lock, now + startupCallbackWaitTimeout, [this](){return m_startupReady;}))
- {
- // Timed out
- m_dbgshim.UnregisterForRuntimeStartup(m_unregisterToken);
- m_unregisterToken = nullptr;
+ std::unique_lock<std::mutex> lockAttachedMutex(m_processAttachedMutex);
+ if (!m_processAttachedCV.wait_for(lockAttachedMutex, startupWaitTimeout, [this]{return m_processAttachedState == ProcessAttachedState::Attached;}))
return E_FAIL;
- }
- if (SUCCEEDED(m_startupResult))
- m_sharedProtocol->EmitExecEvent(PID{m_processId}, fileExec);
+ m_sharedProtocol->EmitExecEvent(PID{m_processId}, fileExec);
- return m_startupResult;
+ return S_OK;
}
HRESULT ManagedDebugger::CheckNoProcess()
IfFailRet(m_dbgshim.CreateDebuggingInterfaceFromVersionEx(CorDebugVersion_4_0, pBuffer, &pCordb));
m_unregisterToken = nullptr;
- return Startup(pCordb, pid);
+ IfFailRet(Startup(pCordb, pid));
+
+ std::unique_lock<std::mutex> lockAttachedMutex(m_processAttachedMutex);
+ if (!m_processAttachedCV.wait_for(lockAttachedMutex, startupWaitTimeout, [this]{return m_processAttachedState == ProcessAttachedState::Attached;}))
+ return E_FAIL;
+
+ return S_OK;
}
HRESULT ManagedDebugger::GetExceptionInfo(ThreadId threadId, ExceptionInfo &exceptionInfo)
bool m_stepFiltering;
bool m_hotReload;
- std::mutex m_startupMutex;
- std::condition_variable m_startupCV;
- bool m_startupReady;
- HRESULT m_startupResult;
-
PVOID m_unregisterToken;
DWORD m_processId;
std::string m_clrPath;
if (run || pidDebuggee)
cliProtocol->SetRunningState();
+ if (pidDebuggee != 0)
+ cliProtocol->Pause();
+
// run commands passed in command line via '-ex' option
cliProtocol->Source({initCommands});
}
// no mutex locking needed here
m_exit = true;
m_sources.reset(nullptr);
- m_sharedDebugger->Disconnect(IDebugger::DisconnectAction::DisconnectTerminate);
+ m_sharedDebugger->Disconnect(); // Terminate debuggee process if debugger ran this process and detach in case debugger was attached to it.
return S_OK;
}
applyCommandMode();
lock.unlock();
- Status = m_sharedDebugger->ConfigurationDone();
- if (SUCCEEDED(Status))
- {
- output = "^running";
-
- lock.lock();
- m_processStatus = Running;
- m_state_cv.notify_all();
- }
- return Status;
+ IfFailRet(m_sharedDebugger->ConfigurationDone());
+ IfFailRet(m_sharedDebugger->Pause(ThreadId::AllThreads));
+ return S_OK;
}
template <>
printf("^exit\n");
- m_sharedDebugger->Disconnect(IDebugger::DisconnectAction::DisconnectTerminate);
+ m_sharedDebugger->Disconnect(); // Terminate debuggee process if debugger ran this process and detach in case debugger was attached to it.
linenoiseHistorySave(HistoryFileName);
linenoiseHistoryFree();
virtual ~LineReader() {}
};
+ // pause debugee execution
+ void Pause();
+
private:
// This function should be used by any others CLIProtocol's functions
// to read input lines for interpreting (commands, etc...)
static CLIProtocol* g_console_owner;
static std::mutex g_console_mutex; // mutex which protect g_console_owner
- // pause debugee execution
- void Pause();
-
// process Ctrl-C events
static void interruptHandler();
return variablesHandle.DeleteVar(args.at(0));
}},
{ "gdb-exit", [&](const std::vector<std::string> &args, std::string &output) -> HRESULT {
- sharedDebugger->Disconnect(IDebugger::DisconnectAction::DisconnectTerminate);
+ sharedDebugger->Disconnect(); // Terminate debuggee process if debugger ran this process and detach in case debugger was attached to it.
return S_OK;
}},
{ "file-exec-and-symbols", [&](const std::vector<std::string> &args, std::string &output) -> HRESULT {
}
if (!m_exit)
- m_sharedDebugger->Disconnect(IDebugger::DisconnectAction::DisconnectTerminate);
+ m_sharedDebugger->Disconnect(); // Terminate debuggee process if debugger ran this process and detach in case debugger was attached to it.
Printf("%s^exit\n", token.c_str());
Printf("(gdb)\n");