```
command alias args
--------------------------
-backtrace bt Print backtrace info.
+backtrace bt [all] Print backtrace info.
break b <loc> Set breakpoint at specified location, where the
- location might be filename.cs:line or function name.
+ location might be source_file_name:line or function name.
Optional, module name also could be provided as part
- of location: module.dll!filename.cs:line
+ of location: module.dll!source_file_name:line
catch Set exception breakpoints.
continue c Continue debugging after stop/pause.
delete clear <num> Delete breakpoint with specified number.
// Must be called only in case all threads stopped and fixed (see InteropDebugger::StopAndDetach()).\r
void InteropRendezvousBreakpoint::RemoveAtDetach(pid_t pid)\r
{\r
- m_sharedInteropBreakpoints->Remove(pid, m_brkAddr, [](){}, [](std::uintptr_t){});\r
+ if (pid != 0)\r
+ {\r
+ m_sharedInteropBreakpoints->Remove(pid, m_brkAddr, [](){}, [](std::uintptr_t){});\r
+ }\r
\r
m_rendezvousAddr = 0;\r
m_rendezvousBrkState = 0;\r
return m_sharedInteropBreakpoints->StepPrevToBrk(pid, brkAddr);
}
-void Breakpoints::InteropStepOverBrk(pid_t pid, std::uintptr_t brkAddr)
+void Breakpoints::InteropStepOverBrk(pid_t pid, std::uintptr_t brkAddr, std::function<bool(pid_t, std::uintptr_t)> SingleStepOnBrk)
{
- m_sharedInteropBreakpoints->StepOverBrk(pid, brkAddr);
+ m_sharedInteropBreakpoints->StepOverBrk(pid, brkAddr, SingleStepOnBrk);
}
// Must be called only in case all threads stopped and fixed (see InteropDebugger::StopAndDetach()).
// Note, this method will reset PC in case thread stop at breakpoint and alter `regs`.
bool InteropStepPrevToBrk(pid_t pid, std::uintptr_t brkAddr);
// Execute real breakpoint's code with single step.
- void InteropStepOverBrk(pid_t pid, std::uintptr_t brkAddr);
+ void InteropStepOverBrk(pid_t pid, std::uintptr_t brkAddr, std::function<bool(pid_t, std::uintptr_t)> SingleStepOnBrk);
// Remove all native breakpoints at interop detach.
void InteropRemoveAllAtDetach(pid_t pid);
// Resolve breakpoints for module.
return m_currentBreakpointsInMemory.find(brkAddr) != m_currentBreakpointsInMemory.end();\r
}\r
\r
-void InteropBreakpoints::StepOverBrk(pid_t pid, std::uintptr_t brkAddr)\r
+void InteropBreakpoints::StepOverBrk(pid_t pid, std::uintptr_t brkAddr, std::function<bool(pid_t, std::uintptr_t)> SingleStepOnBrk)\r
{\r
std::lock_guard<std::recursive_mutex> lock(m_breakpointsMutex);\r
\r
if (find == m_currentBreakpointsInMemory.end())\r
return;\r
\r
- if (!InteropDebugging::StepOverBrk(pid, brkAddr, find->second.m_savedData))\r
+ if (!InteropDebugging::StepOverBrk(pid, brkAddr, find->second.m_savedData, SingleStepOnBrk))\r
std::abort(); // Fatal error, we already logged all data about this error.\r
}\r
\r
// Remove all native breakpoints at interop detach.\r
void RemoveAllAtDetach(pid_t pid);\r
bool IsBreakpoint(std::uintptr_t brkAddr);\r
- void StepOverBrk(pid_t pid, std::uintptr_t brkAddr);\r
+ void StepOverBrk(pid_t pid, std::uintptr_t brkAddr, std::function<bool(pid_t, std::uintptr_t)> SingleStepOnBrk);\r
// Return `false` in case no breakpoint with this PC was found (step is not possible).\r
bool StepPrevToBrk(pid_t pid, std::uintptr_t brkAddr);\r
// Remove all related to unloaded library breakpoints entries in data structures.\r
{\r
m_breakpointsMutex.lock();\r
\r
- for (const auto &entry : m_lineResolvedBreakpoints)\r
+ if (pid != 0)\r
{\r
- for (const auto &bp : entry.second)\r
+ for (const auto &entry : m_lineResolvedBreakpoints)\r
{\r
- if (bp.m_enabled)\r
- m_sharedInteropBreakpoints->Remove(pid, entry.first, [](){}, [](std::uintptr_t){});\r
+ for (const auto &bp : entry.second)\r
+ {\r
+ if (bp.m_enabled)\r
+ m_sharedInteropBreakpoints->Remove(pid, entry.first, [](){}, [](std::uintptr_t){});\r
+ }\r
}\r
}\r
m_lineResolvedBreakpoints.clear();\r
if (SUCCEEDED(pThread->GetActiveFrame(&pFrame)) && pFrame != nullptr)
m_debugger.GetFrameLocation(pFrame, threadId, FrameLevel(0), event.frame);
+#ifdef INTEROP_DEBUGGING
+ StopAllNativeThreads();
+#endif // INTEROP_DEBUGGING
+
m_debugger.SetLastStoppedThread(pThread);
- m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
+ m_debugger.pProtocol->EmitStoppedEvent(event);
m_debugger.m_ioredirect.async_cancel();
return true;
}
StoppedEvent event(StopStep, threadId);
event.frame = stackFrame;
+#ifdef INTEROP_DEBUGGING
+ StopAllNativeThreads();
+#endif // INTEROP_DEBUGGING
+
m_debugger.SetLastStoppedThread(pThread);
- m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
+ m_debugger.pProtocol->EmitStoppedEvent(event);
m_debugger.m_ioredirect.async_cancel();
return true;
}
if (SUCCEEDED(pThread->GetActiveFrame(&iCorFrame)) && iCorFrame != nullptr)
m_debugger.GetFrameLocation(iCorFrame, threadId, FrameLevel(0), stackFrame);
+#ifdef INTEROP_DEBUGGING
+ StopAllNativeThreads();
+#endif // INTEROP_DEBUGGING
+
StoppedEvent event(StopPause, threadId);
event.frame = stackFrame;
- m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
+ m_debugger.pProtocol->EmitStoppedEvent(event);
m_debugger.m_ioredirect.async_cancel();
return true;
}
// Disable all steppers if we stop during step.
m_debugger.m_uniqueSteppers->DisableAllSteppers(pAppDomain);
- m_debugger.SetLastStoppedThread(pThread);
+#ifdef INTEROP_DEBUGGING
+ StopAllNativeThreads();
+#endif // INTEROP_DEBUGGING
- m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
+ m_debugger.SetLastStoppedThread(pThread);
+ m_debugger.pProtocol->EmitStoppedEvent(event);
m_debugger.m_ioredirect.async_cancel();
return true;
}
case CallbackQueueCall::InteropBreakpoint:
m_stopEventInProcess = CallbacksWorkerInteropBreakpoint(c.pid, c.addr);
break;
+ case CallbackQueueCall::InteropSignal:
+ m_stopEventInProcess = CallbacksWorkerInteropSignal(c.pid, c.addr, c.signal);
+ break;
#endif // INTEROP_DEBUGGING
default:
// finish loop
{
#ifdef INTEROP_DEBUGGING
if (m_debugger.m_interopDebugging)
- m_debugger.m_uniqueInteropDebugger->ContinueAllThreadsWithEvents();
+ m_debugger.m_sharedInteropDebugger->ContinueAllThreadsWithEvents();
if (iCorAppDomain) // last stop event was managed
{
{
#ifdef INTEROP_DEBUGGING
if (m_debugger.m_interopDebugging)
- m_debugger.m_uniqueInteropDebugger->ContinueAllThreadsWithEvents();
+ m_debugger.m_sharedInteropDebugger->ContinueAllThreadsWithEvents();
#endif // INTEROP_DEBUGGING
return pProcess->Continue(0);
return S_OK;
}
-// Caller should care about m_callbacksMutex.
+// NOTE caller must care about m_callbacksMutex.
// Check stop status and stop, if need.
// Return S_FALSE in case already was stopped, S_OK in case stopped by this call.
static HRESULT InternalStop(ICorDebugProcess *pProcess, bool &stopEventInProcess)
}
// Stop process and set last stopped thread. If `lastStoppedThread` not passed value from protocol, find best thread.
-HRESULT CallbacksQueue::Pause(ICorDebugProcess *pProcess, ThreadId lastStoppedThread)
+HRESULT CallbacksQueue::Pause(ICorDebugProcess *pProcess, ThreadId lastStoppedThread, EventFormat eventFormat)
{
// Must be real thread ID or ThreadId::AllThreads.
if (!lastStoppedThread)
if (Status == S_FALSE) // Already stopped.
return S_OK;
+#ifdef INTEROP_DEBUGGING
+ if (m_debugger.m_interopDebugging)
+ IfFailRet(m_debugger.m_sharedInteropDebugger->StopAllNativeThreads(pProcess));
+#endif // INTEROP_DEBUGGING
+
// Same logic as provide vsdbg in case of pause during stepping.
m_debugger.m_uniqueSteppers->DisableAllSteppers(pProcess);
{
// VSCode protocol event must provide thread only (VSCode count on this), even if this thread don't have user code.
m_debugger.SetLastStoppedThreadId(lastStoppedThread);
- m_debugger.m_sharedProtocol->EmitStoppedEvent(StoppedEvent(StopPause, lastStoppedThread));
+ m_debugger.pProtocol->EmitStoppedEvent(StoppedEvent(StopPause, lastStoppedThread));
+ m_debugger.m_ioredirect.async_cancel();
+ return S_OK;
+ }
+ }
+ else if (eventFormat == EventFormat::CLI)
+ {
+ // CLI protocol provide ThreadId::AllThreads as lastStoppedThread, stop at main thread with real top frame in event.
+ m_debugger.SetLastStoppedThreadId(threads[0].id);
+
+ int totalFrames = 0;
+ std::vector<StackFrame> stackFrames;
+ if (SUCCEEDED(m_debugger.GetStackTrace(threads[0].id, FrameLevel(0), 1, stackFrames, totalFrames)))
+ {
+ StoppedEvent event(StopPause, threads[0].id);
+ event.frame = stackFrames[0];
+ m_debugger.pProtocol->EmitStoppedEvent(event);
m_debugger.m_ioredirect.async_cancel();
return S_OK;
}
}
else
{
- // MI and CLI protocols provide ThreadId::AllThreads as lastStoppedThread, this protocols require thread and frame with user code.
+ // MI protocol provide ThreadId::AllThreads as lastStoppedThread, this protocols require thread and frame with user code.
// Note, MIEngine (MI/GDB) require frame connected to user source or it will crash Visual Studio.
ThreadId lastStoppedId = m_debugger.GetLastStoppedThreadId();
StoppedEvent event(StopPause, thread.id);
event.frame = stackFrame;
m_debugger.SetLastStoppedThreadId(thread.id);
- m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
+ m_debugger.pProtocol->EmitStoppedEvent(event);
m_debugger.m_ioredirect.async_cancel();
return S_OK;
}
m_debugger.SetLastStoppedThreadId(ThreadId(pid));
- if (FAILED(m_debugger.m_uniqueInteropDebugger->GetFrameForAddr(brkAddr, event.frame)))
+ if (FAILED(m_debugger.m_sharedInteropDebugger->GetFrameForAddr(brkAddr, event.frame)))
{
event.frame.source = event.breakpoint.source;
event.frame.line = event.breakpoint.line;
}
- m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
+ m_debugger.pProtocol->EmitStoppedEvent(event);
+ m_debugger.m_ioredirect.async_cancel();
+ return true;
+}
+
+bool CallbacksQueue::CallbacksWorkerInteropSignal(pid_t pid, std::uintptr_t breakAddr, const std::string &signal)
+{
+ ThreadId threadId(pid);
+ StoppedEvent event(StopPause, threadId);
+
+ // Disable all steppers if we stop at breakpoint during step.
+ m_debugger.m_debugProcessRWLock.reader.lock();
+ if (m_debugger.m_iCorProcess)
+ {
+ m_debugger.m_uniqueSteppers->DisableAllSteppers(m_debugger.m_iCorProcess);
+ }
+ m_debugger.m_debugProcessRWLock.reader.unlock();
+
+ m_debugger.SetLastStoppedThreadId(ThreadId(pid));
+
+ if (FAILED(m_debugger.m_sharedInteropDebugger->GetFrameForAddr(breakAddr, event.frame)))
+ {
+ event.frame.source = event.breakpoint.source;
+ event.frame.line = event.breakpoint.line;
+ }
+
+ event.signal_name = signal;
+ m_debugger.pProtocol->EmitStoppedEvent(event);
m_debugger.m_ioredirect.async_cancel();
return true;
}
{
std::unique_lock<std::mutex> lock(m_callbacksMutex);
- callback(); // Caller could add entries into m_callbacksQueue (this is why m_callbacksMutex cover this call).
+ callback(); // Caller should add entries into m_callbacksQueue (this is why m_callbacksMutex cover this call).
+ assert(!m_callbacksQueue.empty());
- if (!m_callbacksQueue.empty())
+ // NOTE
+ // In case `m_stopEventInProcess` is `true`, process have "stopped" status for sure, but could already execute some eval (this is OK, do not stop managed part!).
+ // No need to check `IsEvalRunning()` here, since this code covered by `m_callbacksMutex` (that mean, breakpoint condition check with eval not running now for sure).
+ if (!m_stopEventInProcess)
{
- // NOTE
- // In case `m_stopEventInProcess` is `true`, process have "stopped" status for sure, but could already execute some eval (this is OK, do not stop managed part!).
- // No need to check `IsEvalRunning()` here, since this code covered by `m_callbacksMutex` (that mean, breakpoint condition check with eval not running now for sure).
- if (!m_stopEventInProcess)
+ BOOL procRunning = FALSE;
+ m_debugger.m_debugProcessRWLock.reader.lock();
+ if (m_debugger.m_iCorProcess)
{
- BOOL procRunning = FALSE;
- m_debugger.m_debugProcessRWLock.reader.lock();
- if (m_debugger.m_iCorProcess && SUCCEEDED(m_debugger.m_iCorProcess->IsRunning(&procRunning)) && procRunning == TRUE)
- {
+ if (SUCCEEDED(m_debugger.m_iCorProcess->IsRunning(&procRunning)) && procRunning == TRUE)
m_debugger.m_iCorProcess->Stop(0);
- }
- m_debugger.m_debugProcessRWLock.reader.unlock();
- }
- m_callbacksCV.notify_one(); // notify_one with lock
+ // Early stop of native code at native event, in case this will be not stop event - we will silently continue native code execution.
+ m_debugger.m_sharedInteropDebugger->StopAllNativeThreads(m_debugger.m_iCorProcess);
+ }
+ m_debugger.m_debugProcessRWLock.reader.unlock();
}
+ m_callbacksCV.notify_one(); // notify_one with lock
return S_OK;
}
// NOTE caller must care about m_callbacksMutex.
-void CallbacksQueue::EmplaceBackInterop(CallbackQueueCall Call, pid_t pid, std::uintptr_t addr)
+void CallbacksQueue::EmplaceBackInterop(CallbackQueueCall Call, pid_t pid, std::uintptr_t addr, const std::string &signal)
+{
+ m_callbacksQueue.emplace_back(Call, pid, addr, signal);
+}
+
+// NOTE caller must care about m_callbacksMutex.
+void CallbacksQueue::StopAllNativeThreads()
{
- m_callbacksQueue.emplace_back(Call, pid, addr);
+ if (!m_debugger.m_interopDebugging || m_stopEventInProcess)
+ return;
+
+ m_debugger.m_debugProcessRWLock.reader.lock();
+ if (m_debugger.m_iCorProcess)
+ {
+ m_debugger.m_sharedInteropDebugger->StopAllNativeThreads(m_debugger.m_iCorProcess);
+ }
+ m_debugger.m_debugProcessRWLock.reader.unlock();
}
#endif // INTEROP_DEBUGGING
CreateProcess
#ifdef INTEROP_DEBUGGING
, InteropBreakpoint
+ , InteropSignal
#endif // INTEROP_DEBUGGING
};
{
public:
- CallbacksQueue(ManagedDebugger &debugger) :
+ CallbacksQueue(ManagedDebuggerHelpers &debugger) :
m_debugger(debugger), m_stopEventInProcess(false), m_callbacksWorker{&CallbacksQueue::CallbacksWorker, this} {}
~CallbacksQueue();
bool IsRunning();
HRESULT Continue(ICorDebugProcess *pProcess);
// Stop process and set last stopped thread. If `lastStoppedThread` not passed value from protocol, find best thread.
- HRESULT Pause(ICorDebugProcess *pProcess, ThreadId lastStoppedThread);
+ HRESULT Pause(ICorDebugProcess *pProcess, ThreadId lastStoppedThread, EventFormat eventFormat);
// Analog of "pProcess->Stop(0)" call that also care about callbacks.
HRESULT Stop(ICorDebugProcess *pProcess);
CorDebugStepReason Reason, ExceptionCallbackType EventType, const std::string &ExcModule = std::string{});
#ifdef INTEROP_DEBUGGING
HRESULT AddInteropCallbackToQueue(std::function<void()> callback);
- void EmplaceBackInterop(CallbackQueueCall Call, pid_t pid, std::uintptr_t addr);
+ void EmplaceBackInterop(CallbackQueueCall Call, pid_t pid, std::uintptr_t addr, const std::string &signal);
#endif // INTEROP_DEBUGGING
private:
- ManagedDebugger &m_debugger;
+ ManagedDebuggerHelpers &m_debugger;
// NOTE we have one entry type for both (managed and interop) callbacks (stop events),
// since almost all the time we have CallbackQueue with 1 entry only, no reason complicate code.
#ifdef INTEROP_DEBUGGING
pid_t pid = 0; // Initial value in order to suppress static analyzer warnings.
std::uintptr_t addr = 0; // Initial value in order to suppress static analyzer warnings.
+ std::string signal;
CallbackQueueEntry(CallbackQueueCall call,
pid_t pid_,
- std::uintptr_t addr_) :
+ std::uintptr_t addr_,
+ const std::string &signal_) :
Call(call),
pid(pid_),
- addr(addr_)
+ addr(addr_),
+ signal(signal_)
{}
#endif // INTEROP_DEBUGGING
};
#ifdef INTEROP_DEBUGGING
bool CallbacksWorkerInteropBreakpoint(pid_t pid, std::uintptr_t brkAddr);
+ bool CallbacksWorkerInteropSignal(pid_t pid, std::uintptr_t breakAddr, const std::string &signal);
+ void StopAllNativeThreads();
#endif // INTEROP_DEBUGGING
};
case E_UNEXPECTED:
output = "Evaluation timed out, but function evaluation can't be completed or aborted. Debuggee have inconsistent state now.";
break;
+ case COR_E_THREADSTATE:
+ output = "Thread is in an invalid state for this operation.";
default:
break;
}
m_evalData.pEvalWaiter = m_sharedEvalWaiter.get();\r
}\r
\r
+ void ResetEval()\r
+ {\r
+ m_sharedEvaluator.reset();\r
+ m_sharedEvalHelpers.reset();\r
+ m_sharedEvalWaiter.reset();\r
+ m_evalData.pEvaluator = nullptr;\r
+ m_evalData.pEvalHelpers = nullptr;\r
+ m_evalData.pEvalWaiter = nullptr;\r
+ }\r
+\r
// Evaluate expression. Optional, return `editable` state and in case result is property - setter related information.\r
HRESULT EvaluateExpression(ICorDebugThread *pThread, FrameLevel frameLevel, int evalFlags, const std::string &expression, ICorDebugValue **ppResultValue,\r
std::string &output, bool *editable = nullptr, std::unique_ptr<Evaluator::SetterData> *resultSetterData = nullptr);\r
\r
#include "debugger/evalwaiter.h"\r
#include "utils/platform.h"\r
+#include "debugger/threads.h"\r
+#ifdef INTEROP_DEBUGGING\r
+#include "debugger/interop_debugging.h"\r
+#endif // INTEROP_DEBUGGING\r
\r
namespace netcoredbg\r
{\r
\r
return !!m_evalResult ? m_evalResult->threadId : 0;\r
}\r
+\r
+void EvalWaiter::SetInteropDebugger(std::shared_ptr<InteropDebugging::InteropDebugger> &sharedInteropDebugger)\r
+{\r
+ m_sharedInteropDebugger = sharedInteropDebugger;\r
+}\r
+\r
+void EvalWaiter::ResetInteropDebugger()\r
+{\r
+ m_sharedInteropDebugger.reset();\r
+}\r
#endif // INTEROP_DEBUGGING\r
\r
void EvalWaiter::CancelEvalRunning()\r
DWORD evalThreadId = 0;\r
IfFailRet(pThread->GetID(&evalThreadId));\r
\r
+#ifdef INTEROP_DEBUGGING\r
+ assert(!!m_sharedInteropDebugger);\r
+ if (m_sharedInteropDebugger->IsNativeThreadStopped((pid_t)evalThreadId))\r
+ return COR_E_THREADSTATE;\r
+#endif // INTEROP_DEBUGGING\r
+\r
// Note, we need suspend during eval all managed threads, that not used for eval (delegates, reverse pinvokes, managed threads).\r
auto ChangeThreadsState = [&](CorDebugThreadState state)\r
{\r
namespace netcoredbg\r
{\r
\r
+class Threads;\r
+#ifdef INTEROP_DEBUGGING\r
+namespace InteropDebugging\r
+{\r
+class InteropDebugger;\r
+}\r
+#endif // INTEROP_DEBUGGING\r
+\r
+\r
class EvalWaiter\r
{\r
public:\r
bool IsEvalRunning();\r
#ifdef INTEROP_DEBUGGING\r
DWORD GetEvalRunningThreadID();\r
+ void SetInteropDebugger(std::shared_ptr<InteropDebugging::InteropDebugger> &sharedInteropDebugger);\r
+ void ResetInteropDebugger();\r
#endif // INTEROP_DEBUGGING\r
void CancelEvalRunning();\r
ICorDebugEval *FindEvalForThread(ICorDebugThread *pThread);\r
ToRelease<ICorDebugClass> m_iCorCrossThreadDependencyNotification;\r
HRESULT SetEnableCustomNotification(ICorDebugProcess *pProcess, BOOL fEnable);\r
\r
+#ifdef INTEROP_DEBUGGING\r
+ std::shared_ptr<InteropDebugging::InteropDebugger> m_sharedInteropDebugger;\r
+#endif // INTEROP_DEBUGGING\r
+\r
struct evalResultData_t\r
{\r
ToRelease<ICorDebugValue> iCorEval;\r
#endif
}
+// Return break address by current PC.
+std::uintptr_t GetBreakAddrByPC(const user_regs_struct ®s)
+{
+#if DEBUGGER_UNIX_AMD64
+ return std::uintptr_t(regs.rip);
+#elif DEBUGGER_UNIX_X86
+ return std::uintptr_t(regs.eip);
+#elif DEBUGGER_UNIX_ARM64
+ return std::uintptr_t(regs.pc);
+#elif DEBUGGER_UNIX_ARM
+ const static int REG_PC = 15;
+ return std::uintptr_t(regs.uregs[REG_PC]);
+#else
+#error "Unsupported platform"
+#endif
+}
+
#if DEBUGGER_UNIX_ARM
static bool IsThumbOpcode32Bits(word_t data)
{
#endif
}
-bool StepOverBrk(pid_t pid, std::uintptr_t addr, word_t restoreData)
+bool StepOverBrk(pid_t pid, std::uintptr_t addr, word_t restoreData, std::function<bool(pid_t, std::uintptr_t)> SingleStepOnBrk)
{
// We have 2 cases here (at breakpoint stop):
// * x86/amd64 already changed PC (executed 0xCC code), so, SetPrevBrkPC() call will change PC in our stored registers
return false;
}
- // single step
- if (async_ptrace(PTRACE_SINGLESTEP, pid, nullptr, nullptr) == -1)
- {
- LOGE("Ptrace singlestep error: %s\n", strerror(errno));
- return false;
- }
-
- int wait_status;
- if (GetWaitpid()(pid, &wait_status, 0) == -1)
- {
- LOGE("Waitpid error: %s\n", strerror(errno));
+ if (!SingleStepOnBrk(pid, addr))
return false;
- }
-
- if (WSTOPSIG(wait_status) != SIGTRAP)
- {
- LOGE("Failed with single step, stop signal=%u", WSTOPSIG(wait_status));
- return false;
- }
// setup bp again
if (async_ptrace(PTRACE_POKEDATA, pid, (void*)addr, (void*)brkData) == -1)
#ifdef INTEROP_DEBUGGING
#include "debugger/interop_ptrace_helpers.h"
+#include <functional>
namespace netcoredbg
{
bool NeedSetPrevBrkPC(); // return true if at least one register should be changed
void SetPrevBrkPC(user_regs_struct ®s);
std::uintptr_t GetBrkAddrByPC(const user_regs_struct ®s);
+ std::uintptr_t GetBreakAddrByPC(const user_regs_struct ®s);
word_t EncodeBrkOpcode(word_t data, bool thumbCode);
word_t RestoredOpcode(word_t dataWithBrk, word_t restoreData);
- bool StepOverBrk(pid_t pid, std::uintptr_t addr, word_t restoreData);
+ bool StepOverBrk(pid_t pid, std::uintptr_t addr, word_t restoreData, std::function<bool(pid_t, std::uintptr_t)> SingleStepOnBrk);
} // namespace InteropDebugging
} // namespace netcoredbg
} // unnamed namespace
-InteropDebugger::InteropDebugger(std::shared_ptr<IProtocol> &sharedProtocol,
- std::shared_ptr<Breakpoints> &sharedBreakpoints,
- std::shared_ptr<EvalWaiter> &sharedEvalWaiter) :
- m_sharedProtocol(sharedProtocol),
+InteropDebuggerBase::InteropDebuggerBase(IProtocol *pProtocol_,
+ std::shared_ptr<Breakpoints> &sharedBreakpoints,
+ std::shared_ptr<EvalWaiter> &sharedEvalWaiter) :
+ pProtocol(pProtocol_),
m_sharedBreakpoints(sharedBreakpoints),
m_uniqueInteropLibraries(new InteropLibraries()),
m_sharedEvalWaiter(sharedEvalWaiter)
{}
+InteropDebuggerSignals::InteropDebuggerSignals(IProtocol *pProtocol_,
+ std::shared_ptr<Breakpoints> &sharedBreakpoints,
+ std::shared_ptr<EvalWaiter> &sharedEvalWaiter) :
+ InteropDebuggerBase(pProtocol_, sharedBreakpoints, sharedEvalWaiter)
+{}
+
+InteropDebuggerHelpers::InteropDebuggerHelpers(IProtocol *pProtocol_,
+ std::shared_ptr<Breakpoints> &sharedBreakpoints,
+ std::shared_ptr<EvalWaiter> &sharedEvalWaiter) :
+ InteropDebuggerSignals(pProtocol_, sharedBreakpoints, sharedEvalWaiter)
+{}
+
+InteropDebugger::InteropDebugger(IProtocol *pProtocol_,
+ std::shared_ptr<Breakpoints> &sharedBreakpoints,
+ std::shared_ptr<EvalWaiter> &sharedEvalWaiter) :
+ InteropDebuggerHelpers(pProtocol_, sharedBreakpoints, sharedEvalWaiter)
+{}
+
// NOTE caller must care about m_waitpidMutex.
-void InteropDebugger::WaitThreadStop(pid_t stoppedPid)
+bool InteropDebuggerBase::SingleStepOnBrk(pid_t pid, std::uintptr_t addr)
{
- if (stoppedPid == g_waitForAllThreads)
+ // We may have situation, when we check thread status with internal structure, but thread already stopped at breakpoint
+ // and we send to this thread PTRACE_INTERRUPT (since internal structure was not updated yet at next waitpid() cycle).
+ // In this case, at ptrace(PTRACE_SINGLESTEP) thread will be stopped by PTRACE_INTERRUPT and ptrace(PTRACE_SINGLESTEP)
+ // call must be repeated in oreder to finally make single step.
+
+ // Another case is signal that landed on stopped thread. For example SIGKILL from user or SIGILL
+ // due to wrong instruction that was covered by breakpoint opcode and now executed on single step.
+
+ if (async_ptrace(PTRACE_SINGLESTEP, pid, nullptr, nullptr) == -1)
{
- if (std::find_if(m_TIDs.begin(), m_TIDs.end(), [](std::pair<pid_t, thread_status_t> entry){return entry.second.stat == thread_stat_e::running;}) == m_TIDs.end())
- return;
+ LOGE("Ptrace singlestep error: %s\n", strerror(errno));
+ return false;
}
else
{
- if (m_TIDs[stoppedPid].stat != thread_stat_e::running)
- return;
+ m_TIDs[pid].stat = thread_stat_e::running;
+ m_TIDs[pid].stop_signal = 0;
}
+ WaitThreadStop(pid);
+
+ // Check that we still have this thread alive.
+ if (m_TIDs.find(pid) == m_TIDs.end())
+ return false;
+
+ if (m_TIDs[pid].stop_signal == SIGTRAP &&
+ m_TIDs[pid].event == 0) // not ptrace event (breakpoint, step)
+ {
+ siginfo_t ptrace_info;
+ if (async_ptrace(PTRACE_GETSIGINFO, pid, nullptr, &ptrace_info) == -1)
+ {
+ LOGW("Ptrace getsiginfo error: %s\n", strerror(errno));
+ return false;
+ }
+
+ switch (ptrace_info.si_code)
+ {
+ case SI_KERNEL:
+ case TRAP_BRKPT:
+ // Care about `__builtin_debugtrap()` in user code.
+ m_TIDs[pid].stat = thread_stat_e::stopped_signal_event_detected;
+ m_TIDs[pid].stop_event_data.signal = "SIGTRAP";
+ m_eventedThreads.emplace_back(pid);
+ return true;
+
+ case TRAP_TRACE: // single step
+ m_TIDs[pid].stop_signal = 0;
+ return true;
+ }
+ }
+ else if (m_TIDs[pid].stop_signal == SIGILL)
+ {
+ siginfo_t ptrace_info;
+ if (async_ptrace(PTRACE_GETSIGINFO, pid, nullptr, &ptrace_info) == -1)
+ {
+ LOGW("Ptrace getsiginfo error: %s\n", strerror(errno));
+ return false;
+ }
+
+ if (ptrace_info.si_code == TRAP_TRACE)
+ {
+ // Care about `__builtin_trap()` in user code.
+ m_TIDs[pid].stat = thread_stat_e::stopped_signal_event_detected;
+ m_TIDs[pid].stop_event_data.signal = "SIGILL";
+ m_eventedThreads.emplace_back(pid);
+ return true;
+ }
+ }
+
+ // Got some signal, that must be handled first.
+ // In this case we don't execute sigle step again, since this could be SIGILL for initial wrong opcode, but restore breakoint in memory.
+ // We will care about this signal and after that will step over breakpoint if this breakpoint happens again on this thread at next code
+ // execution continue (take into account, that breakpoint could be removed before next continue, for example).
+ m_TIDs[pid].addrStepOverBreakpointFailed = addr;
+ return true;
+}
+
+// NOTE caller must care about m_waitpidMutex.
+void InteropDebuggerBase::WaitThreadStop(pid_t stoppedPid, std::vector<pid_t> *stoppedTreads)
+{
+ auto AllRequestedThreadsNotRunning = [&]() -> bool
+ {
+ if (stoppedTreads != nullptr)
+ {
+ if (std::find_if(stoppedTreads->begin(), stoppedTreads->end(), [this](pid_t entry)
+ {// NOTE some threads could exit, don't create m_TIDs entry by m_TIDs[] request.
+ auto find = m_TIDs.find(entry);
+ return find == m_TIDs.end() ? false : find->second.stat == thread_stat_e::running;
+ }) == stoppedTreads->end())
+ return true;
+ }
+ else if (stoppedPid == g_waitForAllThreads)
+ {
+ if (std::find_if(m_TIDs.begin(), m_TIDs.end(), [](std::pair<pid_t, thread_status_t> entry){return entry.second.stat == thread_stat_e::running;}) == m_TIDs.end())
+ return true;
+ }
+ else
+ {
+ // NOTE thread could exit, don't create m_TIDs entry by m_TIDs[] request.
+ auto find = m_TIDs.find(stoppedPid);
+ if (find == m_TIDs.end() || find->second.stat != thread_stat_e::running)
+ return true;
+ }
+ return false;
+ };
+ if (AllRequestedThreadsNotRunning())
+ return;
+
// At this point all threads must be stopped or interrupted, we need parse all signals now.
pid_t pid = 0;
int status = 0;
- // Note, we ignore errors here and don't check is m_TGID exit or not, since in case m_TGID exited `waitpid` return error and break loop.
+ // Note, we ignore errors here and don't check is m_TGID exit or not, since in case m_TGID exited `AllRequestedThreadsNotRunning()` break loop.
while ((pid = GetWaitpid()(-1, &status, __WALL)) > 0)
{
if (!WIFSTOPPED(status))
{
m_TIDs.erase(pid);
+ pProtocol->EmitThreadEvent(ThreadEvent(NativeThreadExited, ThreadId(pid), true));
// Tracee exited or was killed by signal.
if (pid == m_TGID)
assert(m_TIDs.empty());
m_TGID = 0;
GetWaitpid().SetPidExitedStatus(pid, status);
+ m_NotifyLastThreadExited(status);
}
- if (stoppedPid == pid)
+ if (AllRequestedThreadsNotRunning())
break;
continue;
stop_signal = 0;
}
+ if (m_TIDs.find(pid) == m_TIDs.end())
+ pProtocol->EmitThreadEvent(ThreadEvent(NativeThreadStarted, ThreadId(pid), true));
+
m_TIDs[pid].stat = thread_stat_e::stopped; // if we here, this mean we get some stop signal for this thread
m_TIDs[pid].stop_signal = stop_signal;
m_TIDs[pid].event = (unsigned)status >> 16;
m_changedThreads.emplace_back(pid);
- if (stoppedPid == pid ||
- (stoppedPid == g_waitForAllThreads &&
- std::find_if(m_TIDs.begin(), m_TIDs.end(), [](std::pair<pid_t, thread_status_t> entry){return entry.second.stat == thread_stat_e::running;}) == m_TIDs.end()))
- {
+ if (AllRequestedThreadsNotRunning())
break;
- }
}
}
// NOTE caller must care about m_waitpidMutex.
-void InteropDebugger::StopAndDetach(pid_t tgid)
+void InteropDebuggerHelpers::StopAndDetach(pid_t tgid)
{
WaitThreadStop(g_waitForAllThreads);
}
// NOTE caller must care about m_waitpidMutex.
-void InteropDebugger::StopAllRunningThreads()
+static void StopAllRunningThreads(const std::unordered_map<pid_t, thread_status_t> &TIDs)
{
- for (const auto &tid : m_TIDs)
+ for (const auto &tid : TIDs)
{
if (tid.second.stat == thread_stat_e::running &&
async_ptrace(PTRACE_INTERRUPT, tid.first, nullptr, nullptr) == -1)
}
// NOTE caller must care about m_waitpidMutex.
-void InteropDebugger::Detach(pid_t tgid)
+void InteropDebuggerHelpers::Detach(pid_t tgid)
{
- StopAllRunningThreads();
+ StopAllRunningThreads(m_TIDs);
StopAndDetach(tgid);
}
if (m_waitpidThreadStatus == WaitpidThreadStatus::WORK)
{
m_waitpidNeedExit = true;
- m_waitpidCV.notify_one(); // notify for exit from infinite loop (thread may stay and unlock mutex on wait() or usleep())
m_waitpidCV.wait(lock); // wait for exit from infinite loop
}
m_waitpidWorker.join();
Detach(m_TGID);
m_TGID = 0;
+ m_NotifyLastThreadExited = std::function<void(int)>{};
m_sharedCallbacksQueue = nullptr;
GetWaitpid().SetInteropWaitpidMode(false);
m_waitpidThreadStatus = WaitpidThreadStatus::FINISHED_AND_JOINED;
async_ptrace_shutdown();
}
-static HRESULT SeizeAndInterruptAllThreads(std::unordered_map<pid_t, thread_status_t> &TIDs, const pid_t pid, int &error_n)
+static HRESULT SeizeAndInterruptAllThreads(std::unordered_map<pid_t, thread_status_t> &TIDs, const pid_t pid, bool attach, int &error_n, IProtocol *pProtocol)
{
char dirname[128];
if (snprintf(dirname, sizeof dirname, "/proc/%d/task/", pid) >= (int)sizeof(dirname))
closedir(dir);
return E_FAIL;
}
+
+ pProtocol->EmitThreadEvent(ThreadEvent(attach ? NativeThreadAttached : NativeThreadStarted, ThreadId(tid), true));
TIDs[tid].stat = thread_stat_e::running; // seize - attach without stop
if (async_ptrace(PTRACE_INTERRUPT, tid, nullptr, nullptr) == -1)
return S_OK;
}
-void InteropDebugger::LoadLib(pid_t pid, const std::string &libLoadName, const std::string &realLibName, std::uintptr_t startAddr, std::uintptr_t endAddr)
+void InteropDebuggerHelpers::LoadLib(pid_t pid, const std::string &libLoadName, const std::string &realLibName, std::uintptr_t startAddr, std::uintptr_t endAddr)
{
- // TODO setup related to this lib native breakpoints
-
Module module;
module.id = ""; // TODO add "The `id` field is an opaque identifier of the library"
module.name = GetBasename(realLibName);
std::vector<BreakpointEvent> events;
m_sharedBreakpoints->InteropLoadModule(pid, startAddr, m_uniqueInteropLibraries.get(), events);
for (const BreakpointEvent &event : events)
- m_sharedProtocol->EmitBreakpointEvent(event);
+ pProtocol->EmitBreakpointEvent(event);
}
- m_sharedProtocol->EmitModuleEvent(ModuleEvent(ModuleNew, module));
+ pProtocol->EmitModuleEvent(ModuleEvent(ModuleNew, module));
}
-void InteropDebugger::UnloadLib(const std::string &realLibName)
+void InteropDebuggerHelpers::UnloadLib(const std::string &realLibName)
{
Module module;
module.id = ""; // TODO add "The `id` field is an opaque identifier of the library"
module.name = GetBasename(realLibName);
module.path = realLibName;
- m_sharedProtocol->EmitModuleEvent(ModuleEvent(ModuleRemoved, module));
+ pProtocol->EmitModuleEvent(ModuleEvent(ModuleRemoved, module));
std::uintptr_t startAddr = 0;
std::uintptr_t endAddr = 0;
if (m_uniqueInteropLibraries->RemoveLibrary(realLibName, startAddr, endAddr))
std::vector<BreakpointEvent> events;
m_sharedBreakpoints->InteropUnloadModule(startAddr, endAddr, events);
for (const BreakpointEvent &event : events)
- m_sharedProtocol->EmitBreakpointEvent(event);
+ pProtocol->EmitBreakpointEvent(event);
}
}
-// NOTE caller must care about m_waitpidMutex.
-void InteropDebugger::ParseThreadsChanges()
+static bool AddSignalEventForUserCode(pid_t pid, InteropLibraries *pInteropLibraries, const std::string &signal, thread_status_t &threadStatus)
{
- if (m_changedThreads.empty())
- return;
+ // get registers (we need real breakpoint address for check)
+ user_regs_struct regs;
+ iovec iov;
+ iov.iov_base = ®s;
+ iov.iov_len = sizeof(user_regs_struct);
+ if (async_ptrace(PTRACE_GETREGSET, pid, (void*)NT_PRSTATUS, &iov) == -1)
+ {
+ LOGW("Ptrace getregset error: %s\n", strerror(errno));
+ return false;
+ }
- for (auto it = m_changedThreads.begin(); it != m_changedThreads.end(); ++it)
+ // Should be user code only with debug info and ignore CoreCLR libs.
+ std::uintptr_t breakAddr = GetBreakAddrByPC(regs);
+ if (!pInteropLibraries->IsUserDebuggingCode(breakAddr))
+ return false;
+
+ // We need stop event and only at "continue" sent this signal to CoreCLR (let CoreCLR decide).
+ threadStatus.stat = thread_stat_e::stopped_signal_event_detected;
+ threadStatus.stop_event_data.addr = breakAddr;
+ threadStatus.stop_event_data.signal = signal;
+ return true;
+}
+
+static bool AddSignalEventForCallerInUserCode(pid_t pid, pid_t TGID, InteropLibraries *pInteropLibraries, const std::string &signal, thread_status_t &threadStatus)
+{
+ // Check, that debuggee send this to itself by PID.
+ siginfo_t siginfo;
+ memset(&siginfo, 0, sizeof(siginfo_t));
+ if (async_ptrace(PTRACE_GETSIGINFO, pid, nullptr, &siginfo) == -1)
{
- pid_t &pid = *it;
+ LOGW("Ptrace getsiginfo error: %s\n", strerror(errno));
+ return false;
+ }
+ if (siginfo.si_pid != TGID)
+ return false;
+
+ // Should be user code only with debug info and ignore CoreCLR libs.
+ // Find second frame - raise()/kill() caller.
+ // NOTE in case we can't get second frame data we can't guarantee that stop event will not broke CoreCLR debug API.
+ int frameCount = 0;
+ bool isUserDebuggingCode = false;
+ std::uintptr_t breakAddr = 0;
+ ThreadStackUnwind(pid, nullptr, [&](std::uintptr_t addr)
+ {
+ frameCount++;
- // TODO (CoreCLR have sigaction for this signals):
- // SIGSTOP
- // SIGILL
- // SIGFPE
- // SIGSEGV
- // SIGBUS
- // SIGABRT
- // SIGINT - Note, in CLI set to SIG_IGN.
- // SIGQUIT
- // SIGTERM
+ // TODO care about case when one thread could send signal to another inside process:
+ // kill(getpid(), SIGTRAP);
+ // and that thread's second frame will be in user code it the same time.
+
+ // (?) check that top frame is user code / not CoreCLR (but skip frames with "system" libs like libc, libpthread, ...)
+
+ // (?) check lib + method name for first frame:
+ // raise(SIGTRAP) -> libpthread-2.31.so` raise()
+ // kill(syscall(SYS_gettid), SIGTRAP) -> libc-2.31.so` kill()
+ // tgkill(getpid(), syscall(SYS_gettid), SIGTRAP) -> libc-2.31.so` tgkill()
+ // syscall(SYS_tkill, syscall(SYS_gettid), SIGTRAP) -> libc-2.31.so` syscall()
+
+ if (frameCount == 1)
+ breakAddr = addr;
+ else
+ isUserDebuggingCode = pInteropLibraries->IsUserDebuggingCode(addr);
+
+ return frameCount < 2;
+ });
+ if (!isUserDebuggingCode)
+ return false;
+
+ // We need stop event and only at "continue" sent this signal to CoreCLR (let CoreCLR decide).
+ threadStatus.stat = thread_stat_e::stopped_signal_event_detected;
+ threadStatus.stop_event_data.addr = breakAddr;
+ threadStatus.stop_event_data.signal = signal;
+ return true;
+}
- if (m_TIDs[pid].stop_signal == SIGTRAP)
+void InteropDebuggerSignals::Parse_SIGILL(pid_t pid)
+{
+ siginfo_t ptrace_info;
+ if (async_ptrace(PTRACE_GETSIGINFO, pid, nullptr, &ptrace_info) == -1)
+ {
+ LOGW("Ptrace getsiginfo error: %s\n", strerror(errno));
+ }
+ else
+ {
+ switch (ptrace_info.si_code)
{
- switch (m_TIDs[pid].event)
- {
- case PTRACE_EVENT_EXEC:
- if (pid != m_TGID)
- {
- if (async_ptrace(PTRACE_DETACH, pid, nullptr, nullptr) == -1)
- LOGW("Ptrace detach at exec error: %s\n", strerror(errno));
- else
- m_TIDs.erase(pid);
+ case TRAP_TRACE:
+ // Care about `__builtin_trap()` in user code.
+ if (AddSignalEventForUserCode(pid, m_uniqueInteropLibraries.get(), "SIGILL", m_TIDs[pid]))
+ m_eventedThreads.emplace_back(pid);
+ break;
+ case SI_USER:
+ case SI_TKILL:
+ // Care about `raise()` and `kill()` for SIGILL in user code.
+ if (AddSignalEventForCallerInUserCode(pid, m_TGID, m_uniqueInteropLibraries.get(), "SIGILL", m_TIDs[pid]))
+ m_eventedThreads.emplace_back(pid);
+ break;
+ }
+ }
+}
- continue;
- }
+void InteropDebuggerSignals::Parse_SIGTRAP__PTRACE_EVENT_EXEC(pid_t pid)
+{
+ if (pid == m_TGID)
+ {
+ m_TIDs[pid].stop_signal = 0;
+ return;
+ }
- m_TIDs[pid].stop_signal = 0;
- break;
-
- case 0: // not ptrace-related event
+ if (async_ptrace(PTRACE_DETACH, pid, nullptr, nullptr) == -1)
+ LOGW("Ptrace detach at exec error: %s\n", strerror(errno));
+ else
+ m_TIDs.erase(pid);
+}
+
+void InteropDebuggerSignals::Parse_SIGTRAP__NOT_PTRACE_EVENT(pid_t pid)
+{
+ siginfo_t ptrace_info;
+ if (async_ptrace(PTRACE_GETSIGINFO, pid, nullptr, &ptrace_info) == -1)
+ {
+ LOGW("Ptrace getsiginfo error: %s\n", strerror(errno));
+ }
+ else
+ {
+ switch (ptrace_info.si_code)
+ {
+ case SI_KERNEL:
+ case TRAP_BRKPT:
+ {
+ // get registers (we need real breakpoint address for check)
+ user_regs_struct regs;
+ iovec iov;
+ iov.iov_base = ®s;
+ iov.iov_len = sizeof(user_regs_struct);
+ if (async_ptrace(PTRACE_GETREGSET, pid, (void*)NT_PRSTATUS, &iov) == -1)
+ LOGW("Ptrace getregset error: %s\n", strerror(errno));
+
+ std::uintptr_t brkAddr = GetBrkAddrByPC(regs);
+
+ // Step over breakpoint (that previously was failed, since some signal at single step was received).
+ // Note, breakpoint with this address could be already deleted and we stop at another breakpoint, make sure addrStepOverBreakpointFailed is reset.
+ if (m_TIDs[pid].addrStepOverBreakpointFailed != 0)
{
- siginfo_t ptrace_info;
- if (async_ptrace(PTRACE_GETSIGINFO, pid, nullptr, &ptrace_info) == -1)
+ // Reset must be before `InteropStepOverBrk()` call, since it could change it again.
+ std::uintptr_t addrStepOverBreakpointFailed = m_TIDs[pid].addrStepOverBreakpointFailed;
+ m_TIDs[pid].addrStepOverBreakpointFailed = 0;
+ if (addrStepOverBreakpointFailed == brkAddr)
{
- LOGW("Ptrace getsiginfo error: %s\n", strerror(errno));
+ StopAllRunningThreads(m_TIDs);
+ WaitThreadStop(g_waitForAllThreads);
+ m_sharedBreakpoints->InteropStepOverBrk(pid, brkAddr, [&](pid_t step_pid, std::uintptr_t step_addr) {return SingleStepOnBrk(step_pid, step_addr);});
+ break;
}
- else
+ }
+
+ if (m_sharedBreakpoints->IsInteropRendezvousBreakpoint(brkAddr))
+ {
+ m_sharedBreakpoints->InteropChangeRendezvousState(m_TGID, pid);
+ m_sharedBreakpoints->InteropStepOverBrk(pid, brkAddr, [&](pid_t step_pid, std::uintptr_t step_addr) {return SingleStepOnBrk(step_pid, step_addr);});
+ }
+ else if (m_sharedBreakpoints->IsInteropBreakpoint(brkAddr))
+ {
+ // Ignore breakpoints during managed evaluation.
+ if (m_sharedEvalWaiter->GetEvalRunningThreadID() == (DWORD)pid)
{
- switch (ptrace_info.si_code)
- {
-#ifdef SI_KERNEL
- case SI_KERNEL:
-#endif
- case SI_USER:
- case TRAP_BRKPT:
- {
- // get registers (we need real breakpoint address for check)
- user_regs_struct regs;
- iovec iov;
- iov.iov_base = ®s;
- iov.iov_len = sizeof(user_regs_struct);
- if (async_ptrace(PTRACE_GETREGSET, pid, (void*)NT_PRSTATUS, &iov) == -1)
- LOGW("Ptrace getregset error: %s\n", strerror(errno));
-
- std::uintptr_t brkAddr = GetBrkAddrByPC(regs);
-
- if (m_sharedBreakpoints->IsInteropRendezvousBreakpoint(brkAddr))
- {
- m_sharedBreakpoints->InteropChangeRendezvousState(m_TGID, pid);
- m_sharedBreakpoints->InteropStepOverBrk(pid, brkAddr);
- m_TIDs[pid].stop_signal = 0;
- }
- else if (m_sharedBreakpoints->IsInteropBreakpoint(brkAddr))
- {
- // Ignore breakpoints during managed evaluation.
- if (m_sharedEvalWaiter->GetEvalRunningThreadID() == (DWORD)pid)
- {
- StopAllRunningThreads();
- WaitThreadStop(g_waitForAllThreads);
- m_sharedBreakpoints->InteropStepOverBrk(pid, brkAddr);
- m_TIDs[pid].stop_signal = 0;
- break;
- }
-
- m_TIDs[pid].stop_signal = 0;
- m_TIDs[pid].stat = thread_stat_e::stopped_breakpoint_event_detected;
- m_TIDs[pid].stop_event_data.addr = brkAddr;
- m_eventedThreads.emplace_back(pid);
- }
- break;
- }
- case TRAP_TRACE:
- // TODO check all native steppers
- // m_TIDs[pid].stop_signal = 0;
- break;
- }
+ StopAllRunningThreads(m_TIDs);
+ WaitThreadStop(g_waitForAllThreads);
+ m_sharedBreakpoints->InteropStepOverBrk(pid, brkAddr, [&](pid_t step_pid, std::uintptr_t step_addr) {return SingleStepOnBrk(step_pid, step_addr);});
+ break;
}
+
+ m_TIDs[pid].stop_signal = 0;
+ m_TIDs[pid].stat = thread_stat_e::stopped_breakpoint_event_detected;
+ m_TIDs[pid].stop_event_data.addr = brkAddr;
+ m_eventedThreads.emplace_back(pid);
}
+ else
+ {
+ // Care about `__builtin_debugtrap()` in user code.
+ if (AddSignalEventForUserCode(pid, m_uniqueInteropLibraries.get(), "SIGTRAP", m_TIDs[pid]))
+ m_eventedThreads.emplace_back(pid);
+ }
+
+ break;
+ }
+ case SI_USER:
+ case SI_TKILL:
+ // Care about `raise()` and `kill()` for SIGTRAP in user code.
+ if (AddSignalEventForCallerInUserCode(pid, m_TGID, m_uniqueInteropLibraries.get(), "SIGTRAP", m_TIDs[pid]))
+ m_eventedThreads.emplace_back(pid);
+ break;
+ case TRAP_TRACE:
+ // TODO check all native steppers
+ // m_TIDs[pid].stop_signal = 0;
+ // Reset m_TIDs[pid].addrStepOverBreakpointFailed
+ break;
+ }
+ }
+}
+
+// NOTE caller must care about m_waitpidMutex.
+void InteropDebuggerBase::ParseThreadsChanges()
+{
+ if (m_changedThreads.empty())
+ return;
+
+ static std::unordered_map<unsigned, std::function<void(pid_t pid)>> signalActions
+ {
+ { 0 , [&](pid_t pid) {
+ // From man 2 kill:
+ // If sig is 0, then no signal is sent, but existence and permission
+ // checks are still performed; this can be used to check for the
+ // existence of a process ID or process group ID that the caller is
+ // permitted to signal.
+ // Note, we also use it as previous signal "reset" (signal, that was parsed by debugger and should not be send to debuggee).
+ } },
+ { SIGILL , [&](pid_t pid) {
+ Parse_SIGILL(pid);
+ } },
+ { SIGTRAP , [&](pid_t pid) {
+ switch (m_TIDs[pid].event)
+ {
+ case PTRACE_EVENT_EXEC:
+ Parse_SIGTRAP__PTRACE_EVENT_EXEC(pid);
+ break;
+
+ case 0: // not ptrace-related event
+ Parse_SIGTRAP__NOT_PTRACE_EVENT(pid);
break;
//case PTRACE_EVENT_FORK:
m_TIDs[pid].stop_signal = 0;
break;
}
+ } }
+
+ // TODO (CoreCLR have sigaction for this signals):
+ // SIGSTOP
+ // SIGFPE
+ // SIGSEGV
+ // SIGBUS
+ // SIGABRT
+ // SIGINT - Note, in CLI set to SIG_IGN.
+ // SIGQUIT
+ // SIGTERM
+ };
+
+ for (auto it = m_changedThreads.begin(); it != m_changedThreads.end(); ++it)
+ {
+ pid_t &pid = *it;
+
+ if (m_TIDs[pid].stat != thread_stat_e::stopped)
+ continue;
+
+ auto find = signalActions.find(m_TIDs[pid].stop_signal);
+ if (find != signalActions.end())
+ {
+ find->second(pid);
}
}
// NOTE we use second cycle, since during first (parsing) we may need stop all running threads (for example, in case user breakpoint during eval).
for (const auto &pid : m_changedThreads)
{
- if (m_TIDs[pid].stat != thread_stat_e::stopped)
+ if (m_TIDs[pid].stat != thread_stat_e::stopped &&
+ m_TIDs[pid].stat != thread_stat_e::stopped_on_event_need_continue)
continue;
if (async_ptrace(PTRACE_CONT, pid, nullptr, (void*)((word_t)m_TIDs[pid].stop_signal)) == -1)
}
// Separate thread for callbacks setup in order to make waitpid and CoreCLR debug API work in the same time.
-void InteropDebugger::CallbackEventWorker()
+void InteropDebuggerBase::CallbackEventWorker()
{
std::unique_lock<std::mutex> lock(m_callbackEventMutex);
m_callbackEventCV.notify_one(); // notify WaitpidWorker(), that thread init complete
if (m_callbackEventNeedExit)
break;
+ // m_sharedCallbacksQueue's wrapper that care about m_callbacksMutex lock before code execution in lambda.
m_sharedCallbacksQueue->AddInteropCallbackToQueue([&]()
{
for (const auto &entry : m_callbackEvents)
switch (entry.stat)
{
case thread_stat_e::stopped_breakpoint_event_detected:
- m_sharedCallbacksQueue->EmplaceBackInterop(CallbackQueueCall::InteropBreakpoint, entry.pid, entry.stop_event_data.addr);
+ m_sharedCallbacksQueue->EmplaceBackInterop(CallbackQueueCall::InteropBreakpoint, entry.pid, entry.stop_event_data.addr, "");
{
+ // Important! Lock sequence must be (1)m_callbackEventMutex -> (2)m_waitpidMutex only!
+ // Important! Lock sequence must be (1)m_callbacksMutex -> (2)m_waitpidMutex only!
std::lock_guard<std::mutex> lock(m_waitpidMutex);
auto find = m_TIDs.find(entry.pid);
if (find != m_TIDs.end())
find->second.stat = thread_stat_e::stopped_breakpoint_event_in_progress;
}
break;
-
+ case thread_stat_e::stopped_signal_event_detected:
+ m_sharedCallbacksQueue->EmplaceBackInterop(CallbackQueueCall::InteropSignal, entry.pid, entry.stop_event_data.addr, entry.stop_event_data.signal);
+ {
+ // Important! Lock sequence must be (1)m_callbackEventMutex -> (2)m_waitpidMutex only!
+ // Important! Lock sequence must be (1)m_callbacksMutex -> (2)m_waitpidMutex only!
+ std::lock_guard<std::mutex> lock(m_waitpidMutex);
+ auto find = m_TIDs.find(entry.pid);
+ if (find != m_TIDs.end())
+ find->second.stat = thread_stat_e::stopped_signal_event_in_progress;
+ }
+ break;
default:
LOGW("This event type is not stop event: %d", entry.stat);
break;
m_callbackEventCV.notify_one(); // notify WaitpidWorker(), that execution exit from CallbackEventWorker()
}
-void InteropDebugger::ParseThreadsEvents()
+// Caller must care about m_waitpidMutex.
+void InteropDebuggerBase::ParseThreadsEvents()
{
- std::lock_guard<std::mutex> lock(m_waitpidMutex);
-
if (m_eventedThreads.empty())
return;
// could be stopped at CoreCLR's breakpoint and wait for waitpid, but we wait for managed process `Stop()` in the same time.
// In case m_callbackEventMutex is locked, return to waitpid loop for next cycle.
+ // Important! Lock sequence must be (1)m_callbackEventMutex -> (2)m_waitpidMutex only, but with `try_lock()` we fine here.
if (!m_callbackEventMutex.try_lock())
return;
case thread_stat_e::stopped_breakpoint_event_detected:
m_callbackEvents.emplace_back(pid, m_TIDs[pid].stat, m_TIDs[pid].stop_event_data);
break;
-
+ case thread_stat_e::stopped_signal_event_detected:
+ m_callbackEvents.emplace_back(pid, m_TIDs[pid].stat, m_TIDs[pid].stop_event_data);
+ break;
default:
LOGW("This event type is not stop event: %d", m_TIDs[pid].stat);
break;
{
case thread_stat_e::stopped_breakpoint_event_in_progress:
BrkStopAllThreads(allThreadsWereStopped);
- m_sharedBreakpoints->InteropStepOverBrk(tid.first, tid.second.stop_event_data.addr);
+ m_sharedBreakpoints->InteropStepOverBrk(tid.first, tid.second.stop_event_data.addr,
+ [&](pid_t step_pid, std::uintptr_t step_addr) {return SingleStepOnBrk(step_pid, step_addr);});
+ break;
+ case thread_stat_e::stopped_signal_event_in_progress:
+ tid.second.stat = thread_stat_e::stopped_on_event_need_continue;
+ m_changedThreads.emplace_back(tid.first);
+ break;
+ case thread_stat_e::stopped_on_event_as_native_thread:
+ tid.second.stat = thread_stat_e::stopped;
+ tid.second.stop_signal = 0;
+ m_changedThreads.emplace_back(tid.first);
break;
default:
continue;
}
+ }
- if (async_ptrace(PTRACE_CONT, tid.first, nullptr, nullptr) == -1)
- LOGW("Ptrace cont error: %s", strerror(errno));
+ // Continue native code execution with care about stop events (CallbacksQueue).
+ // Ignore allThreadsWereStopped status here, since we could have different events not only breakpoints.
+ ParseThreadsChanges();
+}
+
+static void GetAllManagedThreads(ICorDebugProcess *pProcess, std::map<pid_t, ToRelease<ICorDebugThread>> &allManagedTreads)
+{
+ ToRelease<ICorDebugThreadEnum> iCorThreadEnum;
+ pProcess->EnumerateThreads(&iCorThreadEnum);
+ ULONG fetched = 0;
+ ToRelease<ICorDebugThread> iCorThread;
+ while (SUCCEEDED(iCorThreadEnum->Next(1, &iCorThread, &fetched)) && fetched == 1)
+ {
+ DWORD tid = 0;
+ if (SUCCEEDED(iCorThread->GetID(&tid)))
+ allManagedTreads[tid] = iCorThread.Detach();
+ else
+ iCorThread.Free();
+ }
+}
+
+static void StopAllManagedThreads(std::unordered_map<pid_t, thread_status_t> &TIDs, std::map<pid_t, ToRelease<ICorDebugThread>> &allManagedTreads,
+ std::vector<pid_t> &stoppedManagedTreads)
+{
+ for (const auto &managedThread : allManagedTreads)
+ {
+ if (TIDs[managedThread.first].stat != thread_stat_e::running)
+ continue;
+
+ if (async_ptrace(PTRACE_INTERRUPT, managedThread.first, nullptr, nullptr) == -1)
+ LOGW("Ptrace interrupt error: %s\n", strerror(errno));
else
+ stoppedManagedTreads.emplace_back(managedThread.first);
+ }
+}
+
+static void AnalyzeAllManagedThreadsTopFrame(std::unordered_map<pid_t, thread_status_t> &TIDs,
+ std::map<pid_t, ToRelease<ICorDebugThread>> &allManagedTreads)
+{
+ for (const auto &managedThread : allManagedTreads)
+ {
+ // Note, we could already have some stop event here, that should be parsed separately.
+ if (TIDs[managedThread.first].stat != thread_stat_e::stopped)
+ continue;
+
+ HRESULT Status;
+ ToRelease<ICorDebugThread3> iCorThread3;
+ ToRelease<ICorDebugStackWalk> iCorStackWalk;
+ ToRelease<ICorDebugFrame> iCorFrame;
+ if (FAILED(managedThread.second->QueryInterface(IID_ICorDebugThread3, (LPVOID *) &iCorThread3)) ||
+ FAILED(iCorThread3->CreateStackWalk(&iCorStackWalk)) ||
+ FAILED(Status = iCorStackWalk->GetFrame(&iCorFrame)))
+ continue;
+
+ if (Status == S_FALSE) // S_FALSE - The current frame is a native stack frame.
{
- tid.second.stat = thread_stat_e::running;
- tid.second.stop_signal = 0;
+ TIDs[managedThread.first].stat = thread_stat_e::stopped_on_event_as_native_thread;
+ continue;
+ }
+
+ // At this point (Status == S_OK).
+ // Accordingly to CoreCLR sources, S_OK could be with nulled iCorFrame, that must be skipped.
+ // Related to `FrameType::kExplicitFrame` in runtime (skipped frame function with no-frame transition represents)
+ if (iCorFrame == NULL)
+ continue;
+
+ // Managed frame.
+ ToRelease<ICorDebugFunction> iCorFunction;
+ if (SUCCEEDED(iCorFrame->GetFunction(&iCorFunction)))
+ {
+ // In case of optimized managed code, top frame could be native (optimized code could have inlined pinvoke).
+ // Note, breakpoint can't be set in optimized managed code and step can't stop here, since this code is not JMC for sure.
+ BOOL bJustMyCode;
+ ToRelease<ICorDebugFunction2> iCorFunction2;
+ if (SUCCEEDED(iCorFunction->QueryInterface(IID_ICorDebugFunction2, (LPVOID *) &iCorFunction2)) &&
+ SUCCEEDED(iCorFunction2->GetJMCStatus(&bJustMyCode)) &&
+ // Check for optimized code. In case of optimized code, JMC status can't be set to TRUE.
+ // https://github.com/dotnet/runtime/blob/main/src/coreclr/debug/ee/debugger.cpp#L11257-L11260
+ SUCCEEDED(iCorFunction2->SetJMCStatus(TRUE)))
+ {
+ // Revert back JMC status if need.
+ if (bJustMyCode != TRUE)
+ iCorFunction2->SetJMCStatus(bJustMyCode);
+
+ continue; // not optimized code for sure, don't stop thread.
+ }
+
+ // Prevent thread native stop in case thread stopped by managed exception.
+ ToRelease<ICorDebugThread> iCorThread;
+ ToRelease<ICorDebugValue> iCorExceptionValue;
+ if (SUCCEEDED(iCorThread3->QueryInterface(IID_ICorDebugThread, (LPVOID *) &iCorThread)) &&
+ SUCCEEDED(iCorThread->GetCurrentException(&iCorExceptionValue)) && iCorExceptionValue != nullptr)
+ continue;
+
+ TIDs[managedThread.first].stat = thread_stat_e::stopped_on_event_as_native_thread;
+ continue;
}
+
+ ToRelease<ICorDebugNativeFrame> iCorNativeFrame;
+ if (SUCCEEDED(iCorFrame->QueryInterface(IID_ICorDebugNativeFrame, (LPVOID*) &iCorNativeFrame)))
+ TIDs[managedThread.first].stat = thread_stat_e::stopped_on_event_as_native_thread;
+ //else
+ // Some unknown frame, don't stop it during stop event.
}
+}
- // Continue threads execution with care about stop events (CallbacksQueue).
- if (allThreadsWereStopped)
- ParseThreadsChanges();
+HRESULT InteropDebugger::StopAllNativeThreads(ICorDebugProcess *pProcess)
+{
+ if (!pProcess)
+ return E_INVALIDARG;
+
+ std::lock_guard<std::mutex> lock(m_waitpidMutex);
+
+ if (m_TIDs.empty())
+ return S_OK;
+
+ std::map<pid_t, ToRelease<ICorDebugThread>> allManagedTreads;
+ GetAllManagedThreads(pProcess, allManagedTreads);
+
+ std::vector<pid_t> stoppedManagedTreads;
+ stoppedManagedTreads.reserve(allManagedTreads.size());
+ StopAllManagedThreads(m_TIDs, allManagedTreads, stoppedManagedTreads);
+ WaitThreadStop(g_waitForAllThreads, &stoppedManagedTreads);
+
+ AnalyzeAllManagedThreadsTopFrame(m_TIDs, allManagedTreads);
+ // At this point we have all managed threads stopped by `ptrace`:
+ // thread_stat_e::stopped - execution will continue in ParseThreadsChanges() call in this method
+ // thread_stat_e::stopped_on_event_as_native_thread - execution will continue only after stop event ends
+
+ // Stop all native treads and analyze them (we need stop all not CoreCLR related treads now).
+
+ StopAllRunningThreads(m_TIDs);
+ WaitThreadStop(g_waitForAllThreads);
+
+ static std::unordered_set<std::string> unwindStopFrames{
+ "libstdc++.so`execute_native_thread_routine()",
+ "libpthread.so`start_thread()",
+ "libc.so`__libc_start_main()",
+ "libc.so`clone()"
+ };
+
+ for (auto &nativeThread : m_TIDs)
+ {
+ // Skip managed threads, we analyzed all of them early.
+ if (allManagedTreads.find(nativeThread.first) != allManagedTreads.end())
+ continue;
+
+ // Note, we could already have some stop event here, that should be parsed separately.
+ if (m_TIDs[nativeThread.first].stat != thread_stat_e::stopped)
+ continue;
+
+ bool skipThread = false;
+ bool reachedStopFrames = false;
+ ThreadStackUnwind(nativeThread.first, nullptr, [&](std::uintptr_t addr)
+ {
+#if defined(DEBUGGER_UNIX_ARM)
+ addr = addr & ~((std::uintptr_t)1); // convert to proper (even) address (debug info use only even addresses)
+#endif
+
+ // Note, in this case we don't need info for address that is part of previous (already executed) code (we need only procedure name).
+ // This mean, no need address correction here for all frames.
+
+ std::string libLoadName;
+ std::string procName;
+ if (!m_uniqueInteropLibraries->FindDataForNotClrAddr(addr, libLoadName, procName))
+ {
+ skipThread = true;
+ return false;
+ }
+
+ // Case when lib name and/or procedure name can't be gathered, considered to be "user" code (frame should be skipped).
+ if (libLoadName.empty() || procName.empty())
+ return true;
+
+ // Note, in order to unwind successfully (and detect all user's code related threads), debug info for all user's code should be installed, plus,
+ // some arches could miss dynsyms for `start_thread()` or `clone()`, that mean debug info for libc/glibc and libstdc++ should be installed.
+ std::string unwindFrame = libLoadName + "`" + procName;
+ reachedStopFrames = unwindStopFrames.find(unwindFrame) != unwindStopFrames.end();
+ return !reachedStopFrames;
+ });
+
+ if (!skipThread && reachedStopFrames)
+ m_TIDs[nativeThread.first].stat = thread_stat_e::stopped_on_event_as_native_thread;
+ }
+
+ ParseThreadsChanges();
+ return S_OK;
}
-void InteropDebugger::WaitpidWorker()
+void InteropDebuggerBase::InitWaitpidWorkerThread()
+{
+ m_waitpidWorker = std::thread(&InteropDebuggerBase::WaitpidWorker, this);
+}
+
+void InteropDebuggerBase::WaitpidWorker()
{
std::unique_lock<std::mutex> lockEvent(m_callbackEventMutex);
m_callbackEventNeedExit = false;
- m_callbackEventWorker = std::thread(&InteropDebugger::CallbackEventWorker, this);
+ m_callbackEventWorker = std::thread(&InteropDebuggerBase::CallbackEventWorker, this);
m_callbackEventCV.wait(lockEvent); // wait for init complete from CallbackEventWorker()
lockEvent.unlock();
- std::unique_lock<std::mutex> lock(m_waitpidMutex);
+ std::unique_lock<std::mutex> lockWaitpid(m_waitpidMutex);
m_waitpidCV.notify_one(); // notify Init(), that WaitpidWorker() thread init complete
- m_waitpidCV.wait(lock); // wait for "respond" and mutex unlock from Init()
+ m_waitpidCV.wait(lockWaitpid); // wait for "respond" and mutex unlock from Init()
pid_t pid = 0;
int status = 0;
}
ParseThreadsChanges();
-
- lock.unlock();
- // NOTE mutexes lock sequence must be CallbacksQueue->InteropDebugger.
ParseThreadsEvents();
+
+ lockWaitpid.unlock();
usleep(10*1000); // sleep 10 ms before next waitpid call
- lock.lock();
+ lockWaitpid.lock();
if (m_waitpidNeedExit)
break;
if (!WIFSTOPPED(status))
{
m_TIDs.erase(pid);
+ pProtocol->EmitThreadEvent(ThreadEvent(NativeThreadExited, ThreadId(pid), true));
// Tracee exited or was killed by signal.
if (pid == m_TGID)
assert(m_TIDs.empty());
m_TGID = 0;
GetWaitpid().SetPidExitedStatus(pid, status);
+ m_NotifyLastThreadExited(status);
}
continue;
}
}
+ if (m_TIDs.find(pid) == m_TIDs.end())
+ pProtocol->EmitThreadEvent(ThreadEvent(NativeThreadStarted, ThreadId(pid), true));
+
m_TIDs[pid].stat = thread_stat_e::stopped; // if we here, this mean we get some stop signal for this thread
m_TIDs[pid].stop_signal = stop_signal;
m_TIDs[pid].event = (unsigned)status >> 16;
m_changedThreads.emplace_back(pid);
}
+ m_waitpidThreadStatus = WaitpidThreadStatus::FINISHED;
+ lockWaitpid.unlock(); // Important! Lock sequence must be (1)m_callbackEventMutex -> (2)m_waitpidMutex only!
+
lockEvent.lock();
m_callbackEventNeedExit = true;
m_callbackEventCV.notify_one(); // notify CallbackEventWorker() for exit from infinite loop
m_callbackEventWorker.join();
m_waitpidCV.notify_one(); // notify Shutdown(), that execution exit from WaitpidWorker()
- m_waitpidThreadStatus = WaitpidThreadStatus::FINISHED;
}
-HRESULT InteropDebugger::Init(pid_t pid, std::shared_ptr<CallbacksQueue> &sharedCallbacksQueue, int &error_n)
+HRESULT InteropDebugger::Init(pid_t pid, std::shared_ptr<CallbacksQueue> &sharedCallbacksQueue, bool attach, std::function<void(int)> NotifyLastThreadExited, int &error_n)
{
async_ptrace_init();
GetWaitpid().SetInteropWaitpidMode(true);
return E_FAIL;
};
- if (FAILED(SeizeAndInterruptAllThreads(m_TIDs, pid, error_n)))
+ if (FAILED(SeizeAndInterruptAllThreads(m_TIDs, pid, attach, error_n, pProtocol)))
return ExitWithError();
WaitThreadStop(g_waitForAllThreads);
if (!m_sharedBreakpoints->InteropSetupRendezvousBrk(pid, loadLib, unloadLib, isThumbCode, error_n))
return ExitWithError();
+ // At this point all threads are stopped, continue execution for all not event-related stopped threads.
+ ParseThreadsChanges();
+
m_waitpidNeedExit = false;
- m_waitpidWorker = std::thread(&InteropDebugger::WaitpidWorker, this);
+ InitWaitpidWorkerThread();
m_waitpidCV.wait(lock); // wait for init complete from WaitpidWorker()
m_waitpidThreadStatus = WaitpidThreadStatus::WORK;
m_TGID = pid;
m_sharedCallbacksQueue = sharedCallbacksQueue;
m_waitpidCV.notify_one(); // notify WaitpidWorker() to start infinite loop
+ m_NotifyLastThreadExited = NotifyLastThreadExited;
InitNativeFramesUnwind(this);
return S_OK;
}
// In order to add or remove breakpoint we must stop all threads first.
-void InteropDebugger::BrkStopAllThreads(bool &allThreadsWereStopped)
+void InteropDebuggerHelpers::BrkStopAllThreads(bool &allThreadsWereStopped)
{
if (allThreadsWereStopped)
return;
- StopAllRunningThreads();
+ StopAllRunningThreads(m_TIDs);
WaitThreadStop(g_waitForAllThreads);
allThreadsWereStopped = true;
}
// In case we need remove breakpoint from address, we must care about all threads first, since some threads could break on this breakpoint already.
// Note, at this point we don't need step over breakpoint, since we don't need "fix, step and restore" logic here.
-void InteropDebugger::BrkFixAllThreads(std::uintptr_t checkAddr)
+void InteropDebuggerHelpers::BrkFixAllThreads(std::uintptr_t checkAddr)
{
for (auto &entry : m_TIDs)
{
return S_OK;
}
+bool InteropDebugger::IsNativeThreadStopped(pid_t pid)
+{
+ std::lock_guard<std::mutex> lock(m_waitpidMutex);
+
+ if (m_TIDs.empty())
+ return S_OK;
+
+ auto tid = m_TIDs.find(pid);
+ if (tid == m_TIDs.end())
+ return E_INVALIDARG;
+
+ assert(tid->second.stat != thread_stat_e::stopped);
+ return tid->second.stat != thread_stat_e::running;
+}
+
+void InteropDebugger::WalkAllThreads(std::function<void(pid_t, bool)> cb)
+{
+ std::lock_guard<std::mutex> lock(m_waitpidMutex);
+
+ if (m_TIDs.empty())
+ return;
+
+ std::map<pid_t, thread_status_t> orderedTIDs(m_TIDs.begin(), m_TIDs.end());
+
+ for (auto &tid : orderedTIDs)
+ {
+ cb(tid.first, tid.second.stat == thread_stat_e::running);
+ }
+}
+
} // namespace InteropDebugging
} // namespace netcoredbg
enum class thread_stat_e
{
+ // Important! `stopped` state is temporary and must be parsed by `ParseThreadsChanges()` and not leave `m_waitpidMutex` locked scope.
+ // Before leave `m_waitpidMutex` locked scope this state should be reseted to `running` with execution continue or changed to event-related state.
stopped,
+ stopped_on_event_as_native_thread,
stopped_breakpoint_event_detected,
stopped_breakpoint_event_in_progress,
+ stopped_signal_event_detected,
+ stopped_signal_event_in_progress,
+ stopped_on_event_need_continue,
running
};
struct stop_event_data_t
{
std::uintptr_t addr = 0;
+ std::string signal;
};
struct thread_status_t
unsigned stop_signal = 0;
unsigned event = 0;
+ // Address of breakpoint that single step at "step over breakpoint" was failed.
+ // Hold address of breakpoint that must be "step over" (skipped).
+ std::uintptr_t addrStepOverBreakpointFailed = 0;
+
// Data, that should be stored in order to create stop event (CallbacksQueue) and/or continue thread execution.
stop_event_data_t stop_event_data;
};
{}
};
-class InteropDebugger
+class InteropDebuggerBase
{
-public:
-
- InteropDebugger(std::shared_ptr<IProtocol> &sharedProtocol,
- std::shared_ptr<Breakpoints> &sharedBreakpoints,
- std::shared_ptr<EvalWaiter> &sharedEvalWaiter);
-
- // Initialize interop debugging, attach to process, detect loaded libs, setup native breakpoints, etc.
- HRESULT Init(pid_t pid, std::shared_ptr<CallbacksQueue> &sharedCallbacksQueue, int &error_n);
- // Shutdown interop debugging, remove all native breakpoints, detach from threads, etc.
- void Shutdown();
-
- // Called by CallbacksQueue for continue process (continue threads with processed stop events).
- void ContinueAllThreadsWithEvents();
- HRESULT SetLineBreakpoints(const std::string& filename, const std::vector<LineBreakpoint> &lineBreakpoints, std::vector<Breakpoint> &breakpoints);
- HRESULT AllBreakpointsActivate(bool act);
- HRESULT BreakpointActivate(uint32_t id, bool act);
+protected:
- HRESULT GetFrameForAddr(std::uintptr_t addr, StackFrame &frame);
- HRESULT UnwindNativeFrames(pid_t pid, bool firstFrame, std::uintptr_t endAddr, CONTEXT *pStartContext,
- std::function<HRESULT(NativeFrame &nativeFrame)> nativeFramesCallback);
-
-private:
+ InteropDebuggerBase(IProtocol *pProtocol_,
+ std::shared_ptr<Breakpoints> &sharedBreakpoints,
+ std::shared_ptr<EvalWaiter> &sharedEvalWaiter);
- std::shared_ptr<IProtocol> m_sharedProtocol;
+ IProtocol *pProtocol;
std::shared_ptr<Breakpoints> m_sharedBreakpoints;
std::shared_ptr<CallbacksQueue> m_sharedCallbacksQueue;
std::unique_ptr<InteropLibraries> m_uniqueInteropLibraries;
std::thread m_waitpidWorker;
bool m_waitpidNeedExit = false;
pid_t m_TGID = 0;
+ std::function<void(int)> m_NotifyLastThreadExited;
std::unordered_map<pid_t, thread_status_t> m_TIDs;
// We use std::list here, since we need container that not invalidate iterators at `emplace_back()` call.
std::list<pid_t> m_changedThreads;
std::condition_variable m_waitpidCV;
WaitpidThreadStatus m_waitpidThreadStatus = WaitpidThreadStatus::UNKNOWN;
+ void InitWaitpidWorkerThread();
void WaitpidWorker();
- void LoadLib(pid_t pid, const std::string &libLoadName, const std::string &realLibName, std::uintptr_t startAddr, std::uintptr_t endAddr);
- void UnloadLib(const std::string &realLibName);
+ void WaitThreadStop(pid_t stoppedPid, std::vector<pid_t> *stopTreads = nullptr);
+ bool SingleStepOnBrk(pid_t pid, std::uintptr_t addr);
+ void ParseThreadsEvents();
+ void ParseThreadsChanges();
+ virtual void Parse_SIGILL(pid_t pid) = 0;
+ virtual void Parse_SIGTRAP__PTRACE_EVENT_EXEC(pid_t pid) = 0;
+ virtual void Parse_SIGTRAP__NOT_PTRACE_EVENT(pid_t pid) = 0;
+};
+
+class InteropDebuggerSignals : protected InteropDebuggerBase
+{
+protected:
+
+ InteropDebuggerSignals(IProtocol *pProtocol_,
+ std::shared_ptr<Breakpoints> &sharedBreakpoints,
+ std::shared_ptr<EvalWaiter> &sharedEvalWaiter);
+
+ void Parse_SIGILL(pid_t pid) override;
+ void Parse_SIGTRAP__PTRACE_EVENT_EXEC(pid_t pid) override;
+ void Parse_SIGTRAP__NOT_PTRACE_EVENT(pid_t pid) override;
+};
+
+class InteropDebuggerHelpers : protected InteropDebuggerSignals
+{
+protected:
+
+ InteropDebuggerHelpers(IProtocol *pProtocol_,
+ std::shared_ptr<Breakpoints> &sharedBreakpoints,
+ std::shared_ptr<EvalWaiter> &sharedEvalWaiter);
- void StopAllRunningThreads();
- void WaitThreadStop(pid_t stoppedPid);
void StopAndDetach(pid_t tgid);
void Detach(pid_t tgid);
- void ParseThreadsChanges();
- void ParseThreadsEvents();
+
+ void LoadLib(pid_t pid, const std::string &libLoadName, const std::string &realLibName, std::uintptr_t startAddr, std::uintptr_t endAddr);
+ void UnloadLib(const std::string &realLibName);
+
void BrkStopAllThreads(bool &allThreadsWereStopped);
void BrkFixAllThreads(std::uintptr_t checkAddr);
+};
+
+class InteropDebugger final : InteropDebuggerHelpers
+{
+public:
+
+ InteropDebugger(IProtocol *pProtocol_,
+ std::shared_ptr<Breakpoints> &sharedBreakpoints,
+ std::shared_ptr<EvalWaiter> &sharedEvalWaiter);
+
+ // Initialize interop debugging, attach to process, detect loaded libs, setup native breakpoints, etc.
+ HRESULT Init(pid_t pid, std::shared_ptr<CallbacksQueue> &sharedCallbacksQueue, bool attach, std::function<void(int)> NotifyLastThreadExited, int &error_n);
+ // Shutdown interop debugging, remove all native breakpoints, detach from threads, etc.
+ void Shutdown();
+
+ // Called by CallbacksQueue for continue process (continue threads with processed stop events).
+ void ContinueAllThreadsWithEvents();
+ HRESULT StopAllNativeThreads(ICorDebugProcess *pProcess);
+ HRESULT SetLineBreakpoints(const std::string& filename, const std::vector<LineBreakpoint> &lineBreakpoints, std::vector<Breakpoint> &breakpoints);
+ HRESULT AllBreakpointsActivate(bool act);
+ HRESULT BreakpointActivate(uint32_t id, bool act);
+
+ HRESULT GetFrameForAddr(std::uintptr_t addr, StackFrame &frame);
+ HRESULT UnwindNativeFrames(pid_t pid, bool firstFrame, std::uintptr_t endAddr, CONTEXT *pStartContext,
+ std::function<HRESULT(NativeFrame &nativeFrame)> nativeFramesCallback);
+ bool IsNativeThreadStopped(pid_t pid);
+ void WalkAllThreads(std::function<void(pid_t, bool)> cb);
};
} // namespace InteropDebugging
return false;
}
- ssize_t read;
char *line = nullptr;
size_t lineLen = 0;
- while ((read = getline(&line, &lineLen, mapsFile)) != -1)
+ while (getline(&line, &lineLen, mapsFile) != -1)
{
void *startAddress, *endAddress, *offset;
int devHi, devLo, inode;
std::uintptr_t endAddr = 0;
assert(realLibName.empty());
- ssize_t read;
char *line = nullptr;
size_t lineLen = 0;
- while ((read = getline(&line, &lineLen, mapsFile)) != -1)
+ while (getline(&line, &lineLen, mapsFile) != -1)
{
void *startAddress, *endAddress, *offset;
int devHi, devLo, inode;
break;
}
+ if (pc == 0)
+ break;
+
#if defined(UNW_TARGET_AARCH64)
if (prev_pc == pc)
break;
// for global variables. coreclr_shutdown only should be called on process exit.
Interop::Init(m_debugger.m_clrPath);
+#ifdef INTEROP_DEBUGGING
+ // Note, in case `attach` CoreCLR also call CreateProcess() that call this method.
+ int error_n = 0;
+ bool attach = m_debugger.m_startMethod == StartAttach;
+ auto NotifyLastThreadExited = [&](int status)
+ {
+ // In case debuggee process rude terminated by some signal, we may have situation when
+ // `ManagedCallback::ExitProcess()` will be newer called by dbgshim.
+ if (!WIFSIGNALED(status))
+ return;
+
+ // If we still `Attached` here, `ManagedCallback::ExitProcess()` was not called.
+ std::unique_lock<std::mutex> lockAttachedMutex(m_debugger.m_processAttachedMutex);
+ if (m_debugger.m_processAttachedState == ProcessAttachedState::Attached)
+ m_debugger.m_processAttachedCV.wait_for(lockAttachedMutex, std::chrono::milliseconds(3000));
+ if (m_debugger.m_processAttachedState == ProcessAttachedState::Unattached)
+ return;
+ lockAttachedMutex.unlock();
+
+ if (m_debugger.m_sharedEvalWaiter->IsEvalRunning())
+ LOGW("The target process exited while evaluating the function.");
+
+ m_debugger.m_sharedEvalWaiter->NotifyEvalComplete(nullptr, nullptr);
+
+ m_debugger.pProtocol->EmitExitedEvent(ExitedEvent(GetWaitpid().GetExitCode()));
+ m_debugger.NotifyProcessExited();
+ m_debugger.pProtocol->EmitTerminatedEvent();
+ m_debugger.m_ioredirect.async_cancel();
+ };
+ if (m_debugger.m_interopDebugging &&
+ FAILED(m_debugger.m_sharedInteropDebugger->Init((pid_t)m_debugger.m_processId, m_sharedCallbacksQueue, attach, NotifyLastThreadExited, error_n)))
+ {
+ LOGE("Interop debugging disabled due to initialization fail: %s", strerror(error_n));
+ m_debugger.pProtocol->EmitInteropDebuggingErrorEvent(error_n);
+ m_debugger.m_interopDebugging = false;
+ }
+#endif // INTEROP_DEBUGGING
+
// 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
}
#endif // FEATURE_PAL
- m_debugger.m_sharedProtocol->EmitExitedEvent(ExitedEvent(exitCode));
+ m_debugger.pProtocol->EmitExitedEvent(ExitedEvent(exitCode));
m_debugger.NotifyProcessExited();
- m_debugger.m_sharedProtocol->EmitTerminatedEvent();
+ m_debugger.pProtocol->EmitTerminatedEvent();
m_debugger.m_ioredirect.async_cancel();
return S_OK;
}
ThreadId threadId(getThreadId(pThread));
m_debugger.m_sharedThreads->Add(threadId);
- m_debugger.m_sharedProtocol->EmitThreadEvent(ThreadEvent(ThreadStarted, threadId));
+ m_debugger.pProtocol->EmitThreadEvent(ThreadEvent(ManagedThreadStarted, threadId, m_debugger.m_interopDebugging));
return m_sharedCallbacksQueue->ContinueAppDomain(pAppDomain);
}
m_debugger.m_sharedBreakpoints->ManagedCallbackExitThread(pThread);
- m_debugger.m_sharedProtocol->EmitThreadEvent(ThreadEvent(ThreadExited, threadId));
+ m_debugger.pProtocol->EmitThreadEvent(ThreadEvent(ManagedThreadExited, threadId, m_debugger.m_interopDebugging));
return m_sharedCallbacksQueue->ContinueAppDomain(pAppDomain);
}
std::string outputText;
m_debugger.m_sharedModules->TryLoadModuleSymbols(pModule, module, m_debugger.IsJustMyCode(), m_debugger.IsHotReload(), outputText);
if (!outputText.empty())
- m_debugger.m_sharedProtocol->EmitOutputEvent(OutputStdErr, outputText);
- m_debugger.m_sharedProtocol->EmitModuleEvent(ModuleEvent(ModuleNew, module));
+ {
+ m_debugger.pProtocol->EmitOutputEvent(OutputStdErr, outputText);
+ }
+ m_debugger.pProtocol->EmitModuleEvent(ModuleEvent(ModuleNew, module));
if (module.symbolStatus == SymbolsLoaded)
{
std::vector<BreakpointEvent> events;
m_debugger.m_sharedBreakpoints->ManagedCallbackLoadModule(pModule, events);
for (const BreakpointEvent &event : events)
- m_debugger.m_sharedProtocol->EmitBreakpointEvent(event);
+ {
+ m_debugger.pProtocol->EmitBreakpointEvent(event);
+ }
}
m_debugger.m_sharedBreakpoints->ManagedCallbackLoadModuleAll(pModule);
src = "Debugger.Log";
}
- m_debugger.m_sharedProtocol->EmitOutputEvent(OutputConsole, to_utf8(pMessage), src);
+ m_debugger.pProtocol->EmitOutputEvent(OutputConsole, to_utf8(pMessage), src);
return m_sharedCallbacksQueue->ContinueAppDomain(pAppDomain);
}
std::mutex m_refCountMutex;
ULONG m_refCount;
- ManagedDebugger &m_debugger;
+ ManagedDebuggerHelpers &m_debugger;
std::shared_ptr<CallbacksQueue> m_sharedCallbacksQueue;
public:
- ManagedCallback(ManagedDebugger &debugger, std::shared_ptr<CallbacksQueue> &sharedCallbacksQueue) :
+ ManagedCallback(ManagedDebuggerHelpers &debugger, std::shared_ptr<CallbacksQueue> &sharedCallbacksQueue) :
m_refCount(0), m_debugger(debugger), m_sharedCallbacksQueue(sharedCallbacksQueue){}
ULONG GetRefCount();
#ifdef INTEROP_DEBUGGING
#include "elf++.h"
#include "dwarf++.h"
+#include "debugger/sigaction.h"
#endif // INTEROP_DEBUGGING
#include "palclr.h"
return 0;
}
+}
- // Caller must care about m_debugProcessRWLock.
- HRESULT CheckDebugProcess(ICorDebugProcess *pProcess, std::mutex &processAttachedMutex, ProcessAttachedState processAttachedState)
- {
- if (!pProcess)
- return E_FAIL;
-
- // We might have case, when process was exited/detached, but m_iCorProcess still not free and hold invalid object.
- // Note, we can't hold this lock, since this could deadlock execution at ICorDebugManagedCallback::ExitProcess call.
- std::unique_lock<std::mutex> lockAttachedMutex(processAttachedMutex);
- if (processAttachedState == ProcessAttachedState::Unattached)
- return E_FAIL;
- lockAttachedMutex.unlock();
+// Caller must care about m_debugProcessRWLock.
+HRESULT ManagedDebuggerBase::CheckDebugProcess()
+{
+ if (!m_iCorProcess)
+ return E_FAIL;
- return S_OK;
- }
+ // We might have case, when process was exited/detached, but m_iCorProcess still not free and hold invalid object.
+ // Note, we can't hold this lock, since this could deadlock execution at ICorDebugManagedCallback::ExitProcess call.
+ std::unique_lock<std::mutex> lockAttachedMutex(m_processAttachedMutex);
+ if (m_processAttachedState == ProcessAttachedState::Unattached)
+ return E_FAIL;
+ lockAttachedMutex.unlock();
- bool HaveDebugProcess(Utility::RWLock &debugProcessRWLock, ICorDebugProcess *pProcess, std::mutex &processAttachedMutex, ProcessAttachedState processAttachedState)
- {
- std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(debugProcessRWLock.reader);
- return SUCCEEDED(CheckDebugProcess(pProcess, processAttachedMutex, processAttachedState));
- }
+ return S_OK;
}
-void ManagedDebugger::NotifyProcessCreated()
+bool ManagedDebuggerBase::HaveDebugProcess()
{
-#ifdef INTEROP_DEBUGGING
- // Note, in case `attach` CoreCLR also call CreateProcess() that call this method.
- int error_n = 0;
- if (m_interopDebugging && FAILED(m_uniqueInteropDebugger->Init((pid_t)m_processId, m_sharedCallbacksQueue, error_n)))
- {
- LOGE("Interop debugging disabled due to initialization fail: %s", strerror(error_n));
- m_sharedProtocol->EmitInteropDebuggingErrorEvent(error_n);
- m_interopDebugging = false;
- }
-#endif // INTEROP_DEBUGGING
+ std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
+ return SUCCEEDED(CheckDebugProcess());
+}
+void ManagedDebuggerBase::NotifyProcessCreated()
+{
std::unique_lock<std::mutex> lock(m_processAttachedMutex);
m_processAttachedState = ProcessAttachedState::Attached;
lock.unlock();
m_processAttachedCV.notify_one();
}
-void ManagedDebugger::NotifyProcessExited()
+void ManagedDebuggerBase::NotifyProcessExited()
{
std::unique_lock<std::mutex> lock(m_processAttachedMutex);
m_processAttachedState = ProcessAttachedState::Unattached;
lock.unlock();
- m_processAttachedCV.notify_one();
+ m_processAttachedCV.notify_all();
}
// Caller must care about m_debugProcessRWLock.
-void ManagedDebugger::DisableAllBreakpointsAndSteppers()
+void ManagedDebuggerBase::DisableAllBreakpointsAndSteppers()
{
m_uniqueSteppers->DisableAllSteppers(m_iCorProcess); // Async stepper could have breakpoints active, disable them first.
m_sharedBreakpoints->DeleteAllManaged();
m_sharedBreakpoints->DisableAllManaged(m_iCorProcess); // Last one, disable all breakpoints on all domains, even if we don't hold them.
}
-void ManagedDebugger::SetLastStoppedThread(ICorDebugThread *pThread)
+void ManagedDebuggerBase::SetLastStoppedThread(ICorDebugThread *pThread)
{
SetLastStoppedThreadId(getThreadId(pThread));
}
-void ManagedDebugger::SetLastStoppedThreadId(ThreadId threadId)
+void ManagedDebuggerBase::SetLastStoppedThreadId(ThreadId threadId)
{
std::lock_guard<std::mutex> lock(m_lastStoppedMutex);
m_lastStoppedThreadId = threadId;
m_sharedBreakpoints->SetLastStoppedIlOffset(m_iCorProcess, m_lastStoppedThreadId);
}
-void ManagedDebugger::InvalidateLastStoppedThreadId()
+void ManagedDebuggerBase::InvalidateLastStoppedThreadId()
{
SetLastStoppedThreadId(ThreadId::AllThreads);
}
return m_lastStoppedThreadId;
}
-ManagedDebugger::ManagedDebugger(std::shared_ptr<IProtocol> &sharedProtocol) :
+ManagedDebuggerBase::ManagedDebuggerBase(IProtocol *pProtocol_) :
m_processAttachedState(ProcessAttachedState::Unattached),
m_lastStoppedThreadId(ThreadId::AllThreads),
m_startMethod(StartNone),
m_isConfigurationDone(false),
- m_sharedProtocol(sharedProtocol),
+ pProtocol(pProtocol_),
m_sharedThreads(new Threads),
m_sharedModules(new Modules),
m_sharedEvalWaiter(new EvalWaiter),
m_sharedCallbacksQueue(nullptr),
m_uniqueManagedCallback(nullptr),
#ifdef INTEROP_DEBUGGING
- m_uniqueInteropDebugger(new InteropDebugging::InteropDebugger(m_sharedProtocol, m_sharedBreakpoints, m_sharedEvalWaiter)),
+ m_sharedInteropDebugger(new InteropDebugging::InteropDebugger(pProtocol, m_sharedBreakpoints, m_sharedEvalWaiter)),
#endif // INTEROP_DEBUGGING
m_justMyCode(true),
m_stepFiltering(true),
{
m_sharedEvalStackMachine->SetupEval(m_sharedEvaluator, m_sharedEvalHelpers, m_sharedEvalWaiter);
m_sharedThreads->SetEvaluator(m_sharedEvaluator);
+#ifdef INTEROP_DEBUGGING
+ // Note, we don't care about m_interopDebugging here, since m_interopDebugging could be changed with env parsing before real start/attach.
+ m_sharedEvalWaiter->SetInteropDebugger(m_sharedInteropDebugger);
+#endif // INTEROP_DEBUGGING
}
-ManagedDebugger::~ManagedDebugger()
+ManagedDebuggerHelpers::ManagedDebuggerHelpers(IProtocol *pProtocol_) :
+ ManagedDebuggerBase(pProtocol_)
+{}
+
+ManagedDebugger::ManagedDebugger(IProtocol *pProtocol_) :
+ ManagedDebuggerHelpers(pProtocol_)
+{}
+
+ManagedDebuggerBase::~ManagedDebuggerBase()
{
+#ifdef INTEROP_DEBUGGING
+ // Note, we don't care about m_interopDebugging here, since m_interopDebugging could be changed with env parsing before real start/attach.
+ m_sharedEvalWaiter->ResetInteropDebugger();
+#endif // INTEROP_DEBUGGING
+ m_sharedThreads->ResetEvaluator();
+ m_sharedEvalStackMachine->ResetEval();
}
HRESULT ManagedDebugger::Initialize()
// TODO: Report capabilities and check client support
m_startMethod = StartNone;
- m_sharedProtocol->EmitInitializedEvent();
+ pProtocol->EmitInitializedEvent();
return S_OK;
}
-HRESULT ManagedDebugger::RunIfReady()
+HRESULT ManagedDebuggerHelpers::RunIfReady()
{
FrameId::invalidate();
case StartLaunch:
return RunProcess(m_execPath, m_execArgs);
case StartAttach:
- return AttachToProcess(m_processId);
+ return AttachToProcess();
default:
return E_FAIL;
}
#ifdef INTEROP_DEBUGGING
if (m_interopDebugging)
- m_uniqueInteropDebugger->Shutdown();
+ m_sharedInteropDebugger->Shutdown();
#endif
if (!terminate)
{
HRESULT Status = DetachFromProcess();
if (SUCCEEDED(Status))
- m_sharedProtocol->EmitTerminatedEvent();
+ pProtocol->EmitTerminatedEvent();
m_ioredirect.async_cancel();
return Status;
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
HRESULT Status;
- IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+ IfFailRet(CheckDebugProcess());
if (m_sharedEvalWaiter->IsEvalRunning())
{
m_sharedVariables->Clear(); // Important, must be sync with MIProtocol m_vars.clear()
FrameId::invalidate(); // Clear all created during break frames.
- m_sharedProtocol->EmitContinuedEvent(threadId); // VSCode protocol need thread ID.
+ pProtocol->EmitContinuedEvent(threadId); // VSCode protocol need thread ID.
// Note, process continue must be after event emitted, since we could get new stop event from queue here.
if (FAILED(Status = m_sharedCallbacksQueue->Continue(m_iCorProcess)))
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
HRESULT Status;
- IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+ IfFailRet(CheckDebugProcess());
if (m_sharedEvalWaiter->IsEvalRunning())
{
m_sharedVariables->Clear(); // Important, must be sync with MIProtocol m_vars.clear()
FrameId::invalidate(); // Clear all created during break frames.
- m_sharedProtocol->EmitContinuedEvent(threadId); // VSCode protocol need thread ID.
+ pProtocol->EmitContinuedEvent(threadId); // VSCode protocol need thread ID.
// Note, process continue must be after event emitted, since we could get new stop event from queue here.
if (FAILED(Status = m_sharedCallbacksQueue->Continue(m_iCorProcess)))
return Status;
}
-HRESULT ManagedDebugger::Pause(ThreadId lastStoppedThread)
+HRESULT ManagedDebugger::Pause(ThreadId lastStoppedThread, EventFormat eventFormat)
{
LogFuncEntry();
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
HRESULT Status;
- IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+ IfFailRet(CheckDebugProcess());
- return m_sharedCallbacksQueue->Pause(m_iCorProcess, lastStoppedThread);
+ return m_sharedCallbacksQueue->Pause(m_iCorProcess, lastStoppedThread, eventFormat);
}
-HRESULT ManagedDebugger::GetThreads(std::vector<Thread> &threads)
+HRESULT ManagedDebugger::GetThreads(std::vector<Thread> &threads, bool withNativeThreads)
{
LogFuncEntry();
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
HRESULT Status;
- IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+ IfFailRet(CheckDebugProcess());
+#ifdef INTEROP_DEBUGGING
+ if (m_interopDebugging && withNativeThreads)
+ return m_sharedThreads->GetInteropThreadsWithState(m_iCorProcess, m_sharedInteropDebugger.get(), threads);
+#endif // INTEROP_DEBUGGING
return m_sharedThreads->GetThreadsWithState(m_iCorProcess, threads);
}
-VOID ManagedDebugger::StartupCallback(IUnknown *pCordb, PVOID parameter, HRESULT hr)
+VOID ManagedDebuggerHelpers::StartupCallback(IUnknown *pCordb, PVOID parameter, HRESULT hr)
{
ManagedDebugger *self = static_cast<ManagedDebugger*>(parameter);
- self->Startup(pCordb, self->m_processId);
+ self->Startup(pCordb);
if (self->m_unregisterToken)
{
return true;
}
-static HRESULT InternalEnumerateCLRs(dbgshim_t &dbgshim, DWORD pid, HANDLE **ppHandleArray, LPWSTR **ppStringArray, DWORD *pdwArrayLength, int tryCount)
+static HRESULT EnumerateCLRs(dbgshim_t &dbgshim, DWORD pid, HANDLE **ppHandleArray, LPWSTR **ppStringArray, DWORD *pdwArrayLength, int tryCount)
{
int numTries = 0;
HRESULT hr;
LPWSTR* pStringArray;
DWORD dwArrayLength;
const int tryCount = timeoutSec * 10; // 100ms interval between attempts
- if (FAILED(InternalEnumerateCLRs(dbgshim, pid, &pHandleArray, &pStringArray, &dwArrayLength, tryCount)) || dwArrayLength == 0)
+ if (FAILED(EnumerateCLRs(dbgshim, pid, &pHandleArray, &pStringArray, &dwArrayLength, tryCount)) || dwArrayLength == 0)
return std::string();
std::string result = to_utf8(pStringArray[0]);
return result;
}
-HRESULT ManagedDebugger::Startup(IUnknown *punk, DWORD pid)
+HRESULT ManagedDebuggerHelpers::Startup(IUnknown *punk)
{
HRESULT Status;
IfFailRet(iCorDebug->Initialize());
if (m_clrPath.empty())
- m_clrPath = GetCLRPath(m_dbgshim, pid);
+ m_clrPath = GetCLRPath(m_dbgshim, m_processId);
m_sharedCallbacksQueue.reset(new CallbacksQueue(*this));
m_uniqueManagedCallback.reset(new ManagedCallback(*this, m_sharedCallbacksQueue));
if (FAILED(Status))
{
iCorDebug->Terminate();
- m_uniqueManagedCallback.reset(nullptr);
- m_sharedCallbacksQueue = nullptr;
+ m_uniqueManagedCallback.reset();
+ m_sharedCallbacksQueue.reset();
return Status;
}
ToRelease<ICorDebugProcess> iCorProcess;
- Status = iCorDebug->DebugActiveProcess(pid, FALSE, &iCorProcess);
+ Status = iCorDebug->DebugActiveProcess(m_processId, FALSE, &iCorProcess);
if (FAILED(Status))
{
iCorDebug->Terminate();
- m_uniqueManagedCallback.reset(nullptr);
- m_sharedCallbacksQueue = nullptr;
+ m_uniqueManagedCallback.reset();
+ m_sharedCallbacksQueue.reset();
return Status;
}
lockProcessRWLock.unlock();
- m_processId = pid;
-
#ifdef FEATURE_PAL
GetWaitpid().SetupTrackingPID(m_processId);
#endif // FEATURE_PAL
outEnv.push_back('\0');
}
-HRESULT ManagedDebugger::RunProcess(const std::string& fileExec, const std::vector<std::string>& execArgs)
+HRESULT ManagedDebuggerHelpers::RunProcess(const std::string& fileExec, const std::vector<std::string>& execArgs)
{
HRESULT Status;
HANDLE resumeHandle = 0; // Fake thread handle for the process resume
+#ifdef INTEROP_DEBUGGING
+ bool prevInteropDebuggingStatus = !!m_interopDebugging;
+#endif INTEROP_DEBUGGING
+
std::vector<char> outEnv;
PrepareSystemEnvironmentArg(m_env, outEnv, m_hotReload, m_interopDebugging);
+#ifdef INTEROP_DEBUGGING
+ if ((!!m_interopDebugging) != prevInteropDebuggingStatus)
+ {
+ // In case of interop debugging we depend on SIGCHLD set to SIG_DFL by init code.
+ // Note, debugger include corhost (CoreCLR) that could setup sigaction for SIGCHLD and ruin interop debugger work.
+ SetSigactionMode(!!m_interopDebugging);
+ }
+#endif INTEROP_DEBUGGING
+
// cwd in launch.json set working directory for debugger https://code.visualstudio.com/docs/python/debugging#_cwd
if (!m_cwd.empty())
{
if (!m_processAttachedCV.wait_for(lockAttachedMutex, startupWaitTimeout, [this]{return m_processAttachedState == ProcessAttachedState::Attached;}))
return E_FAIL;
- m_sharedProtocol->EmitExecEvent(PID{m_processId}, fileExec);
+ pProtocol->EmitExecEvent(PID{m_processId}, fileExec);
return S_OK;
}
-HRESULT ManagedDebugger::CheckNoProcess()
+HRESULT ManagedDebuggerBase::CheckNoProcess()
{
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
return S_OK;
}
-HRESULT ManagedDebugger::DetachFromProcess()
+HRESULT ManagedDebuggerHelpers::DetachFromProcess()
{
do {
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
return S_OK;
}
-HRESULT ManagedDebugger::TerminateProcess()
+HRESULT ManagedDebuggerHelpers::TerminateProcess()
{
do {
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
return S_OK;
}
-void ManagedDebugger::Cleanup()
+void ManagedDebuggerBase::Cleanup()
{
m_sharedModules->CleanupAllModules();
m_sharedEvalHelpers->Cleanup();
m_sharedVariables->Clear(); // Important, must be sync with MIProtocol m_vars.clear()
- m_sharedProtocol->Cleanup();
+ pProtocol->Cleanup();
std::lock_guard<Utility::RWLock::Writer> guardProcessRWLock(m_debugProcessRWLock.writer);
m_sharedCallbacksQueue = nullptr;
}
-HRESULT ManagedDebugger::AttachToProcess(DWORD pid)
+HRESULT ManagedDebuggerHelpers::AttachToProcess()
{
HRESULT Status;
IfFailRet(CheckNoProcess());
- m_clrPath = GetCLRPath(m_dbgshim, pid);
+ m_clrPath = GetCLRPath(m_dbgshim, m_processId);
if (m_clrPath.empty())
return E_INVALIDARG; // Unable to find libcoreclr.so
WCHAR pBuffer[100];
DWORD dwLength;
IfFailRet(m_dbgshim.CreateVersionStringFromModule(
- pid,
+ m_processId,
reinterpret_cast<LPCWSTR>(to_utf16(m_clrPath).c_str()),
pBuffer,
_countof(pBuffer),
IfFailRet(m_dbgshim.CreateDebuggingInterfaceFromVersionEx(CorDebugVersion_4_0, pBuffer, &pCordb));
m_unregisterToken = nullptr;
- IfFailRet(Startup(pCordb, pid));
+ IfFailRet(Startup(pCordb));
std::unique_lock<std::mutex> lockAttachedMutex(m_processAttachedMutex);
if (!m_processAttachedCV.wait_for(lockAttachedMutex, startupWaitTimeout, [this]{return m_processAttachedState == ProcessAttachedState::Attached;}))
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
HRESULT Status;
- IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+ IfFailRet(CheckDebugProcess());
ToRelease<ICorDebugThread> iCorThread;
IfFailRet(m_iCorProcess->GetThread(int(threadId), &iCorThread));
{
LogFuncEntry();
- bool haveProcess = HaveDebugProcess(m_debugProcessRWLock, m_iCorProcess, m_processAttachedMutex, m_processAttachedState);
+ bool haveProcess = HaveDebugProcess();
return m_sharedBreakpoints->UpdateLineBreakpoint(haveProcess, id, linenum, breakpoint);
}
LogFuncEntry();
#ifdef INTEROP_DEBUGGING
- // Note, we don't care about m_interopDebugging here, since breakpoint setup could be start before m_interopDebugging changes with env parsing
+ // Note, we don't care about m_interopDebugging here, since breakpoint setup could be start before m_interopDebugging changes with env parsing.
if (isNativeSource(filename))
- return m_uniqueInteropDebugger->SetLineBreakpoints(filename, lineBreakpoints, breakpoints);
+ return m_sharedInteropDebugger->SetLineBreakpoints(filename, lineBreakpoints, breakpoints);
#endif // INTEROP_DEBUGGING
- bool haveProcess = HaveDebugProcess(m_debugProcessRWLock, m_iCorProcess, m_processAttachedMutex, m_processAttachedState);
+ bool haveProcess = HaveDebugProcess();
return m_sharedBreakpoints->SetLineBreakpoints(haveProcess, filename, lineBreakpoints, breakpoints);
}
{
LogFuncEntry();
- bool haveProcess = HaveDebugProcess(m_debugProcessRWLock, m_iCorProcess, m_processAttachedMutex, m_processAttachedState);
+ bool haveProcess = HaveDebugProcess();
return m_sharedBreakpoints->SetFuncBreakpoints(haveProcess, funcBreakpoints, breakpoints);
}
return S_OK;
#ifdef INTEROP_DEBUGGING
- return m_uniqueInteropDebugger->BreakpointActivate(id, act);
+ // Note, we don't care about m_interopDebugging here, since breakpoint setup could be start before m_interopDebugging changes with env parsing.
+ return m_sharedInteropDebugger->BreakpointActivate(id, act);
#else
return E_FAIL;
#endif // INTEROP_DEBUGGING
HRESULT Status1 = m_sharedBreakpoints->AllBreakpointsActivate(act);
#ifdef INTEROP_DEBUGGING
- HRESULT Status2 = m_uniqueInteropDebugger->AllBreakpointsActivate(act);
+ // Note, we don't care about m_interopDebugging here, since breakpoint setup could be start before m_interopDebugging changes with env parsing.
+ HRESULT Status2 = m_sharedInteropDebugger->AllBreakpointsActivate(act);
return FAILED(Status1) ? Status1 : Status2;
#else
return Status1;
#endif // INTEROP_DEBUGGING
}
-static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModules, bool hotReload, ThreadId threadId, FrameLevel level, StackFrame &stackFrame, bool hotReloadAwareCaller)
+HRESULT ManagedDebuggerBase::GetFrameLocation(ICorDebugFrame *pFrame, ThreadId threadId, FrameLevel level, StackFrame &stackFrame, bool hotReloadAwareCaller)
{
HRESULT Status;
ULONG32 methodVersion = 1;
ULONG32 currentVersion = 1;
- if (hotReload)
+ if (m_hotReload)
{
// In case current (top) code version is 1, executed in this frame method version can't be not 1.
if (SUCCEEDED(pFunc->GetCurrentVersionNumber(¤tVersion)) && currentVersion != 1)
ULONG32 ilOffset;
Modules::SequencePoint sp;
- if (SUCCEEDED(pModules->GetFrameILAndSequencePoint(pFrame, ilOffset, sp)))
+ if (SUCCEEDED(m_sharedModules->GetFrameILAndSequencePoint(pFrame, ilOffset, sp)))
{
stackFrame.source = Source(sp.document);
stackFrame.line = sp.startLine;
return S_OK;
}
-HRESULT ManagedDebugger::GetFrameLocation(ICorDebugFrame *pFrame, ThreadId threadId, FrameLevel level, StackFrame &stackFrame)
-{
- return InternalGetFrameLocation(pFrame, m_sharedModules.get(), m_hotReload, threadId, level, stackFrame, false);
-}
-
static std::string GetModuleNameForFrame(ICorDebugFrame *pFrame)
{
ToRelease<ICorDebugFunction> pFunc;
return GetBasename(to_utf8(name));
}
-static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, bool interopDebugging, ICorDebugThread *pThread, ThreadId threadId, FrameLevel startFrame,
- unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames, bool hotReloadAwareCaller)
+HRESULT ManagedDebuggerBase::GetManagedStackTrace(ICorDebugThread *pThread, ThreadId threadId, FrameLevel startFrame, unsigned maxFrames,
+ std::vector<StackFrame> &stackFrames, int &totalFrames, bool hotReloadAwareCaller)
{
LogFuncEntry();
#ifdef INTEROP_DEBUGGING
// In case debug session without interop, we merge "[CoreCLR Native Frame]" and "user's native frame" into "[Native Frames]".
- const std::string FrameCLRNativeText = interopDebugging ? "[CoreCLR Native Frame]" : "[Native Frames]";
+ const std::string FrameCLRNativeText = m_interopDebugging ? "[CoreCLR Native Frame]" : "[Native Frames]";
#else
// CoreCLR native frame + at least one user's native frame (note, `FrameNative` case should never happen for not interop build)
static const std::string FrameCLRNativeText = "[Native Frames]";
case FrameCLRManaged:
{
StackFrame stackFrame;
- InternalGetFrameLocation(pFrame, pModules, hotReload, threadId, FrameLevel{currentFrame}, stackFrame, hotReloadAwareCaller);
+ GetFrameLocation(pFrame, threadId, FrameLevel{currentFrame}, stackFrame, hotReloadAwareCaller);
stackFrames.push_back(stackFrame);
stackFrames.back().addr = addr;
stackFrames.back().unknownFrameAddr = !addr; // Could be 0 here only in case some CoreCLR registers context issue.
}
#ifdef INTEROP_DEBUGGING
-static HRESULT InternalGetNativeStackTrace(InteropDebugging::InteropDebugger *pInteropDebugger, ThreadId threadId, FrameLevel startFrame,
- unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames)
+HRESULT ManagedDebuggerBase::GetNativeStackTrace(ThreadId threadId, FrameLevel startFrame, unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames)
{
LogFuncEntry();
HRESULT Status;
int currentFrame = -1;
- Status = pInteropDebugger->UnwindNativeFrames(int(threadId), true, 0, nullptr, [&](NativeFrame &nativeFrame)
+ Status = m_sharedInteropDebugger->UnwindNativeFrames(int(threadId), true, 0, nullptr, [&](NativeFrame &nativeFrame)
{
currentFrame++;
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
HRESULT Status;
- IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+ IfFailRet(CheckDebugProcess());
ToRelease<ICorDebugThread> pThread;
if (SUCCEEDED(Status = m_iCorProcess->GetThread(int(threadId), &pThread)))
- return InternalGetStackTrace(m_sharedModules.get(), m_hotReload, m_interopDebugging, pThread, threadId, startFrame, maxFrames, stackFrames, totalFrames, hotReloadAwareCaller);
+ return GetManagedStackTrace(pThread, threadId, startFrame, maxFrames, stackFrames, totalFrames, hotReloadAwareCaller);
#ifdef INTEROP_DEBUGGING
// E_INVALIDARG for ICorDebugProcess::GetThread() mean thread is not managed (can't found ICorDebugThread object that represents the thread)
if (m_interopDebugging && Status == E_INVALIDARG)
- return InternalGetNativeStackTrace(m_uniqueInteropDebugger.get(), threadId, startFrame, maxFrames, stackFrames, totalFrames);
+ return GetNativeStackTrace(threadId, startFrame, maxFrames, stackFrames, totalFrames);
#endif // INTEROP_DEBUGGING
return Status;
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
HRESULT Status;
- IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+ IfFailRet(CheckDebugProcess());
return m_sharedVariables->GetVariables(m_iCorProcess, variablesReference, filter, start, count, variables);
}
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
HRESULT Status;
- IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+ IfFailRet(CheckDebugProcess());
return m_sharedVariables->GetScopes(m_iCorProcess, frameId, scopes);
}
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
HRESULT Status;
- IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+ IfFailRet(CheckDebugProcess());
return m_sharedVariables->Evaluate(m_iCorProcess, frameId, expression, variable, output);
}
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
HRESULT Status;
- IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+ IfFailRet(CheckDebugProcess());
return m_sharedVariables->SetVariable(m_iCorProcess, name, value, ref, output);
}
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
HRESULT Status;
- IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+ IfFailRet(CheckDebugProcess());
return m_sharedVariables->SetExpression(m_iCorProcess, frameId, expression, evalFlags, value, output);
}
m_sharedModules->FindFunctions(pattern, limit, cb);
}
-static void InternalFindVariables(ICorDebugProcess *pProcess, Variables *pVariables, ThreadId thread, FrameLevel framelevel,
- string_view pattern, unsigned limit, ManagedDebugger::SearchCallback cb)
+void ManagedDebugger::FindVariables(ThreadId thread, FrameLevel framelevel, string_view pattern, unsigned limit, SearchCallback cb)
{
LogFuncEntry();
+
+ std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
+ if (FAILED(CheckDebugProcess()))
+ return;
+
StackFrame frame{thread, framelevel, ""};
std::vector<Scope> scopes;
std::vector<Variable> variables;
- HRESULT status = pVariables->GetScopes(pProcess, frame.id, scopes);
+ HRESULT status = m_sharedVariables->GetScopes(m_iCorProcess, frame.id, scopes);
if (FAILED(status))
{
LOGW("GetScopes failed: %s", errormessage(status));
return;
}
- status = pVariables->GetVariables(pProcess, scopes[0].variablesReference, VariablesNamed, 0, 0, variables);
+ status = m_sharedVariables->GetVariables(m_iCorProcess, scopes[0].variablesReference, VariablesNamed, 0, 0, variables);
if (FAILED(status))
{
LOGW("GetVariables failed: %s", errormessage(status));
}
}
-void ManagedDebugger::FindVariables(ThreadId thread, FrameLevel framelevel, string_view pattern, unsigned limit, SearchCallback cb)
-{
- LogFuncEntry();
-
- std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
- if (FAILED(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState)))
- return;
-
- InternalFindVariables(m_iCorProcess, m_sharedVariables.get(), thread, framelevel, pattern, limit, cb);
-}
-
-void ManagedDebugger::InputCallback(IORedirectHelper::StreamType type, span<char> text)
+void ManagedDebuggerBase::InputCallback(IORedirectHelper::StreamType type, span<char> text)
{
- m_sharedProtocol->EmitOutputEvent(type == IOSystem::Stderr ? OutputStdErr : OutputStdOut, {text.begin(), text.size()});
+ pProtocol->EmitOutputEvent(type == IOSystem::Stderr ? OutputStdErr : OutputStdOut, {text.begin(), text.size()});
}
{
std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
HRESULT Status;
- IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+ IfFailRet(CheckDebugProcess());
ToRelease<ICorDebugModule> pModule;
IfFailRet(GetModuleOfCurrentThreadCode(m_iCorProcess, int(GetLastStoppedThreadId()), &pModule));
return S_OK;
}
-HRESULT ManagedDebugger::ApplyPdbDeltaAndLineUpdates(const std::string &dllFileName, const std::string &deltaPDB, const std::string &lineUpdates,
- std::string &updatedDLL, std::unordered_set<mdTypeDef> &updatedTypeTokens)
+HRESULT ManagedDebuggerBase::ApplyPdbDeltaAndLineUpdates(const std::string &dllFileName, const std::string &deltaPDB, const std::string &lineUpdates,
+ std::string &updatedDLL, std::unordered_set<mdTypeDef> &updatedTypeTokens)
{
HRESULT Status;
ToRelease<ICorDebugModule> pModule;
std::vector<BreakpointEvent> events;
m_sharedBreakpoints->UpdateBreakpointsOnHotReload(pModule, pdbMethodTokens, events);
for (const BreakpointEvent &event : events)
- m_sharedProtocol->EmitBreakpointEvent(event);
+ pProtocol->EmitBreakpointEvent(event);
return S_OK;
}
-HRESULT ManagedDebugger::FindEvalCapableThread(ToRelease<ICorDebugThread> &pThread)
+HRESULT ManagedDebuggerBase::FindEvalCapableThread(ToRelease<ICorDebugThread> &pThread)
{
ThreadId lastStoppedId = GetLastStoppedThreadId();
std::vector<ThreadId> threadIds;
Unattached
};
-class ManagedDebugger : public IDebugger
+enum StartMethod
{
-private:
- friend class ManagedCallback;
- friend class CallbacksQueue;
+ StartNone,
+ StartLaunch,
+ StartAttach
+ //StartAttachForSuspendedLaunch
+};
+
+class ManagedDebuggerBase : public IDebugger
+{
+protected:
+ ManagedDebuggerBase(IProtocol *pProtocol);
+ ~ManagedDebuggerBase() override;
std::mutex m_processAttachedMutex; // Note, in case m_debugProcessRWLock+m_processAttachedMutex, m_debugProcessRWLock must be locked first.
std::condition_variable m_processAttachedCV;
void SetLastStoppedThread(ICorDebugThread *pThread);
void SetLastStoppedThreadId(ThreadId threadId);
+ void InvalidateLastStoppedThreadId();
- enum StartMethod
- {
- StartNone,
- StartLaunch,
- StartAttach
- //StartAttachForSuspendedLaunch
- } m_startMethod;
+ StartMethod m_startMethod;
std::string m_execPath;
std::vector<std::string> m_execArgs;
std::string m_cwd;
std::map<std::string, std::string> m_env;
bool m_isConfigurationDone;
- std::shared_ptr<IProtocol> m_sharedProtocol;
+ IProtocol *pProtocol;
std::shared_ptr<Threads> m_sharedThreads;
std::shared_ptr<Modules> m_sharedModules;
std::shared_ptr<EvalWaiter> m_sharedEvalWaiter;
std::shared_ptr<CallbacksQueue> m_sharedCallbacksQueue;
std::unique_ptr<ManagedCallback> m_uniqueManagedCallback;
#ifdef INTEROP_DEBUGGING
- std::unique_ptr<InteropDebugging::InteropDebugger> m_uniqueInteropDebugger;
+ std::shared_ptr<InteropDebugging::InteropDebugger> m_sharedInteropDebugger;
#endif // INTEROP_DEBUGGING
Utility::RWLock m_debugProcessRWLock;
IORedirectHelper m_ioredirect;
- void InputCallback(IORedirectHelper::StreamType, span<char> text);
+ HRESULT CheckDebugProcess();
+ bool HaveDebugProcess();
- static VOID StartupCallback(IUnknown *pCordb, PVOID parameter, HRESULT hr);
- HRESULT Startup(IUnknown *punk, DWORD pid);
+ void InputCallback(IORedirectHelper::StreamType, span<char> text);
void Cleanup();
-
void DisableAllBreakpointsAndSteppers();
- HRESULT GetFrameLocation(ICorDebugFrame *pFrame, ThreadId threadId, FrameLevel level, StackFrame &stackFrame);
-
- HRESULT RunProcess(const std::string& fileExec, const std::vector<std::string>& execArgs);
- HRESULT AttachToProcess(DWORD pid);
- HRESULT DetachFromProcess();
- HRESULT TerminateProcess();
-
- HRESULT RunIfReady();
+ HRESULT GetFrameLocation(ICorDebugFrame *pFrame, ThreadId threadId, FrameLevel level, StackFrame &stackFrame, bool hotReloadAwareCaller = false);
+ HRESULT GetManagedStackTrace(ICorDebugThread *pThread, ThreadId threadId, FrameLevel startFrame, unsigned maxFrames,
+ std::vector<StackFrame> &stackFrames, int &totalFrames, bool hotReloadAwareCaller);
+#ifdef INTEROP_DEBUGGING
+ HRESULT GetNativeStackTrace(ThreadId threadId, FrameLevel startFrame, unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames);
+#endif // INTEROP_DEBUGGING
HRESULT FindEvalCapableThread(ToRelease<ICorDebugThread> &pThread);
HRESULT ApplyPdbDeltaAndLineUpdates(const std::string &dllFileName, const std::string &deltaPDB, const std::string &lineUpdates,
std::string &updatedDLL, std::unordered_set<mdTypeDef> &updatedTypeTokens);
+};
+
+class ManagedDebuggerHelpers : public ManagedDebuggerBase
+{
+protected:
+ friend class ManagedCallback;
+ friend class CallbacksQueue;
+
+ ManagedDebuggerHelpers(IProtocol *pProtocol);
+
+ static VOID StartupCallback(IUnknown *pCordb, PVOID parameter, HRESULT hr);
+ HRESULT Startup(IUnknown *punk);
+ HRESULT RunIfReady();
+ HRESULT RunProcess(const std::string& fileExec, const std::vector<std::string>& execArgs);
+ HRESULT AttachToProcess();
+ HRESULT DetachFromProcess();
+ HRESULT TerminateProcess();
+};
+class ManagedDebugger final : public ManagedDebuggerHelpers
+{
public:
- ManagedDebugger(std::shared_ptr<IProtocol> &sharedProtocol);
- ~ManagedDebugger() override;
+ ManagedDebugger(IProtocol *pProtocol);
bool IsJustMyCode() const override { return m_justMyCode; }
void SetJustMyCode(bool enable) override;
HRESULT Disconnect(DisconnectAction action = DisconnectDefault) override;
ThreadId GetLastStoppedThreadId() override;
- void InvalidateLastStoppedThreadId();
HRESULT Continue(ThreadId threadId) override;
- HRESULT Pause(ThreadId lastStoppedThread) override;
- HRESULT GetThreads(std::vector<Thread> &threads) override;
+ HRESULT Pause(ThreadId lastStoppedThread, EventFormat eventFormat) override;
+ HRESULT GetThreads(std::vector<Thread> &threads, bool withNativeThreads = false) override;
HRESULT UpdateLineBreakpoint(int id, int linenum, Breakpoint &breakpoint) override;
HRESULT SetLineBreakpoints(const std::string& filename, const std::vector<LineBreakpoint> &lineBreakpoints, std::vector<Breakpoint> &breakpoints) override;
HRESULT SetFuncBreakpoints(const std::vector<FuncBreakpoint> &funcBreakpoints, std::vector<Breakpoint> &breakpoints) override;
#include "debugger/evaluator.h"
#include "debugger/valueprint.h"
#include "utils/torelease.h"
+#ifdef INTEROP_DEBUGGING
+#include "debugger/interop_debugging.h"
+#endif // INTEROP_DEBUGGING
namespace netcoredbg
{
m_sharedEvaluator->WalkMembers(iCorThreadObject, nullptr, FrameLevel{0}, false, [&](
ICorDebugType *,
bool,
- const std::string &memberName,
+ const std::string &memberName,
Evaluator::GetValueCallback getValue,
Evaluator::SetterData*)
{
for (auto &userThread : m_userThreads)
{
// ICorDebugThread::GetUserState not available for running thread.
- threads.emplace_back(userThread, GetThreadName(pProcess, userThread), procRunning);
+ threads.emplace_back(userThread, GetThreadName(pProcess, userThread), procRunning == TRUE);
}
return S_OK;
}
+#ifdef INTEROP_DEBUGGING
+// Caller should guarantee, that pProcess is not null.
+HRESULT Threads::GetInteropThreadsWithState(ICorDebugProcess *pProcess, InteropDebugging::InteropDebugger *pInteropDebugger, std::vector<Thread> &threads)
+{
+ HRESULT Status;
+ BOOL managedProcRunning = FALSE;
+ IfFailRet(pProcess->IsRunning(&managedProcRunning));
+
+ std::unordered_set<DWORD> managedThreads;
+ ToRelease<ICorDebugThreadEnum> iCorThreadEnum;
+ pProcess->EnumerateThreads(&iCorThreadEnum);
+ ULONG fetched = 0;
+ ToRelease<ICorDebugThread> iCorThread;
+ while (SUCCEEDED(iCorThreadEnum->Next(1, &iCorThread, &fetched)) && fetched == 1)
+ {
+ DWORD tid = 0;
+ if (SUCCEEDED(iCorThread->GetID(&tid)))
+ {
+ managedThreads.emplace(tid);
+ }
+ iCorThread.Free();
+ }
+
+ pInteropDebugger->WalkAllThreads([&](pid_t tid, bool isRunning)
+ {
+ ThreadId threadId(tid);
+
+ if (managedThreads.find((DWORD)tid) != managedThreads.end())
+ threads.emplace_back(threadId, GetThreadName(pProcess, threadId), managedProcRunning == TRUE, true);
+ else
+ threads.emplace_back(threadId, "<No name>", isRunning, false);
+ });
+
+ return S_OK;
+}
+#endif // INTEROP_DEBUGGING
+
HRESULT Threads::GetThreadIds(std::vector<ThreadId> &threads)
{
std::unique_lock<Utility::RWLock::Reader> read_lock(m_userThreadsRWLock.reader);
m_sharedEvaluator = sharedEvaluator;
}
+void Threads::ResetEvaluator()
+{
+ m_sharedEvaluator.reset();
+}
+
} // namespace netcoredbg
class Evaluator;
ThreadId getThreadId(ICorDebugThread *pThread);
+#ifdef INTEROP_DEBUGGING
+namespace InteropDebugging
+{
+class InteropDebugger;
+}
+#endif // INTEROP_DEBUGGING
class Threads
{
void Add(const ThreadId &threadId);
void Remove(const ThreadId &threadId);
HRESULT GetThreadsWithState(ICorDebugProcess *pProcess, std::vector<Thread> &threads);
+#ifdef INTEROP_DEBUGGING
+ HRESULT GetInteropThreadsWithState(ICorDebugProcess *pProcess, InteropDebugging::InteropDebugger *pInteropDebugger, std::vector<Thread> &threads);
+#endif // INTEROP_DEBUGGING
HRESULT GetThreadIds(std::vector<ThreadId> &threads);
std::string GetThreadName(ICorDebugProcess *pProcess, const ThreadId &userThread);
void SetEvaluator(std::shared_ptr<Evaluator> &sharedEvaluator);
+ void ResetEvaluator();
};
} // namespace netcoredbg
pidExited = true;
pidStatus = status;
- SetExitCode(pid, WEXITSTATUS(pidStatus));
+
+ if (WIFEXITED(pidStatus))
+ {
+ SetExitCode(pid, WEXITSTATUS(pidStatus));
+ }
+ else if (WIFSIGNALED(pidStatus))
+ {
+ LOGW("Process terminated without exiting, can't get exit code. Killed by signal %d. Assuming EXIT_FAILURE.", WTERMSIG(pidStatus));
+ SetExitCode(pid, EXIT_FAILURE);
+ }
+ else
+ {
+ SetExitCode(pid, 0);
+ }
}
bool waitpid_t::GetPidExitedStatus(pid_t &pid, int &status)
return hook::waitpid;
}
-// Note, we guaranty waitpid hook works only during debuggee process execution, it aimed to work only for PAL's waitpid calls interception.
+// Note, we guaranty `waitpid()` hook works only during debuggee process execution, it aimed to work only for PAL's `waitpid()` calls interception.
extern "C" pid_t waitpid(pid_t pid, int *status, int options)
{
#ifdef INTEROP_DEBUGGING
}
else if (WIFSIGNALED(*status))
{
- LOGW("Process terminated without exiting; can't get exit code. Killed by signal %d. Assuming EXIT_FAILURE.", WTERMSIG(*status));
+ LOGW("Process terminated without exiting, can't get exit code. Killed by signal %d. Assuming EXIT_FAILURE.", WTERMSIG(*status));
netcoredbg::hook::waitpid.SetExitCode(pid, EXIT_FAILURE);
}
}
return pidWaitRetval;
}
+// Note, liblttng-ust may call `wait()` at CoreCLR global/static initialization at dlopen() (debugger managed part related).
+extern "C" pid_t wait(int *status)
+{
+ return waitpid(-1, status, 0);
+}
+
} // namespace netcoredbg
#endif // FEATURE_PAL
virtual HRESULT Disconnect(DisconnectAction action = DisconnectDefault) = 0;
virtual ThreadId GetLastStoppedThreadId() = 0;
virtual HRESULT Continue(ThreadId threadId) = 0;
- virtual HRESULT Pause(ThreadId lastStoppedThread) = 0;
- virtual HRESULT GetThreads(std::vector<Thread> &threads) = 0;
+ virtual HRESULT Pause(ThreadId lastStoppedThread, EventFormat eventFormat) = 0;
+ virtual HRESULT GetThreads(std::vector<Thread> &threads, bool withNativeThreads = false) = 0;
virtual HRESULT UpdateLineBreakpoint(int id, int linenum, Breakpoint &breakpoint) = 0;
virtual HRESULT SetLineBreakpoints(const std::string& filename, const std::vector<LineBreakpoint> &lineBreakpoints, std::vector<Breakpoint> &breakpoints) = 0;
virtual HRESULT SetFuncBreakpoints(const std::vector<FuncBreakpoint> &funcBreakpoints, std::vector<Breakpoint> &breakpoints) = 0;
std::string name;
bool running;
- Thread(ThreadId id, const std::string& name, bool running) : id(id), name(name), running(running) {}
+#ifdef INTEROP_DEBUGGING
+ bool managed ;// exposed for CLI protocols
+ Thread(ThreadId id_, const std::string &name_, bool running_) : id(id_), name(name_), running(running_), managed(true) {}
+ Thread(ThreadId id_, const std::string &name_, bool running_, bool managed_) : id(id_), name(name_), running(running_), managed(managed_) {}
+#else
+ Thread(ThreadId id_, const std::string &name_, bool running_) : id(id_), name(name_), running(running_) {}
+#endif // INTEROP_DEBUGGING
};
struct Source
StackFrame frame; // exposed for MI and CLI protocols
Breakpoint breakpoint; // exposed for MI and CLI protocols
+#ifdef INTEROP_DEBUGGING
+ std::string signal_name; // exposed for CLI protocols
+#endif // INTEROP_DEBUGGING
+
StoppedEvent(StopReason reason, ThreadId threadId = ThreadId::Invalid)
:reason(reason), threadId(threadId), allThreadsStopped(true)
{}
enum ThreadReason
{
- ThreadStarted,
- ThreadExited
+ ManagedThreadStarted,
+ ManagedThreadExited,
+ NativeThreadAttached,
+ NativeThreadStarted,
+ NativeThreadExited
};
struct ThreadEvent
{
ThreadReason reason;
ThreadId threadId;
+ bool interopDebugging;
- ThreadEvent(ThreadReason reason, ThreadId threadId) : reason(reason), threadId(threadId) {}
+ ThreadEvent(ThreadReason reason_, ThreadId id_, bool interop_) : reason(reason_), threadId(id_), interopDebugging(interop_) {}
};
enum OutputCategory
}
};
+enum class EventFormat
+{
+ Default,
+ CLI
+};
+
} // namespace netcoredbg
std::shared_ptr<IDebugger> debugger;
try
{
- debugger.reset(new ManagedDebugger(protocol));
+ debugger.reset(new ManagedDebugger(protocol.get()));
}
catch (const std::exception &e)
{
static bool IsCoreCLRLibrary(const std::string &fullName)
{
+ // Could be part of SDK, but will be never part of debuggee process:
+ // libdbgshim.so // 2.1 - 6.0
+ // libmscordaccore.so // 2.1+
+ // libmscordbi.so // 2.1+
+ // libsos.so // 2.1
+ // libsosplugin.so // 2.1
+
static const std::vector<std::string> clrLibs{
- "libclrjit.so",
- "libcoreclr.so",
- "libcoreclrtraceptprovider.so",
- "libhostpolicy.so",
- "libclrgc.so"
+ "libclrjit.so", // 2.1+
+ "libcoreclr.so", // 2.1+
+ "libcoreclrtraceptprovider.so", // 2.1+
+ "libhostpolicy.so", // 2.1+
+ "System.Globalization.Native.so", // 2.1 - 3.1
+ "System.Security.Cryptography.Native.OpenSsl.so", // 2.1 - 3.1
+ "System.IO.Compression.Native.so", // 2.1 - 3.1
+ "System.Net.Security.Native.so", // 2.1 - 3.1
+ "System.Native.so", // 2.1 - 3.1
+ "System.Net.Http.Native.so", // 2.1 - 3.1
+ "libSystem.Native.so", // 5.0+
+ "libSystem.IO.Compression.Native.so", // 5.0+
+ "libSystem.Net.Security.Native.so", // 5.0+
+ "libSystem.Security.Cryptography.Native.OpenSsl.so", // 5.0+
+ "libSystem.Globalization.Native.so", // 6.0+
+ "libclrgc.so", // 7.0+
};
for (auto &clrLibName : clrLibs)
LibraryInfo &info = m_librariesInfo[startAddr];
info.fullName = fullName;
+ info.fullLoadName = libLoadName;
info.libEndAddr = endAddr;
symbolStatus = LoadDebuginfo(libLoadName, info);
info.isCoreCLR = IsCoreCLRLibrary(fullName);
});
}
+bool InteropLibraries::IsUserDebuggingCode(std::uintptr_t addr)
+{
+ bool isUserCode = false;
+ FindLibraryInfoForAddr(addr, [&](std::uintptr_t startAddr, LibraryInfo &info)
+ {
+ if (!info.isCoreCLR && info.dw != nullptr)
+ isUserCode = true;
+ });
+
+ return isUserCode;
+}
+
bool InteropLibraries::IsThumbCode(std::uintptr_t addr)
{
#if DEBUGGER_UNIX_ARM
return false;
}
+bool InteropLibraries::FindDataForNotClrAddr(std::uintptr_t addr, std::string &libLoadName, std::string &procName)
+{
+ bool isUserCode = true;
+ FindLibraryInfoForAddr(addr, [&](std::uintptr_t startAddr, LibraryInfo &info)
+ {
+ if (info.isCoreCLR)
+ {
+ isUserCode = false;
+ return;
+ }
+
+ libLoadName = GetBasename(info.fullLoadName);
+ // Remove version after ".so" (if load name have it)
+ static std::string versionDetect(".so.");
+ constexpr size_t versionDetectSize = 4;
+ if (libLoadName.size() > versionDetectSize)
+ {
+ size_t i = libLoadName.rfind(versionDetect);
+ if (i != std::string::npos)
+ libLoadName = libLoadName.substr(0, i + 3);
+ }
+
+ if (info.dw != nullptr)
+ {
+ std::string fullSourcePath;
+ int lineNum;
+ FindDataForAddrInDebugInfo(info.dw.get(), addr - startAddr, procName, fullSourcePath, lineNum);
+ return;
+ }
+
+ if (!info.proceduresDataValid)
+ CollectProcDataFromElf(startAddr, info);
+
+ if (info.proceduresData.empty() ||
+ addr >= info.proceduresData.rbegin()->second.endAddr)
+ return;
+
+ auto upper_bound = info.proceduresData.upper_bound(addr);
+ if (upper_bound != info.proceduresData.begin())
+ {
+ auto closest_lower = std::prev(upper_bound);
+ if (closest_lower->first <= addr && addr < closest_lower->second.endAddr)
+ {
+ if (!DemangleCXXABI(closest_lower->second.procName.c_str(), procName))
+ procName = closest_lower->second.procName + "()";
+ }
+ }
+ });
+
+ return isUserCode;
+}
+
} // namespace InteropDebugging
} // namespace netcoredbg
struct LibraryInfo
{
std::string fullName;
+ std::string fullLoadName;
std::uintptr_t libEndAddr; // have same logic as STL `end()` iterator - "first address after"
// debuginfo related
std::unique_ptr<elf::elf> ef;
void FindDataForAddr(std::uintptr_t addr, std::string &libName, std::uintptr_t &libStartAddr, std::string &procName,
std::uintptr_t &procStartAddr, std::string &fullSourcePath, int &lineNum);
+ bool FindDataForNotClrAddr(std::uintptr_t addr, std::string &libLoadName, std::string &procName);
+ bool IsUserDebuggingCode(std::uintptr_t addr);
bool IsThumbCode(std::uintptr_t addr);
private:
// Comma (,) Precedes the Assembly name.
// Period (.) Denotes namespace identifiers.
// Plus sign (+) Precedes a nested class.
-static void ParceTypeName(const std::string &fullName, std::string &mainTypeName, std::vector<std::string> &nestedClasses)
+static void ParseTypeName(const std::string &fullName, std::string &mainTypeName, std::vector<std::string> &nestedClasses)
{
std::string::size_type genericDelimiterPos = fullName.find("`");
std::string fullTypeName;
{
std::string mainTypeName;
std::vector<std::string> nestedClasses;
- ParceTypeName(entry, mainTypeName, nestedClasses);
+ ParseTypeName(entry, mainTypeName, nestedClasses);
// Resolve main type part.
mdTypeDef typeToken = mdTypeDefNil;
std::string frameLocation;
PrintFrameLocation(event.frame, frameLocation);
+ if (!event.frame.moduleOrLibName.empty())
+ {
+ frameLocation = event.frame.moduleOrLibName + "` " + frameLocation;
+ }
m_sourcePath = event.frame.source.path;
m_sourceLine = event.frame.line - m_listSize / 2;
m_stoppedAt = event.frame.line;
}
case StopPause:
{
- printf("\nstopped, reason: interrupted, thread id: %i, stopped threads: all, frame={%s}\n",
- int(event.threadId), frameLocation.c_str());
+#ifdef INTEROP_DEBUGGING
+ if (!event.signal_name.empty())
+ {
+ printf("\nstopped, reason: interrupted, signal-name=\"%s\", thread id: %i, stopped threads: all, frame={%s}\n",
+ event.signal_name.c_str(), int(event.threadId), frameLocation.c_str());
+ }
+ else
+#endif INTEROP_DEBUGGING
+ {
+ printf("\nstopped, reason: interrupted, thread id: %i, stopped threads: all, frame={%s}\n",
+ int(event.threadId), frameLocation.c_str());
+ }
break;
}
default:
const char *reasonText = "";
switch(event.reason)
{
- case ThreadStarted:
- reasonText = "thread created";
+ case ManagedThreadStarted:
+ reasonText = event.interopDebugging ? "managed thread created" : "thread created";
+ break;
+ case ManagedThreadExited:
+ reasonText = event.interopDebugging ? "managed thread exited" : "thread exited";
+ break;
+ case NativeThreadAttached:
+ reasonText = "native thread attached";
break;
- case ThreadExited:
- reasonText = "thread exited";
+ case NativeThreadStarted:
+ reasonText = "native thread created";
break;
+ case NativeThreadExited:
+ reasonText = "native thread exited";
+ break;
+ default:
+ return;
}
printf("\n%s, id: %i\n", reasonText, int(event.threadId));
}
}
std::vector<std::string> args = args_orig;
- ThreadId threadId{ ProtocolUtils::GetIntArg(args, "--thread", int(tid)) };
+ ProtocolUtils::StripArgs(args);
int lowFrame = 0;
int highFrame = FrameLevel::MaxFrameLevel;
- ProtocolUtils::StripArgs(args);
ProtocolUtils::GetIndices(args, lowFrame, highFrame);
+
+ // command "bt all"
+ if (!args.empty() && args[0] == "all")
+ {
+ std::vector<Thread> threads;
+ if (FAILED(m_sharedDebugger->GetThreads(threads, true)))
+ {
+ output = "No threads.";
+ return E_FAIL;
+ }
+
+ std::ostringstream ss;
+ int number = 1;
+
+ for (const auto &thread : threads)
+ {
+ std::string stackTrace;
+ if (SUCCEEDED(PrintFrames(thread.id, stackTrace, FrameLevel{lowFrame}, FrameLevel{highFrame})))
+ {
+ ss << "\nThread " << number << ", id=\"" << int(thread.id)
+ << "\", name=\"" << thread.name << "\", state=\""
+ << (thread.running ? "running" : "stopped") << "\"";
+#ifdef INTEROP_DEBUGGING
+ ss << ", type=\"" << (thread.managed ? "managed" : "native") << "\"";
+#endif // INTEROP_DEBUGGING
+ number++;
+
+ ss << "\n" << stackTrace;
+ }
+ }
+
+ if (ss.str().empty())
+ {
+ output = "No stacktraces.";
+ return E_FAIL;
+ }
+
+ output = ss.str();
+ return S_OK;
+ }
+
+ // command "bt [--thread TID]"
+ ThreadId threadId{ ProtocolUtils::GetIntArg(args, "--thread", int(tid)) };
return PrintFrames(threadId, output, FrameLevel{lowFrame}, FrameLevel{highFrame});
}
}
std::vector<Thread> threads;
- if (FAILED(m_sharedDebugger->GetThreads(threads)))
+ if (FAILED(m_sharedDebugger->GetThreads(threads, true)))
{
output = "No threads.";
return E_FAIL;
}
std::ostringstream ss;
- ss << "\nthreads=[\n";
+ ss << "Threads:";
- const char *sep = "";
+ int number = 1;
for (const Thread& thread : threads)
{
- ss << sep << "{id=\"" << int(thread.id)
+ ss << "\n" << number << ": id=\"" << int(thread.id)
<< "\", name=\"" << thread.name << "\", state=\""
- << (thread.running ? "running" : "stopped") << "\"}";
- sep = ",\n";
+ << (thread.running ? "running" : "stopped") << "\"";
+#ifdef INTEROP_DEBUGGING
+ ss << ", type=\"" << (thread.managed ? "managed" : "native") << "\"";
+#endif // INTEROP_DEBUGGING
+ number++;
}
-
- ss << "]";
output = ss.str();
return S_OK;
}
}
HRESULT Status;
- IfFailRet(m_sharedDebugger->Pause(ThreadId::AllThreads));
+ IfFailRet(m_sharedDebugger->Pause(ThreadId::AllThreads, EventFormat::CLI));
output = "^stopped";
return S_OK;
}
lock.unlock();
IfFailRet(m_sharedDebugger->ConfigurationDone());
- IfFailRet(m_sharedDebugger->Pause(ThreadId::AllThreads));
+ IfFailRet(m_sharedDebugger->Pause(ThreadId::AllThreads, EventFormat::CLI));
return S_OK;
}
if (m_processStatus == Running)
{
lock.unlock();
- m_sharedDebugger->Pause(ThreadId::AllThreads);
+ m_sharedDebugger->Pause(ThreadId::AllThreads, EventFormat::CLI);
}
}
const char *reasonText = "";
switch(event.reason)
{
- case ThreadStarted:
+ case ManagedThreadStarted:
reasonText = "thread-created";
break;
- case ThreadExited:
+ case ManagedThreadExited:
reasonText = "thread-exited";
break;
+ default:
+ return;
}
MIProtocol::Printf("=%s,id=\"%i\"\n", reasonText, int(event.threadId));
}
} },
{ "exec-interrupt", [&](const std::vector<std::string> &, std::string &output){
HRESULT Status;
- IfFailRet(sharedDebugger->Pause(ThreadId::AllThreads));
+ IfFailRet(sharedDebugger->Pause(ThreadId::AllThreads, EventFormat::Default));
output = "^done";
return S_OK;
} },
switch(event.reason)
{
- case ThreadStarted:
+ case ManagedThreadStarted:
body["reason"] = "started";
break;
- case ThreadExited:
+ case ManagedThreadExited:
body["reason"] = "exited";
break;
+ default:
+ return;
}
body["threadId"] = int(event.threadId);
{ "pause", [&](const json &arguments, json &body){
ThreadId threadId{int(arguments.at("threadId"))};
body["threadId"] = int(threadId);
- return sharedDebugger->Pause(threadId);
+ return sharedDebugger->Pause(threadId, EventFormat::Default);
} },
{ "next", [&](const json &arguments, json &body){
return sharedDebugger->StepCommand(ThreadId{int(arguments.at("threadId"))}, IDebugger::StepType::STEP_OVER);