Add support for Alpine Linux (#7440)
authorJan Vorlicek <janvorli@microsoft.com>
Tue, 4 Oct 2016 00:56:42 +0000 (02:56 +0200)
committerGitHub <noreply@github.com>
Tue, 4 Oct 2016 00:56:42 +0000 (02:56 +0200)
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
clrdefinitions.cmake
src/pal/src/CMakeLists.txt
src/pal/src/exception/seh-unwind.cpp
src/pal/src/exception/seh.cpp
src/pal/src/exception/signal.cpp
src/pal/src/include/pal/context.h
src/pal/src/init/pal.cpp
src/pal/src/thread/context.cpp
src/vm/stackwalk.cpp
src/vm/threads.cpp

index a40d7f2..140b787 100644 (file)
@@ -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)
index e088f12..135e8c2 100644 (file)
@@ -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)
index b099227..74f16aa 100644 (file)
@@ -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()
 
index 24eebbb..6f552bd 100644 (file)
@@ -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)
     {
index 38779bf..473c490 100644 (file)
@@ -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();
 
index c2c2179..26e2a01 100644 (file)
@@ -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);
 
index 5e37894..3da3005 100644 (file)
@@ -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
index a5edb36..0bda276 100644 (file)
@@ -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;
 }
 
index f832015..6a4a7e9 100644 (file)
@@ -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_
 }
 
index 29b5617..dbc83f4 100644 (file)
@@ -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
index b52d7dc..3d24a87 100644 (file)
@@ -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());
     }
 }