Skip to content

[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

Merged
merged 1 commit into from
Feb 28, 2025
Merged

[asan][win] Fix CreateThread leak #126738

merged 1 commit into from
Feb 28, 2025

Conversation

GkvJwa
Copy link
Contributor

@GkvJwa GkvJwa commented Feb 11, 2025

Fix #126541

Since t->Destroy cannot be called after start_routine(When calling standard thread_start in crt), the func is run in advance to avoid memory leaks and remain the same as before.

@llvmbot
Copy link
Member

llvmbot commented Feb 11, 2025

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: None (GkvJwa)

Changes

Fix #126541

Since t->Destroy cannot be called after start_routine(When calling standard thread_start in crt), the func is run in advance to avoid memory leaks and remain the same as before.


Full diff: https://github.com/llvm/llvm-project/pull/126738.diff

1 Files Affected:

  • (modified) compiler-rt/lib/asan/asan_win.cpp (+3-1)
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;
 }
 

@GkvJwa GkvJwa requested a review from rnk February 13, 2025 15:40
@@ -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();
Copy link
Collaborator

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.

Copy link
Contributor Author

@GkvJwa GkvJwa Feb 14, 2025

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.

Copy link
Contributor Author

@GkvJwa GkvJwa Feb 16, 2025

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

@GkvJwa GkvJwa requested a review from rnk February 16, 2025 05:24
@GkvJwa GkvJwa force-pushed the fix_asan_leak branch 2 times, most recently from 22bcc77 to 2fcd13e Compare February 16, 2025 15:54
@@ -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);
Copy link
Collaborator

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 ?

Copy link
Contributor Author

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

Copy link
Contributor Author

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

Copy link
Collaborator

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()?

Copy link
Contributor Author

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

@@ -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) {
Copy link
Collaborator

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

Copy link
Collaborator

@vitalybuka vitalybuka left a 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.

Intercept `ExitThread` and free the memory created by `VirtualAlloc'
Copy link
Collaborator

@rnk rnk left a 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.

@GkvJwa GkvJwa merged commit 1594fa8 into llvm:main Feb 28, 2025
10 checks passed
@GkvJwa GkvJwa deleted the fix_asan_leak branch February 28, 2025 01:38
cheezeburglar pushed a commit to cheezeburglar/llvm-project that referenced this pull request Feb 28, 2025
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'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[asan][win] 'CreateThread' leaks on Windows
4 participants