[newlib-cygwin] Implement correct RLIMIT_STACK handling
Corinna Vinschen
corinna@sourceware.org
Sun Jul 5 13:51:00 GMT 2015
https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;h=a54bc198b16635956d1ac94595e8abdee10a2e60
commit a54bc198b16635956d1ac94595e8abdee10a2e60
Author: Corinna Vinschen <corinna@vinschen.de>
Date: Sun Jul 5 15:51:37 2015 +0200
Implement correct RLIMIT_STACK handling
* miscfuncs.cc (struct pthread_wrapper_arg): Add member guardsize.
(pthread_wrapper): Set thread stack guarantee according to guardsize.
Tweak assembler code so that $rax/$eax is not required by GCC to
prepare the wrapper_arg value.
(CygwinCreateThread): Fix deadzone handling. Drop setting a "POSIX"
guardpage (aka page w/ PAGE_NOACCESS). Always use Windows guard
pages instead. On post-XP systems (providing SetThreadStackGuarantee)
always set up stack Windows like with reserved/commited areas and
movable guard pages. Only on XP set up stack fully commited if the
guardpage size is not the default system guardpage size.
Fill out pthread_wrapper_arg::guardsize. Improve comments.
* resource.cc: Implement RSTACK_LIMIT Linux-like.
(DEFAULT_STACKSIZE): New macro.
(DEFAULT_STACKGUARD): Ditto.
(rlimit_stack_guard): New muto.
(rlimit_stack): New global variable holding current RSTACK_LIMIT values.
(__set_rlimit_stack): Set rlimit_stack under lock.
(__get_rlimit_stack): Initialize rlimit_stack from executable header
and return rlimit_stack values under lock.
(get_rlimit_stack): Filtering function to return useful default
stacksize from rlimit_stack.rlim_cur value.
(getrlimit): Call __get_rlimit_stack in RLIMIT_STACK case.
(setrlimit): Call __set_rlimit_stack in RLIMIT_STACK case.
* thread.cc (pthread::create): Fetch default stacksize calling
get_rlimit_stack.
(pthread_attr::pthread_attr): Fetch default guardsize calling
wincap.def_guard_page_size.
(pthread_attr_getstacksize): Fetch default stacksize calling
get_rlimit_stack.
* thread.h (PTHREAD_DEFAULT_STACKSIZE): Remove.
(PTHREAD_DEFAULT_GUARDSIZE): Remove.
(get_rlimit_stack): Declare.
Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
Diff:
---
winsup/cygwin/ChangeLog | 35 +++++++++++++++++
winsup/cygwin/miscfuncs.cc | 94 ++++++++++++++++++++++++++++-----------------
winsup/cygwin/release/2.1.0 | 6 +++
winsup/cygwin/resource.cc | 85 +++++++++++++++++++++++++++-------------
winsup/cygwin/thread.cc | 6 +--
winsup/cygwin/thread.h | 9 +----
winsup/doc/new-features.xml | 8 ++++
7 files changed, 172 insertions(+), 71 deletions(-)
diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog
index a1f4957..82a8a58 100644
--- a/winsup/cygwin/ChangeLog
+++ b/winsup/cygwin/ChangeLog
@@ -1,5 +1,40 @@
2015-07-05 Corinna Vinschen <corinna@vinschen.de>
+ * miscfuncs.cc (struct pthread_wrapper_arg): Add member guardsize.
+ (pthread_wrapper): Set thread stack guarantee according to guardsize.
+ Tweak assembler code so that $rax/$eax is not required by GCC to
+ prepare the wrapper_arg value.
+ (CygwinCreateThread): Fix deadzone handling. Drop setting a "POSIX"
+ guardpage (aka page w/ PAGE_NOACCESS). Always use Windows guard
+ pages instead. On post-XP systems (providing SetThreadStackGuarantee)
+ always set up stack Windows like with reserved/commited areas and
+ movable guard pages. Only on XP set up stack fully commited if the
+ guardpage size is not the default system guardpage size.
+ Fill out pthread_wrapper_arg::guardsize. Improve comments.
+ * resource.cc: Implement RSTACK_LIMIT Linux-like.
+ (DEFAULT_STACKSIZE): New macro.
+ (DEFAULT_STACKGUARD): Ditto.
+ (rlimit_stack_guard): New muto.
+ (rlimit_stack): New global variable holding current RSTACK_LIMIT values.
+ (__set_rlimit_stack): Set rlimit_stack under lock.
+ (__get_rlimit_stack): Initialize rlimit_stack from executable header
+ and return rlimit_stack values under lock.
+ (get_rlimit_stack): Filtering function to return useful default
+ stacksize from rlimit_stack.rlim_cur value.
+ (getrlimit): Call __get_rlimit_stack in RLIMIT_STACK case.
+ (setrlimit): Call __set_rlimit_stack in RLIMIT_STACK case.
+ * thread.cc (pthread::create): Fetch default stacksize calling
+ get_rlimit_stack.
+ (pthread_attr::pthread_attr): Fetch default guardsize calling
+ wincap.def_guard_page_size.
+ (pthread_attr_getstacksize): Fetch default stacksize calling
+ get_rlimit_stack.
+ * thread.h (PTHREAD_DEFAULT_STACKSIZE): Remove.
+ (PTHREAD_DEFAULT_GUARDSIZE): Remove.
+ (get_rlimit_stack): Declare.
+
+2015-07-05 Corinna Vinschen <corinna@vinschen.de>
+
* fhandler_process.cc (heap_info::heap_info): Disable fetching heap info
on 64 bit XP/2003. Explain why.
* wincap.h (wincaps::has_broken_rtl_query_process_debug_information):
diff --git a/winsup/cygwin/miscfuncs.cc b/winsup/cygwin/miscfuncs.cc
index 7f324b9..4a7a1b8 100644
--- a/winsup/cygwin/miscfuncs.cc
+++ b/winsup/cygwin/miscfuncs.cc
@@ -560,6 +560,7 @@ struct pthread_wrapper_arg
PBYTE stackaddr;
PBYTE stackbase;
PBYTE stacklimit;
+ ULONG guardsize;
};
DWORD WINAPI
@@ -592,7 +593,14 @@ pthread_wrapper (PVOID arg)
The below assembler code will release the OS stack after switching to our
new stack. */
wrapper_arg.stackaddr = dealloc_addr;
-
+ /* On post-XP systems, set thread stack guarantee matching the guardsize.
+ Note that the guardsize is one page bigger than the guarantee. */
+ if (wincap.has_set_thread_stack_guarantee ()
+ && wrapper_arg.guardsize > wincap.def_guard_page_size ())
+ {
+ wrapper_arg.guardsize -= wincap.page_size ();
+ SetThreadStackGuarantee (&wrapper_arg.guardsize);
+ }
/* Initialize new _cygtls. */
_my_tls.init_thread (wrapper_arg.stackbase - CYGTLS_PADSIZE,
(DWORD (*)(void*, void*)) wrapper_arg.func);
@@ -632,7 +640,7 @@ pthread_wrapper (PVOID arg)
#endif
#ifdef __x86_64__
__asm__ ("\n\
- movq %[WRAPPER_ARG], %%rbx # Load &wrapper_arg into rbx \n\
+ leaq %[WRAPPER_ARG], %%rbx # Load &wrapper_arg into rbx \n\
movq (%%rbx), %%r12 # Load thread func into r12 \n\
movq 8(%%rbx), %%r13 # Load thread arg into r13 \n\
movq 16(%%rbx), %%rcx # Load stackaddr into rcx \n\
@@ -652,11 +660,11 @@ pthread_wrapper (PVOID arg)
# register r13 and then just call the function. \n\
movq %%r13, %%rcx # Move thread arg to 1st arg reg\n\
call *%%r12 # Call thread func \n"
- : : [WRAPPER_ARG] "r" (&wrapper_arg),
+ : : [WRAPPER_ARG] "o" (wrapper_arg),
[CYGTLS] "i" (CYGTLS_PADSIZE));
#else
__asm__ ("\n\
- movl %[WRAPPER_ARG], %%ebx # Load &wrapper_arg into ebx \n\
+ leal %[WRAPPER_ARG], %%ebx # Load &wrapper_arg into ebx \n\
movl (%%ebx), %%eax # Load thread func into eax \n\
movl 4(%%ebx), %%ecx # Load thread arg into ecx \n\
movl 8(%%ebx), %%edx # Load stackaddr into edx \n\
@@ -683,7 +691,7 @@ pthread_wrapper (PVOID arg)
# stack in the expected spot. \n\
popl %%eax # Pop thread_func address \n\
call *%%eax # Call thread func \n"
- : : [WRAPPER_ARG] "r" (&wrapper_arg),
+ : : [WRAPPER_ARG] "o" (wrapper_arg),
[CYGTLS] "i" (CYGTLS_PADSIZE));
#endif
/* pthread::thread_init_wrapper calls pthread::exit, which
@@ -777,7 +785,8 @@ CygwinCreateThread (LPTHREAD_START_ROUTINE thread_func, PVOID thread_arg,
if (stackaddr)
{
- /* If the application provided the stack, just use it. */
+ /* If the application provided the stack, just use it. There won't
+ be any stack overflow handling! */
wrapper_arg->stackaddr = (PBYTE) stackaddr;
wrapper_arg->stackbase = (PBYTE) stackaddr + stacksize;
}
@@ -790,10 +799,8 @@ CygwinCreateThread (LPTHREAD_START_ROUTINE thread_func, PVOID thread_arg,
real_guardsize = roundup2 (guardsize, wincap.page_size ());
/* Add the guardsize to the stacksize */
real_stacksize += real_guardsize;
- /* If we use the default Windows guardpage method, we have to take
- the 2 pages dead zone into account. */
- if (real_guardsize == wincap.page_size ())
- real_stacksize += 2 * wincap.page_size ();
+ /* Take dead zone page into account, which always stays uncommited. */
+ real_stacksize += wincap.page_size ();
/* Now roundup the result to the next allocation boundary. */
real_stacksize = roundup2 (real_stacksize,
wincap.allocation_granularity ());
@@ -811,46 +818,63 @@ CygwinCreateThread (LPTHREAD_START_ROUTINE thread_func, PVOID thread_arg,
#endif
if (!real_stackaddr)
return NULL;
- /* Set up committed region. Two cases: */
- if (real_guardsize != wincap.page_size ())
+ /* Set up committed region. We have two cases: */
+ if (!wincap.has_set_thread_stack_guarantee ()
+ && real_guardsize != wincap.def_guard_page_size ())
{
- /* If guardsize is set to something other than the page size, we
- commit the entire stack and, if guardsize is > 0, we set up a
- POSIX guardpage. We don't set up a Windows guardpage. */
- if (!VirtualAlloc (real_stackaddr, real_guardsize, MEM_COMMIT,
- PAGE_NOACCESS))
+ /* If guardsize is set to something other than the default guard page
+ size, and if we're running on Windows XP 32 bit, we commit the
+ entire stack, and, if guardsize is > 0, set up a guard page. */
+ real_stacklimit = (PBYTE) real_stackaddr + wincap.page_size ();
+ if (real_guardsize
+ && !VirtualAlloc (real_stacklimit, real_guardsize, MEM_COMMIT,
+ PAGE_READWRITE | PAGE_GUARD))
goto err;
- real_stacklimit = (PBYTE) real_stackaddr + real_guardsize;
- if (!VirtualAlloc (real_stacklimit, real_stacksize - real_guardsize,
+ real_stacklimit += real_guardsize;
+ if (!VirtualAlloc (real_stacklimit, real_stacksize - real_guardsize
+ - wincap.page_size (),
MEM_COMMIT, PAGE_READWRITE))
goto err;
}
else
{
- /* If guardsize is exactly the page_size, we can assume that the
- application will behave Windows conformant in terms of stack usage.
- We can especially assume that it never allocates more than one
- page at a time (alloca/_chkstk). Therefore, this is the default
- case which allows a Windows compatible stack setup with a
- reserved region, a guard page, and a commited region. We don't
- need to set up a POSIX guardpage since Windows already handles
- stack overflow: Trying to extend the stack into the last three
- pages of the stack results in a SEGV.
- We always commit 64K here, starting with the guardpage. */
+ /* Otherwise we set up the stack like the OS does, with a reserved
+ region, the guard pages, and a commited region. We commit the
+ stack commit size from the executable header, but at least
+ PTHREAD_STACK_MIN (64K). */
+ static ULONG exe_commitsize;
+
+ if (!exe_commitsize)
+ {
+ PIMAGE_DOS_HEADER dosheader;
+ PIMAGE_NT_HEADERS ntheader;
+
+ dosheader = (PIMAGE_DOS_HEADER) GetModuleHandle (NULL);
+ ntheader = (PIMAGE_NT_HEADERS)
+ ((PBYTE) dosheader + dosheader->e_lfanew);
+ exe_commitsize = ntheader->OptionalHeader.SizeOfStackCommit;
+ exe_commitsize = roundup2 (exe_commitsize, wincap.page_size ());
+ }
+ ULONG commitsize = exe_commitsize;
+ if (commitsize > real_stacksize - real_guardsize
+ - wincap.page_size ())
+ commitsize = real_stacksize - real_guardsize - wincap.page_size ();
+ else if (commitsize < PTHREAD_STACK_MIN)
+ commitsize = PTHREAD_STACK_MIN;
real_stacklimit = (PBYTE) real_stackaddr + real_stacksize
- - wincap.allocation_granularity ();
- if (!VirtualAlloc (real_stacklimit, wincap.page_size (), MEM_COMMIT,
- PAGE_READWRITE | PAGE_GUARD))
+ - commitsize - real_guardsize;
+ if (!VirtualAlloc (real_stacklimit, real_guardsize,
+ MEM_COMMIT, PAGE_READWRITE | PAGE_GUARD))
goto err;
- real_stacklimit += wincap.page_size ();
- if (!VirtualAlloc (real_stacklimit, wincap.allocation_granularity ()
- - wincap.page_size (), MEM_COMMIT,
+ real_stacklimit += real_guardsize;
+ if (!VirtualAlloc (real_stacklimit, commitsize, MEM_COMMIT,
PAGE_READWRITE))
goto err;
}
wrapper_arg->stackaddr = (PBYTE) real_stackaddr;
wrapper_arg->stackbase = (PBYTE) real_stackaddr + real_stacksize;
wrapper_arg->stacklimit = real_stacklimit;
+ wrapper_arg->guardsize = real_guardsize;
}
/* Use the STACK_SIZE_PARAM_IS_A_RESERVATION parameter so only the
minimum size for a thread stack is reserved by the OS. Note that we
diff --git a/winsup/cygwin/release/2.1.0 b/winsup/cygwin/release/2.1.0
index da484f5..aca69cd 100644
--- a/winsup/cygwin/release/2.1.0
+++ b/winsup/cygwin/release/2.1.0
@@ -1,6 +1,12 @@
What's new:
-----------
+- Handle pthread stacksizes as in GLibc: Default to RLIMIT_STACK resource.
+ Allow to set RLIMIT_STACK via setrlimit. Default RLIMIT_STACK to value
+ from executable header as described on
+ https://msdn.microsoft.com/en-us/library/windows/desktop/ms686774.aspx
+ Default stacksize to 2 Megs in case RLIMIT_STACK is set to RLIM_INFINITY.
+
- First cut of an implementation to allow signal handlers running on an
alternate signal stack.
diff --git a/winsup/cygwin/resource.cc b/winsup/cygwin/resource.cc
index 895ba7f..a5a23a4 100644
--- a/winsup/cygwin/resource.cc
+++ b/winsup/cygwin/resource.cc
@@ -111,6 +111,61 @@ getrusage (int intwho, struct rusage *rusage_in)
return res;
}
+/* Default stacksize in case RLIMIT_STACK is RLIM_INFINITY is 2 Megs with
+ system-dependent number of guard pages. The pthread stacksize does not
+ include the guardpage size, so we have to subtract the default guardpage
+ size. Additionally the Windows stack handling disallows to commit the
+ last page, so we subtract it, too. */
+#define DEFAULT_STACKSIZE (2 * 1024 * 1024)
+#define DEFAULT_STACKGUARD (wincap.def_guard_page_size() + wincap.page_size ())
+
+muto NO_COPY rlimit_stack_guard;
+static struct rlimit rlimit_stack = { 0, RLIM_INFINITY };
+
+static void
+__set_rlimit_stack (const struct rlimit *rlp)
+{
+ rlimit_stack_guard.init ("rlimit_stack_guard")->acquire ();
+ rlimit_stack = *rlp;
+ rlimit_stack_guard.release ();
+}
+
+static void
+__get_rlimit_stack (struct rlimit *rlp)
+{
+ rlimit_stack_guard.init ("rlimit_stack_guard")->acquire ();
+ if (!rlimit_stack.rlim_cur)
+ {
+ /* Fetch the default stacksize from the executable header... */
+ PIMAGE_DOS_HEADER dosheader;
+ PIMAGE_NT_HEADERS ntheader;
+
+ dosheader = (PIMAGE_DOS_HEADER) GetModuleHandle (NULL);
+ ntheader = (PIMAGE_NT_HEADERS) ((PBYTE) dosheader + dosheader->e_lfanew);
+ rlimit_stack.rlim_cur = ntheader->OptionalHeader.SizeOfStackReserve;
+ /* ...and subtract the guardpages. */
+ rlimit_stack.rlim_cur -= DEFAULT_STACKGUARD;
+ }
+ *rlp = rlimit_stack;
+ rlimit_stack_guard.release ();
+}
+
+size_t
+get_rlimit_stack (void)
+{
+ struct rlimit rl;
+
+ __get_rlimit_stack (&rl);
+ /* RLIM_INFINITY doesn't make much sense. As in glibc, use an
+ "architecture-specific default". */
+ if (rl.rlim_cur == RLIM_INFINITY)
+ rl.rlim_cur = DEFAULT_STACKSIZE - DEFAULT_STACKGUARD;
+ /* Always return at least minimum stacksize. */
+ else if (rl.rlim_cur < PTHREAD_STACK_MIN)
+ rl.rlim_cur = PTHREAD_STACK_MIN;
+ return (size_t) rl.rlim_cur;
+}
+
extern "C" int
getrlimit (int resource, struct rlimit *rlp)
{
@@ -127,32 +182,7 @@ getrlimit (int resource, struct rlimit *rlp)
case RLIMIT_AS:
break;
case RLIMIT_STACK:
- PTEB teb;
- /* 2015-06-26: Originally rlim_cur returned the size of the still
- available stack area on the current stack, rlim_max the total size
- of the current stack. Two problems:
-
- - Per POSIX, RLIMIT_STACK returns "the maximum size of the initial
- thread's stack, in bytes. The implementation does not
- automatically grow the stack beyond this limit".
-
- - With the implementation of sigaltstack, the current stack is not
- necessarily the "initial thread's stack" anymore. Rather, when
- called from a signal handler running on the alternate stack,
- RLIMIT_STACK should return the size of the original stack.
-
- rlim_cur is now the size of the stack. For system-provided stacks
- it's the size between DeallocationStack and StackBase. For
- application-provided stacks (via pthread_attr_setstack),
- DeallocationStack is NULL, but StackLimit points to the bottom
- of the stack.
-
- rlim_max is set to RLIM_INFINITY since there's no hard limit
- for stack sizes on Windows. */
- teb = NtCurrentTeb ();
- rlp->rlim_cur = (rlim_t) teb->Tib.StackBase
- - (rlim_t) (teb->DeallocationStack
- ?: teb->Tib.StackLimit);
+ __get_rlimit_stack (rlp);
break;
case RLIMIT_NOFILE:
rlp->rlim_cur = getdtablesize ();
@@ -206,6 +236,9 @@ setrlimit (int resource, const struct rlimit *rlp)
if (rlp->rlim_cur != RLIM_INFINITY)
return setdtablesize (rlp->rlim_cur);
break;
+ case RLIMIT_STACK:
+ __set_rlimit_stack (rlp);
+ break;
default:
set_errno (EINVAL);
__leave;
diff --git a/winsup/cygwin/thread.cc b/winsup/cygwin/thread.cc
index 9320868..b92a806 100644
--- a/winsup/cygwin/thread.cc
+++ b/winsup/cygwin/thread.cc
@@ -475,7 +475,7 @@ pthread::create (void *(*func) (void *), pthread_attr *newattr,
mutex.lock ();
/* stackaddr holds the uppermost stack address. See the comments in
pthread_attr_setstack and pthread_attr_setstackaddr for a description. */
- ULONG stacksize = attr.stacksize ?: PTHREAD_DEFAULT_STACKSIZE;
+ ULONG stacksize = attr.stacksize ?: get_rlimit_stack ();
PVOID stackaddr = attr.stackaddr ? ((caddr_t) attr.stackaddr - stacksize)
: NULL;
win32_obj_id = CygwinCreateThread (thread_init_wrapper, this, stackaddr,
@@ -1093,7 +1093,7 @@ pthread::resume ()
pthread_attr::pthread_attr ():verifyable_object (PTHREAD_ATTR_MAGIC),
joinable (PTHREAD_CREATE_JOINABLE), contentionscope (PTHREAD_SCOPE_PROCESS),
inheritsched (PTHREAD_INHERIT_SCHED), stackaddr (NULL), stacksize (0),
-guardsize (PTHREAD_DEFAULT_GUARDSIZE)
+guardsize (wincap.def_guard_page_size ())
{
schedparam.sched_priority = 0;
}
@@ -2330,7 +2330,7 @@ pthread_attr_getstacksize (const pthread_attr_t *attr, size_t *size)
/* If the stacksize has not been set by the application, return the
default stacksize. Note that this is different from what
pthread_attr_getstack returns. */
- *size = (*attr)->stacksize ?: PTHREAD_DEFAULT_STACKSIZE;
+ *size = (*attr)->stacksize ?: get_rlimit_stack ();
return 0;
}
diff --git a/winsup/cygwin/thread.h b/winsup/cygwin/thread.h
index 3650e95..a6c7358 100644
--- a/winsup/cygwin/thread.h
+++ b/winsup/cygwin/thread.h
@@ -16,13 +16,8 @@ details. */
#define WRITE_LOCK 1
#define READ_LOCK 2
-/* Default is a 1 Megs stack with a 4K guardpage. The pthread stacksize
- does not include the guardpage size, so we subtract the default guardpage
- size. Additionally, the Windows stack handling disallows to use the last
- two pages as guard page (tested on XP and W7). That results in a zone of
- three pages which have to be subtract to get the actual stack size. */
-#define PTHREAD_DEFAULT_STACKSIZE (1024 * 1024 - 3 * wincap.page_size ())
-#define PTHREAD_DEFAULT_GUARDSIZE (wincap.page_size ())
+/* resource.cc */
+extern size_t get_rlimit_stack (void);
#include <pthread.h>
#include <limits.h>
diff --git a/winsup/doc/new-features.xml b/winsup/doc/new-features.xml
index be3e389..c52574c 100644
--- a/winsup/doc/new-features.xml
+++ b/winsup/doc/new-features.xml
@@ -9,6 +9,14 @@
<itemizedlist mark="bullet">
<listitem><para>
+Handle pthread stacksizes as in GLibc: Default to RLIMIT_STACK resource.
+Allow to set RLIMIT_STACK via setrlimit. Default RLIMIT_STACK to value
+from executable header as described on the MSDN website
+<ulink url="https://msdn.microsoft.com/en-us/library/windows/desktop/ms686774.aspx">Thread Stack Size</ulink>
+Default stacksize to 2 Megs in case RLIMIT_STACK is set to RLIM_INFINITY.
+</para></listitem>
+
+<listitem><para>
First cut of an implementation to allow signal handlers running on an
alternate signal stack.
</para></listitem>
More information about the Cygwin-cvs
mailing list