From 697fd3992f4e113bd9d63d2ac6605cf67356c54d Mon Sep 17 00:00:00 2001 From: Eugene Zemtsov Date: Sat, 28 Mar 2015 04:42:11 -0700 Subject: [PATCH] Make managed debugging on Linux possible (attach, load, bp, exceptions, stacktrace) Fixes that were required to achieve that 1. Funnel SIGTRAP into EE debugger. 2. Making that memory allocated by EE debugger is executable. 3. Disable metadata reading optimizations which are not working on Linux. 4. Workaround RtlRestoreContext not properly working with EFlags. 5. Avoid calls to ShimRemoteDataTarget.GetContext on Linux, it is not implemented and not needed for pure managed debugging. 6. Adjust IP value after breakpoint SIGTRAP to meet debuggers expectations. --- src/debug/daccess/dacdbiimpl.cpp | 22 +++++++++++++++++++++- src/debug/di/module.cpp | 15 ++++++++++++++- src/debug/ee/debugger.cpp | 12 ++++++++++++ src/pal/src/exception/signal.cpp | 33 ++++++++++++++++++++++++++++++++- src/utilcode/clrhost_nodependencies.cpp | 2 +- src/vm/exceptionhandling.cpp | 31 +++++++++++++++++++++++++++++-- 6 files changed, 109 insertions(+), 6 deletions(-) diff --git a/src/debug/daccess/dacdbiimpl.cpp b/src/debug/daccess/dacdbiimpl.cpp index 8151f79..755b4a0 100644 --- a/src/debug/daccess/dacdbiimpl.cpp +++ b/src/debug/daccess/dacdbiimpl.cpp @@ -476,6 +476,15 @@ BOOL DacDbiInterfaceImpl::IsTransitionStub(CORDB_ADDRESS address) BOOL fIsStub = FALSE; +#if defined(FEATURE_PAL) + // Currently IsIPInModule() is not implemented in the PAL. Rather than skipping the check, we should + // either E_NOTIMPL this API or implement IsIPInModule() in the PAL. Since ICDProcess::IsTransitionStub() + // is only called by VS in mixed-mode debugging scenarios, and mixed-mode debugging is not supported on + // Mac, there is really no incentive to implement this API on Mac at this point. + ThrowHR(E_NOTIMPL); + +#else // !FEATURE_PAL + TADDR ip = (TADDR)address; if (ip == NULL) @@ -493,6 +502,8 @@ BOOL DacDbiInterfaceImpl::IsTransitionStub(CORDB_ADDRESS address) fIsStub = IsIPInModule(m_globalBase, ip); } +#endif // FEATURE_PAL + return fIsStub; } @@ -5604,12 +5615,21 @@ void DacDbiInterfaceImpl::GetContext(VMPTR_Thread vmThread, DT_CONTEXT * pContex if (pFilterContext == NULL) { // If the filter context is NULL, then we use the true context of the thread. + +#ifdef FEATURE_PAL + // GetThreadContext() is currently not implemented in PAL because we have no way to convert a thread ID to a + // thread handle. The function to do the conversion is OpenThread(), which is not implemented in PAL. + // Instead, we just zero out the seed CONTEXT for the stackwalk. This tells the stackwalker to + // start the stackwalk with the first explicit frame. This won't work when we do native debugging, + // but that won't happen on the Linux/Mac since they don't support native debugging. + ZeroMemory(pContextBuffer, sizeof(*pContextBuffer)); +#else // FEATURE_PAL pContextBuffer->ContextFlags = CONTEXT_ALL; - IfFailThrow(m_pTarget->GetThreadContext(pThread->GetOSThreadId(), pContextBuffer->ContextFlags, sizeof(*pContextBuffer), reinterpret_cast(pContextBuffer))); +#endif // FEATURE_PAL } else { diff --git a/src/debug/di/module.cpp b/src/debug/di/module.cpp index f7eda08..d88b789 100644 --- a/src/debug/di/module.cpp +++ b/src/debug/di/module.cpp @@ -814,11 +814,12 @@ HRESULT CordbModule::InitPublicMetaDataFromFile() // Its possible that the debugger would still load the NGEN image sometime in the future and we will miss a sharing // opportunity. Its an acceptable loss from an imperfect heuristic. if (NULL == WszGetModuleHandle(szFullPathName)) +#endif { szFullPathName = NULL; fDebuggerLoadingNgen = false; } -#endif + } // If we don't have or decided not to load the NGEN image, check to see if IL image is available @@ -880,6 +881,11 @@ HRESULT CordbModule::InitPublicMetaDataFromFile(const WCHAR * pszFullPathName, DWORD dwOpenFlags, bool validateFileInfo) { +#ifdef FEATURE_PAL + // TODO: Some intricate details of file mapping don't work on Linux as on Windows. + // We have to revisit this and try to fix it for POSIX system. + return E_FAIL; +#else if (validateFileInfo) { // Check that we've got the right file to target. @@ -988,6 +994,7 @@ HRESULT CordbModule::InitPublicMetaDataFromFile(const WCHAR * pszFullPathName, } return hr; +#endif // FEATURE_PAL } //--------------------------------------------------------------------------------------- @@ -2550,6 +2557,7 @@ HRESULT CordbModule::CreateReaderForInMemorySymbols(REFIID riid, void** ppObj) ReleaseHolder pBinder; if (symFormat == IDacDbiInterface::kSymbolFormatPDB) { +#ifndef FEATURE_PAL // PDB format - use diasymreader.dll with COM activation InlineSString<_MAX_PATH> ssBuf; IfFailThrow(FakeCoCreateInstanceEx(CLSID_CorSymBinder_SxS, @@ -2557,6 +2565,11 @@ HRESULT CordbModule::CreateReaderForInMemorySymbols(REFIID riid, void** ppObj) IID_ISymUnmanagedBinder, (void**)&pBinder, NULL)); +#else + IfFailThrow(FakeCoCreateInstance(CLSID_CorSymBinder_SxS, + IID_ISymUnmanagedBinder, + (void**)&pBinder)); +#endif } else if (symFormat == IDacDbiInterface::kSymbolFormatILDB) { diff --git a/src/debug/ee/debugger.cpp b/src/debug/ee/debugger.cpp index 11460ec..f091b3c 100644 --- a/src/debug/ee/debugger.cpp +++ b/src/debug/ee/debugger.cpp @@ -16732,6 +16732,18 @@ void *DebuggerHeap::Alloc(DWORD size) ret = pCanary->GetUserAddr(); #endif +#ifdef FEATURE_PAL + // We don't have executable heap in PAL, but we still need to allocate + // executable memory, that's why have change protection level for + // each allocation. + // TODO: We need to look how JIT solves this problem. + DWORD unusedFlags; + if (!VirtualProtect(ret, size, PAGE_EXECUTE_READWRITE, &unusedFlags)) + { + _ASSERTE(!"VirtualProtect failed to make this memory executable"); + } +#endif // FEATURE_PAL + return ret; } diff --git a/src/pal/src/exception/signal.cpp b/src/pal/src/exception/signal.cpp index 99efbbc..2c99e6d 100644 --- a/src/pal/src/exception/signal.cpp +++ b/src/pal/src/exception/signal.cpp @@ -556,7 +556,38 @@ static void common_signal_handler(PEXCEPTION_POINTERS pointers, int code, g_hardwareExceptionHandler(&exception); - ASSERT("HandleHardwareException has returned, it should not.\n"); + if (pointers->ExceptionRecord->ExceptionCode == STATUS_BREAKPOINT || + pointers->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) + { + +#if HAVE_MACH_EXCEPTIONS + RtlRestoreContext(&exception.ContextRecord, &exception.ExceptionRecord); +#elif __LINUX__ + // Here we shamelessly exploit undocumented behavior of Linux signal handlers: + // + // When signal handler exits it completely restores context saved in ucontext, + // if any changes are applied to it by the signal handler they will also be + // restored and affect further course of execution. + // + // We'd love to avoid using this assumption, but unfortunately setcontext() + // (which is used in PAL implementation of RtlRestoreContext) doesn't restore eflags + // and loads zero into RAX register. It might be fine for exception handling, but unacceptable + // for debugger. That's why for debugger's signals (breakpoint and singlestep) we + // actually allow HardwareExceptionHandler to return and change context on exit. + + // TODO: We should just implement setcontext helper in assembly and call it from RtlRestoreContext + // on Linux. setcontext() is not actually that hard to implement. + + CONTEXTToNativeContext(&exception.ContextRecord, ucontext); + return; +#else // HAVE_MACH_EXCEPTIONS + #error not yet implemented +#endif // HAVE_MACH_EXCEPTIONS + } + else + { + ASSERT("HardwareExceptionHandler has returned, it should not."); + } } else { diff --git a/src/utilcode/clrhost_nodependencies.cpp b/src/utilcode/clrhost_nodependencies.cpp index 4a85e68..d95ac8f 100644 --- a/src/utilcode/clrhost_nodependencies.cpp +++ b/src/utilcode/clrhost_nodependencies.cpp @@ -581,7 +581,7 @@ void * __cdecl operator new[](size_t n, const CExecutable&, const NoThrow&) // This is a DEBUG routing to verify that a memory region complies with executable requirements BOOL DbgIsExecutable(LPVOID lpMem, SIZE_T length) { -#if defined(CROSSGEN_COMPILE) +#if defined(CROSSGEN_COMPILE) || defined(FEATURE_PAL) // No NX support on PAL or for crossgen compilations. return TRUE; #else // !defined(CROSSGEN_COMPILE) diff --git a/src/vm/exceptionhandling.cpp b/src/vm/exceptionhandling.cpp index 4012a62..17425e8 100644 --- a/src/vm/exceptionhandling.cpp +++ b/src/vm/exceptionhandling.cpp @@ -4625,7 +4625,7 @@ VOID DECLSPEC_NORETURN DispatchManagedException(PAL_SEHException& ex) VOID PALAPI HandleHardwareException(PAL_SEHException* ex) { - if (ex->ExceptionRecord.ExceptionCode != STATUS_BREAKPOINT) + if (ex->ExceptionRecord.ExceptionCode != STATUS_BREAKPOINT && ex->ExceptionRecord.ExceptionCode != STATUS_SINGLE_STEP) { // A hardware exception is handled only if it happened in a jitted code or // in one of the JIT helper functions (JIT_MemSet, ...) @@ -4644,7 +4644,34 @@ VOID PALAPI HandleHardwareException(PAL_SEHException* ex) UNREACHABLE(); } - _ASSERTE(!"HandleHardwareException: Hardware exception happened out of managed code"); + _ASSERTE(!"HandleHardwareException: Hardware exception happened out of managed code"); + } + else + { + // This is a breakpoint or single step stop, we report it to the debugger. + Thread *pThread = GetThread(); + if (pThread != NULL && g_pDebugInterface != NULL) + { + if (ex->ExceptionRecord.ExceptionCode == STATUS_BREAKPOINT) + { + // If this is breakpoint context is set up to point to an instuction after the break instuction. + // But debugger expectes to see context that points to the break instruction, that's why we correct it. + SetIP(&ex->ContextRecord, GetIP(&ex->ContextRecord) - CORDbg_BREAK_INSTRUCTION_SIZE); + ex->ExceptionRecord.ExceptionAddress = (void *)GetIP(&ex->ContextRecord); + } + + if (g_pDebugInterface->FirstChanceNativeException(&ex->ExceptionRecord, + &ex->ContextRecord, + ex->ExceptionRecord.ExceptionCode, + pThread)) + { + return; + // Ideally we'd like to put + // RtlRestoreContext(&ex->ContextRecord, &ex->ExceptionRecord); + // here, but RtlRestoreContext is not completely reliable for debugging on Linux (doesn't restore EFlags) + // that's why we'll just return. Read more in signal.cpp/common_signal_handler . + } + } } EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); } -- 2.7.4