-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Arm64] System.Threading.Thread.Tests stack overflow #10015
Comments
The stack overflow occurs during the All other test cases pass. |
On my
This test tries to allocate 16MB on the stack. Legitimately causing a stack overflow. This looks like a test bug to me. It is also not clear why this is not failing on |
@kouvel I assert this is a corefx test bug |
I suspect pthread_create is not failing when 16 MB of stack size is requested and it only allocates 8 MB. On linux-x64 it seems to actually allocate the requested stack size despite the configured value, and this: https://linux.die.net/man/3/pthread_create
Seems to suggest that the configured value is intended to be the default stack size and not the max, I could be missing something. @sdmaclea could you please run the following on that machine and let me know the output? #include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <iostream>
using namespace std;
void PrintError(int error)
{
const char *errorName = "?";
switch(error)
{
case ENOMEM:
errorName = "ENOMEM";
break;
case EINVAL:
errorName = "EINVAL";
break;
case EAGAIN:
errorName = "EAGAIN";
break;
case EPERM:
errorName = "EPERM";
break;
default:
break;
}
cout << errorName << " (" << error << ')';
}
bool CheckAndPrintErrorOnNonzero(const char *op, int error)
{
if(error == 0)
return true;
cout << op << " failed: ";
PrintError(error);
cout << '\n';
return false;
}
void *ThreadStart(void *)
{
return nullptr;
}
int main()
{
pthread_attr_t attr;
int error = pthread_attr_init(&attr);
if(!CheckAndPrintErrorOnNonzero("attr_init", error))
return 0;
error = pthread_attr_setstacksize(&attr, (size_t)16 << 20);
if(!CheckAndPrintErrorOnNonzero("attr_setstacksize", error))
return 0;
pthread_t thread;
error = pthread_create(&thread, &attr, &ThreadStart, nullptr);
if(!CheckAndPrintErrorOnNonzero("create", error))
return 0;
error = pthread_attr_destroy(&attr);
if(!CheckAndPrintErrorOnNonzero("attr_destroy", error))
return 0;
error = pthread_getattr_np(thread, &attr);
if(!CheckAndPrintErrorOnNonzero("getattr", error))
return 0;
size_t stackSize;
error = pthread_attr_getstacksize(&attr, &stackSize);
if(!CheckAndPrintErrorOnNonzero("attr_getstacksize", error))
return 0;
cout << "Actual stack size: " << stackSize;
error = pthread_attr_destroy(&attr);
if(!CheckAndPrintErrorOnNonzero("attr_destroy", error))
return 0;
return 0;
} On linux-x64 I'm seeing that there's no error even for |
I'll try your test. Also note, while debuigging this I changed the request to 7MB from 16MB and the test passed. |
Your program as written reports |
If I do an allocation in the stack, like is done in this test case, I see a SEGV in the debugger. (At least with the hard limit) |
I guess it only commits the necessary pages, I should have figured. I'll fix.
Interesting. I'm not sure what we can do about that from the product side. Any ideas? I'll go ahead and fix the test to use at most 2 MB for 32-bit platforms, that should be safe. |
Actually that still seems odd, I guess it would skip the guard page and happens to work. It's not typical stack usage anyway, I'll make sure to go through the guard page. |
This is a 16GB 64 bit platform |
I just mean 2 GB should be a safe test for 32-bit and 64-bit |
It might just be that because this is a 64K page platform arm64 platform, the 16M-64K does not leave enough space for stack address randomization In gdb, I can see the stack from
|
Linux maps only a small portion of the stack space in the beginning and adds mapping more as necessary. But it keeps the virtual range reserved based on the ulimit for the primary thread and based on pthread internal limit or the limit asked for during pthread creation. So going over that limit should result in sigsegv. |
I guess something would need to ensure that stack is allocated in continuous fashion. For a local array it is similar to direct addressing so it could potentially skip the guard page and read/write other threads' stacks if stack address randomization is not enabled (or by chance). The compiler may do something for functions with larger stacks (though it doesn't seem to be happening here). alloca may do something as well. In any case I guess it is inherently unsafe and similar would apply to C# stackalloc which has to be in unsafe block anyway. |
The JIT inserts stack probes for all functions with frame larger than memory page. However, about a week ago or so, the stack probing was reverted back to a way where the SP doesn't move as the probe continues (dotnet/coreclr#17112). It was causing issues on Windows. But it needs to go back for Unix. |
I see, makes sense |
Works around https://github.com/dotnet/coreclr/issues/17170 - Works around an issue on linux-arm64 where pthread_create appears to reserve less stack space than requested without failing, and limits the max reserved stack size to the ulimit-configued value, leading to seg fault when the requested size of stack space is attempted to be used - Fixed to write every page in the stack region that is being checked, to guarantee hitting the guard page in case of failure. The JIT's stack probe for stackalloc should cover this, https://github.com/dotnet/coreclr/issues/16827 details why that was not happening, in any case this test is not intended to test stack probing behavior, so this change makes the test a bit stronger in what it actually intends to test.
Works around and closes https://github.com/dotnet/coreclr/issues/17170 - Works around an issue on linux-arm64 where pthread_create appears to reserve less stack space than requested without failing, and limits the max reserved stack size to the ulimit-configued value, leading to seg fault when the requested size of stack space is attempted to be used - Fixed to write every page in the stack region that is being checked, to guarantee hitting the guard page in case of failure. The JIT's stack probe for stackalloc should cover this, https://github.com/dotnet/coreclr/issues/16827 details why that was not happening, in any case this test is not intended to test stack probing behavior, so this change makes the test a bit stronger in what it actually intends to test.
The main issue here seems to be that pthread_create allocates less stack space than requested without failing. Filed issue https://sourceware.org/bugzilla/show_bug.cgi?id=23018, not sure if that's the right place but let's see. @sdmaclea it would be great if you could provide any info requested there (probably I guess they will ask for glibc version). |
Works around and closes https://github.com/dotnet/coreclr/issues/17170 - Works around an issue on linux-arm64 where pthread_create appears to reserve less stack space than requested without failing, and limits the max reserved stack size to the ulimit-configued value, leading to seg fault when the requested size of stack space is attempted to be used - Fixed to write every page in the stack region that is being checked, to guarantee hitting the guard page in case of failure. The JIT's stack probe for stackalloc should cover this, https://github.com/dotnet/coreclr/issues/16827 details why that was not happening, in any case this test is not intended to test stack probing behavior, so this change makes the test a bit stronger in what it actually intends to test.
I think you are right, the 64 KB buffer was not enough. Based on the response from the Bugzilla bug above, I think 4 pages worth of gap should be more than enough. @sdmaclea, could you please try the test below (slightly modified from above to fix some issues, changed 64 KB buffer to 256 KB) and see if it works? If it works I'll go ahead and change the test back to what it was before and increase the buffer. #include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <iostream>
using namespace std;
void PrintError(int error)
{
const char *errorName = "?";
switch(error)
{
case ENOMEM:
errorName = "ENOMEM";
break;
case EINVAL:
errorName = "EINVAL";
break;
case EAGAIN:
errorName = "EAGAIN";
break;
case EPERM:
errorName = "EPERM";
break;
default:
break;
}
cout << errorName << " (" << error << ')';
}
bool CheckAndPrintErrorOnNonzero(const char *op, int error)
{
if(error == 0)
return true;
cout << op << " failed: ";
PrintError(error);
cout << '\n';
return false;
}
size_t requestedStackSize = 16 << 20;
void *ThreadStart(void *)
{
char a[requestedStackSize - (256 << 10)];
memset(a, 0, sizeof(a));
cout << "success\n";
return nullptr;
}
int main()
{
pthread_attr_t attr;
int error = pthread_attr_init(&attr);
if(!CheckAndPrintErrorOnNonzero("attr_init", error))
return 0;
error = pthread_attr_setstacksize(&attr, requestedStackSize);
if(!CheckAndPrintErrorOnNonzero("attr_setstacksize", error))
return 0;
pthread_t thread;
error = pthread_create(&thread, &attr, &ThreadStart, nullptr);
if(!CheckAndPrintErrorOnNonzero("create", error))
return 0;
error = pthread_attr_destroy(&attr);
if(!CheckAndPrintErrorOnNonzero("attr_destroy", error))
return 0;
error = pthread_getattr_np(thread, &attr);
if(!CheckAndPrintErrorOnNonzero("getattr", error))
return 0;
error = pthread_attr_destroy(&attr);
if(!CheckAndPrintErrorOnNonzero("attr_destroy", error))
return 0;
error = pthread_join(thread, nullptr);
if(!CheckAndPrintErrorOnNonzero("attr_destroy", error))
return 0;
return 0;
} |
@kouvel Latest test passes with "success" |
Ok great, thanks! |
Leave some pages worth of buffer when allocating stack space. See https://github.com/dotnet/coreclr/issues/17170.
* Fix test for Thread constructor taking stack size Leave some pages worth of buffer when allocating stack space. See https://github.com/dotnet/coreclr/issues/17170. * Fix comment * Fix comment
The text was updated successfully, but these errors were encountered: