From 9c506048df2e03002eda612966977b739b585aba Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Tue, 4 Oct 2016 02:56:42 +0200 Subject: [PATCH] Add support for Alpine Linux (#7440) This change enables build of CoreCLR on Alpine Linux. Here is the list of changes: - Disable asserts checking RSP in arbitrary threads against cached stack limit for the respective thread. The stack on Alpine obviously grows over the limit reported by the pthread functions. - Disable using XSTATE. This should be re-enabled after MUSL gets the _xstate, _fpx_sw_bytes and related data structures added to the signal.h header. - Disable setting rlimit of RLIMIT_NOFILE to the max value, since it breaks debugging for some reason. - Add skipping over the hardware signal trampoline in the PAL_VirtualUnwind. While we were not trying to walk over it in a simple case, in a case where an exception was thrown from a catch handler of a hardware exception, we still attempted to walk over it and it fails on Alpine. - Fix detection of Alpine Linux in the PAL's CMakeLists.txt so that it works in Docker containers too. - Modified PAL_VirtualUnwind to make the check for unwinding past the bottom of the stack unconditional. We had a long list of platforms where we were doing this check and it doesn't hurt to do it on platforms where it is not needed. I have done that rather than adding a check for Alpine Linux as another platform that needs it. --- CMakeLists.txt | 12 ++++++++- clrdefinitions.cmake | 6 +++++ src/pal/src/CMakeLists.txt | 19 ++++++++------ src/pal/src/exception/seh-unwind.cpp | 49 +++++++++++++++++++++++------------- src/pal/src/exception/seh.cpp | 6 +++++ src/pal/src/exception/signal.cpp | 6 +++++ src/pal/src/include/pal/context.h | 17 +++++++++++++ src/pal/src/init/pal.cpp | 3 ++- src/pal/src/thread/context.cpp | 12 +++++---- src/vm/stackwalk.cpp | 2 ++ src/vm/threads.cpp | 2 ++ 11 files changed, 101 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a40d7f2..140b787 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,16 @@ if(CMAKE_SYSTEM_NAME STREQUAL Linux) clr_unknown_arch() endif() set(CLR_CMAKE_PLATFORM_LINUX 1) + + # Detect Alpine Linux + SET(OS_RELEASE_FILENAME "/etc/os-release") + if (EXISTS ${OS_RELEASE_FILENAME}) + file(READ ${OS_RELEASE_FILENAME} OS_RELEASE) + string(FIND "${OS_RELEASE}" "ID=alpine" CLR_CMAKE_PLATFORM_ALPINE_LINUX) + if(CLR_CMAKE_PLATFORM_ALPINE_LINUX EQUAL -1) + unset(CLR_CMAKE_PLATFORM_ALPINE_LINUX) + endif(CLR_CMAKE_PLATFORM_ALPINE_LINUX EQUAL -1) + endif(EXISTS ${OS_RELEASE_FILENAME}) endif(CMAKE_SYSTEM_NAME STREQUAL Linux) if(CMAKE_SYSTEM_NAME STREQUAL Darwin) @@ -562,4 +572,4 @@ if(CLR_CMAKE_BUILD_TESTS) add_subdirectory(tests) endif(CLR_CMAKE_BUILD_TESTS) -include(definitionsconsistencycheck.cmake) \ No newline at end of file +include(definitionsconsistencycheck.cmake) diff --git a/clrdefinitions.cmake b/clrdefinitions.cmake index e088f12..135e8c2 100644 --- a/clrdefinitions.cmake +++ b/clrdefinitions.cmake @@ -61,6 +61,12 @@ if (CLR_CMAKE_PLATFORM_UNIX) endif(CLR_CMAKE_PLATFORM_UNIX) +if(CLR_CMAKE_PLATFORM_ALPINE_LINUX) + # Alpine Linux doesn't have fixed stack limit, this define disables some stack pointer + # sanity checks in debug / checked build that rely on a fixed stack limit + add_definitions(-DNO_FIXED_STACK_LIMIT) +endif(CLR_CMAKE_PLATFORM_ALPINE_LINUX) + add_definitions(-D_BLD_CLR) add_definitions(-DDEBUGGING_SUPPORTED) add_definitions(-DPROFILING_SUPPORTED) diff --git a/src/pal/src/CMakeLists.txt b/src/pal/src/CMakeLists.txt index b099227..74f16aa 100644 --- a/src/pal/src/CMakeLists.txt +++ b/src/pal/src/CMakeLists.txt @@ -2,13 +2,6 @@ cmake_minimum_required(VERSION 2.8.12.2) include_directories(SYSTEM /usr/local/include) -# set kernel version to detect Alpine -EXEC_PROGRAM(uname ARGS -v OUTPUT_VARIABLE CMAKE_SYSTEM_KERNEL_VERSION) -string(FIND "${CMAKE_SYSTEM_KERNEL_VERSION}" "Alpine" PAL_SYSTEM_ALPINE) -if(PAL_SYSTEM_ALPINE EQUAL -1) - unset(PAL_SYSTEM_ALPINE) -endif() - include(configure.cmake) project(coreclrpal) @@ -70,6 +63,16 @@ elseif(PAL_CMAKE_PLATFORM_ARCH_ARM64) add_definitions(-D_WIN64=1) endif() +if(CMAKE_SYSTEM_NAME STREQUAL Linux AND NOT CLR_CMAKE_PLATFORM_ALPINE_LINUX) + # Currently the _xstate is not available on Alpine Linux + add_definitions(-DXSTATE_SUPPORTED) +endif(CMAKE_SYSTEM_NAME STREQUAL Linux AND NOT CLR_CMAKE_PLATFORM_ALPINE_LINUX) + +if(CLR_CMAKE_PLATFORM_ALPINE_LINUX) + # Setting RLIMIT_NOFILE breaks debugging of coreclr on Alpine Linux for some reason + add_definitions(-DDONT_SET_RLIMIT_NOFILE) +endif(CLR_CMAKE_PLATFORM_ALPINE_LINUX) + # turn off capability to remove unused functions (which was enabled in debug build with sanitizers) set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -Wl,--no-gc-sections") @@ -252,7 +255,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL Linux) find_library(UNWIND_ARCH NAMES unwind-x86_64) endif() - if(PAL_SYSTEM_ALPINE) + if(CLR_CMAKE_PLATFORM_ALPINE_LINUX) find_library(INTL intl) endif() diff --git a/src/pal/src/exception/seh-unwind.cpp b/src/pal/src/exception/seh-unwind.cpp index 24eebbb..6f552bd 100644 --- a/src/pal/src/exception/seh-unwind.cpp +++ b/src/pal/src/exception/seh-unwind.cpp @@ -221,15 +221,34 @@ static void GetContextPointers(unw_cursor_t *cursor, unw_context_t *unwContext, #endif } +extern int g_common_signal_handler_context_locvar_offset; + BOOL PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers) { int st; unw_context_t unwContext; unw_cursor_t cursor; -#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(_ARM64_) || defined(_ARM_) - DWORD64 curPc; -#endif + DWORD64 curPc = CONTEXTGetPC(context); + +#ifndef __APPLE__ + // Check if the PC is the return address from the SEHProcessException in the common_signal_handler. + // If that's the case, extract its local variable containing the native_context_t of the hardware + // exception and return that. This skips the hardware signal handler trampoline that the libunwind + // cannot cross on some systems. + if ((void*)curPc == g_SEHProcessExceptionReturnAddress) + { + ULONG contextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT | CONTEXT_EXCEPTION_ACTIVE; + + #if defined(_AMD64_) + contextFlags |= CONTEXT_XSTATE; + #endif + size_t nativeContext = *(size_t*)(CONTEXTGetFP(context) + g_common_signal_handler_context_locvar_offset); + CONTEXTFromNativeContext((const native_context_t *)nativeContext, context, contextFlags); + + return TRUE; + } +#endif if ((context->ContextFlags & CONTEXT_EXCEPTION_ACTIVE) != 0) { @@ -240,7 +259,7 @@ BOOL PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextP // So we compensate it by incrementing the PC before passing it to the unwinder. // Without it, the unwinder would not find unwind info if the hardware exception // happened in the first instruction of a function. - CONTEXTSetPC(context, CONTEXTGetPC(context) + 1); + CONTEXTSetPC(context, curPc + 1); } #if !UNWIND_CONTEXT_IS_UCONTEXT_T @@ -264,18 +283,6 @@ BOOL PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextP WinContextToUnwindCursor(context, &cursor); #endif -#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(_ARM64_) || defined(_ARM_) - // FreeBSD, NetBSD and OSX appear to do two different things when unwinding - // 1: If it reaches where it cannot unwind anymore, say a - // managed frame. It wil return 0, but also update the $pc - // 2: If it unwinds all the way to _start it will return - // 0 from the step, but $pc will stay the same. - // The behaviour of libunwind from nongnu.org is to null the PC - // So we bank the original PC here, so we can compare it after - // the step - curPc = CONTEXTGetPC(context); -#endif - st = unw_step(&cursor); if (st < 0) { @@ -303,12 +310,18 @@ BOOL PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextP // Update the passed in windows context to reflect the unwind // UnwindContextToWinContext(&cursor, context); -#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(_ARM64_) || defined(_ARM_) + + // FreeBSD, NetBSD, OSX and Alpine appear to do two different things when unwinding + // 1: If it reaches where it cannot unwind anymore, say a + // managed frame. It will return 0, but also update the $pc + // 2: If it unwinds all the way to _start it will return + // 0 from the step, but $pc will stay the same. + // So we detect that here and set the $pc to NULL in that case. + // This is the default behavior of the libunwind on Linux. if (st == 0 && CONTEXTGetPC(context) == curPc) { CONTEXTSetPC(context, 0); } -#endif if (contextPointers != NULL) { diff --git a/src/pal/src/exception/seh.cpp b/src/pal/src/exception/seh.cpp index 38779bf..473c490 100644 --- a/src/pal/src/exception/seh.cpp +++ b/src/pal/src/exception/seh.cpp @@ -88,6 +88,10 @@ PHARDWARE_EXCEPTION_SAFETY_CHECK_FUNCTION g_safeExceptionCheckFunction = NULL; PGET_GCMARKER_EXCEPTION_CODE g_getGcMarkerExceptionCode = NULL; +// Return address of the SEHProcessException, which is used to enable walking over +// the signal handler trampoline on some Unixes where the libunwind cannot do that. +void* g_SEHProcessExceptionReturnAddress = NULL; + /* Internal function definitions **********************************************/ /*++ @@ -245,6 +249,8 @@ Return value: BOOL SEHProcessException(PAL_SEHException* exception) { + g_SEHProcessExceptionReturnAddress = __builtin_return_address(0); + CONTEXT* contextRecord = exception->GetContextRecord(); EXCEPTION_RECORD* exceptionRecord = exception->GetExceptionRecord(); diff --git a/src/pal/src/exception/signal.cpp b/src/pal/src/exception/signal.cpp index c2c2179..26e2a01 100644 --- a/src/pal/src/exception/signal.cpp +++ b/src/pal/src/exception/signal.cpp @@ -100,6 +100,10 @@ static bool registered_sigterm_handler = false; struct sigaction g_previous_activation; #endif +// Offset of the local variable containing native context in the common_signal_handler function. +// This offset is relative to the frame pointer. +int g_common_signal_handler_context_locvar_offset = 0; + /* public function definitions ************************************************/ /*++ @@ -582,6 +586,7 @@ Note: the "pointers" parameter should contain a valid exception record pointer, but the ContextRecord pointer will be overwritten. --*/ +__attribute__((noinline)) static bool common_signal_handler(int code, siginfo_t *siginfo, void *sigcontext, int numParams, ...) { sigset_t signal_set; @@ -590,6 +595,7 @@ static bool common_signal_handler(int code, siginfo_t *siginfo, void *sigcontext native_context_t *ucontext; ucontext = (native_context_t *)sigcontext; + g_common_signal_handler_context_locvar_offset = (int)((char*)&ucontext - (char*)__builtin_frame_address(0)); AllocateExceptionRecords(&exceptionRecord, &contextRecord); diff --git a/src/pal/src/include/pal/context.h b/src/pal/src/include/pal/context.h index 5e37894..3da3005 100644 --- a/src/pal/src/include/pal/context.h +++ b/src/pal/src/include/pal/context.h @@ -139,6 +139,8 @@ typedef ucontext_t native_context_t; ///////////////////// // Extended state +#ifdef XSTATE_SUPPORTED + inline _fpx_sw_bytes *FPREG_FpxSwBytes(const ucontext_t *uc) { // Bytes 464..511 in the FXSAVE format are available for software to use for any purpose. In this case, they are used to @@ -185,6 +187,8 @@ inline void *FPREG_Xstate_Ymmh(const ucontext_t *uc) return reinterpret_cast<_xstate *>(FPREG_Fpstate(uc))->ymmh.ymmh_space; } +#endif // XSTATE_SUPPORTED + ///////////////////// #else // BIT64 @@ -468,6 +472,19 @@ inline static void CONTEXTSetPC(LPCONTEXT pContext, DWORD64 pc) #endif } +inline static DWORD64 CONTEXTGetFP(LPCONTEXT pContext) +{ +#if defined(_AMD64_) + return pContext->Rbp; +#elif defined(_ARM_) + return pContext->R7; +#elif defined(_ARM64_) + return pContext->X29; +#else +#error don't know how to get the frame pointer for this architecture +#endif +} + /*++ Function : CONTEXT_CaptureContext diff --git a/src/pal/src/init/pal.cpp b/src/pal/src/init/pal.cpp index a5edb36..0bda276 100644 --- a/src/pal/src/init/pal.cpp +++ b/src/pal/src/init/pal.cpp @@ -992,6 +992,7 @@ Return value: --*/ static BOOL INIT_IncreaseDescriptorLimit(void) { +#ifndef DONT_SET_RLIMIT_NOFILE struct rlimit rlp; int result; @@ -1008,7 +1009,7 @@ static BOOL INIT_IncreaseDescriptorLimit(void) { return FALSE; } - +#endif // !DONT_SET_RLIMIT_NOFILE return TRUE; } diff --git a/src/pal/src/thread/context.cpp b/src/pal/src/thread/context.cpp index f832015..6a4a7e9 100644 --- a/src/pal/src/thread/context.cpp +++ b/src/pal/src/thread/context.cpp @@ -467,13 +467,13 @@ void CONTEXTToNativeContext(CONST CONTEXT *lpContext, native_context_t *native) } // TODO: Enable for all Unix systems -#if defined(_AMD64_) && defined(__linux__) +#if defined(_AMD64_) && defined(XSTATE_SUPPORTED) if ((lpContext->ContextFlags & CONTEXT_XSTATE) == CONTEXT_XSTATE) { _ASSERTE(FPREG_HasExtendedState(native)); memcpy_s(FPREG_Xstate_Ymmh(native), sizeof(M128A) * 16, lpContext->VectorRegister, sizeof(M128A) * 16); } -#endif // _AMD64_ +#endif //_AMD64_ && XSTATE_SUPPORTED } /*++ @@ -564,22 +564,24 @@ void CONTEXTFromNativeContext(const native_context_t *native, LPCONTEXT lpContex #endif } - // TODO: Enable for all Unix systems -#if defined(_AMD64_) && defined(__linux__) +#ifdef _AMD64_ if ((contextFlags & CONTEXT_XSTATE) == CONTEXT_XSTATE) { + // TODO: Enable for all Unix systems +#if XSTATE_SUPPORTED if (FPREG_HasExtendedState(native)) { memcpy_s(lpContext->VectorRegister, sizeof(M128A) * 16, FPREG_Xstate_Ymmh(native), sizeof(M128A) * 16); } else +#endif // XSTATE_SUPPORTED { // Reset the CONTEXT_XSTATE bit(s) so it's clear that the extended state data in // the CONTEXT is not valid. const ULONG xstateFlags = CONTEXT_XSTATE & ~(CONTEXT_CONTROL & CONTEXT_INTEGER); lpContext->ContextFlags &= ~xstateFlags; } - } + } #endif // _AMD64_ } diff --git a/src/vm/stackwalk.cpp b/src/vm/stackwalk.cpp index 29b5617..dbc83f4 100644 --- a/src/vm/stackwalk.cpp +++ b/src/vm/stackwalk.cpp @@ -2560,7 +2560,9 @@ StackWalkAction StackFrameIterator::NextRaw(void) // to recover from AVs during profiler stackwalk.) PTR_VOID newSP = PTR_VOID((TADDR)GetRegdisplaySP(m_crawl.pRD)); +#ifndef NO_FIXED_STACK_LIMIT FAIL_IF_SPECULATIVE_WALK(newSP >= m_crawl.pThread->GetCachedStackLimit()); +#endif // !NO_FIXED_STACK_LIMIT FAIL_IF_SPECULATIVE_WALK(newSP < m_crawl.pThread->GetCachedStackBase()); #undef FAIL_IF_SPECULATIVE_WALK diff --git a/src/vm/threads.cpp b/src/vm/threads.cpp index b52d7dc..3d24a87 100644 --- a/src/vm/threads.cpp +++ b/src/vm/threads.cpp @@ -8245,7 +8245,9 @@ void CheckRegDisplaySP (REGDISPLAY *pRD) { if (pRD->SP && pRD->_pThread) { +#ifndef NO_FIXED_STACK_LIMIT _ASSERTE(PTR_VOID(pRD->SP) >= pRD->_pThread->GetCachedStackLimit()); +#endif // NO_FIXED_STACK_LIMIT _ASSERTE(PTR_VOID(pRD->SP) < pRD->_pThread->GetCachedStackBase()); } } -- 2.7.4