-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[asan][win] Fix CreateThread leak #126738
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
Conversation
@llvm/pr-subscribers-compiler-rt-sanitizer Author: None (GkvJwa) ChangesFix #126541 Since Full diff: https://github.com/llvm/llvm-project/pull/126738.diff 1 Files Affected:
diff --git a/compiler-rt/lib/asan/asan_win.cpp b/compiler-rt/lib/asan/asan_win.cpp
index 09a13b11cff1f53..d043bb822d2cf15 100644
--- a/compiler-rt/lib/asan/asan_win.cpp
+++ b/compiler-rt/lib/asan/asan_win.cpp
@@ -143,9 +143,11 @@ static thread_return_t THREAD_CALLING_CONV asan_thread_start(void *arg) {
ThreadStartParams params;
t->GetStartData(params);
+ // The ExitThread will end the current thread, causing destroy to be unable to
+ // be called.
+ t->Destroy();
auto res = (*params.start_routine)(params.arg);
- t->Destroy(); // POSIX calls this from TSD destructor.
return res;
}
|
compiler-rt/lib/asan/asan_win.cpp
Outdated
@@ -143,9 +143,11 @@ static thread_return_t THREAD_CALLING_CONV asan_thread_start(void *arg) { | |||
|
|||
ThreadStartParams params; | |||
t->GetStartData(params); | |||
// The ExitThread will end the current thread, causing destroy to be unable to | |||
// be called. | |||
t->Destroy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think t
is escaped above, so this isn't correct, it needs to happen after the thread runs.
If the user thread routine calls ExitThread manually rather than returning, then the proper fix for ASan is probably to intercept ExitThread and do the deallocation there.
Also, it sounds like small memory leaks are expected when calling ExitThread
, so maybe we shouldn't worry about this:
A thread in an executable that is linked to the static C run-time library (CRT) should use _beginthread and _endthread for thread management rather than CreateThread and ExitThread. Failure to do so results in small memory leaks when the thread calls ExitThread.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
t
is escaped above, so this isn't correct, it needs to happen after the thread runs.If the user thread routine calls ExitThread manually rather than returning, then the proper fix for ASan is probably to intercept ExitThread and do the deallocation there.
Also, it sounds like small memory leaks are expected when calling
ExitThread
, so maybe we shouldn't worry about this:A thread in an executable that is linked to the static C run-time library (CRT) should use _beginthread and _endthread for thread management rather than CreateThread and ExitThread. Failure to do so results in small memory leaks when the thread calls ExitThread.
Yes, But different from CreateThread in static libraries in handling tlsdata issues
The memory allocated through Virtualalloc
itself is not released.(56k bytes are leaked each time)
As a result, my unittest quickly omm
The following demo can be reproduced
#include <iostream>
#include <thread>
void test(int i)
{
std::cout << i << std::endl;
}
int main()
{
for (int i = 0; i < 10000; i++)
{
std::thread t(test, i);
t.detach();
}
char a;
std::cin >> a;
}
I'll try to hook ExitThread
so that t
can be released normally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's just a dynamic library, can modify asan_thread_exit
. Here intercept ExitThread
for general purpose and leak g_native_tls_key(TlsAlloc)
You can review it when you have time
04e9279
to
d1a37b8
Compare
22bcc77
to
2fcd13e
Compare
compiler-rt/lib/asan/asan_win.cpp
Outdated
@@ -166,6 +190,15 @@ INTERCEPTOR_WINAPI(HANDLE, CreateThread, LPSECURITY_ATTRIBUTES security, | |||
thr_flags, tid); | |||
} | |||
|
|||
INTERCEPTOR_WINAPI(void, ExitThread, DWORD dwExitCode) { | |||
DWORD key = atomic_load(&g_native_tls_key, memory_order_relaxed); | |||
AsanThread *t = (AsanThread *)TlsGetValue(key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this the same as in SetCurrentThread ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Different, SetCurrentThread calls AsanTSDSet to save the context
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course there is another problem with the log
CHECK(t->context());
VReport(2, "SetCurrentThread: %p for thread %p\n", (void *)t->context(),
(void *)GetThreadSelf());
// Make sure we do not reset the current AsanThread.
CHECK_EQ(0, AsanTSDGet());
---
// In contrast to POSIX, on Windows GetCurrentThreadId()
// returns a system-unique identifier.
tid_t GetTid() {
return GetCurrentThreadId();
}
uptr GetThreadSelf() {
return GetTid();
}
GetCurrentThreadId is returned a handle on Windows
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So why we can't just GetCurrentThread()->Destroy()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So why we can't just GetCurrentThread()->Destroy()?
Thanks, review it again when you have time
2fcd13e
to
d85fea7
Compare
compiler-rt/lib/asan/asan_win.cpp
Outdated
@@ -166,6 +165,14 @@ INTERCEPTOR_WINAPI(HANDLE, CreateThread, LPSECURITY_ATTRIBUTES security, | |||
thr_flags, tid); | |||
} | |||
|
|||
INTERCEPTOR_WINAPI(void, ExitThread, DWORD dwExitCode) { | |||
AsanThread *t = (AsanThread *)__asan::GetCurrentThread(); | |||
if (t) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this code don't use {} in cases like these
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, but It would be nice to get from @rnk who requested changes.
d85fea7
to
630c45e
Compare
Intercept `ExitThread` and free the memory created by `VirtualAlloc'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I think this approach should work.
Fix llvm#126541 Since ```t->Destroy``` cannot be called after ```start_routine```(When calling standard thread_start in crt) Intercept `ExitThread` and free the memory created by `VirtualAlloc'
Fix #126541
Since
t->Destroy
cannot be called afterstart_routine
(When calling standard thread_start in crt), the func is run in advance to avoid memory leaks and remain the same as before.