RFR: 8368124: Show useful thread names in ASAN reports [v2]

Thomas Stuefe stuefe at openjdk.org
Tue Sep 23 12:55:28 UTC 2025


On Sat, 20 Sep 2025 07:52:58 GMT, Thomas Stuefe <stuefe at openjdk.org> wrote:

>> On Linux, ASAN only shows some internal thread designation on reports, e.g. `T49`, which is useless.
>> 
>> This patch adds the ability to see the real internal JVM thread names or user-supplied Java thread names in ASAN. This makes it possible to correlate ASAN reports with hs-err file reports (if ASAN is run with `halt_on_error=0` to give us a chance to get an hs-err file) or with a thread dump done beforehand.
>> 
>> Note that it can only show up to 15 characters, though. Therefore, the patch tries to be smart about names that end in digits: such numbers are preserved such that the truncation happens in the middle of the name, e.g.:
>> 
>> `"MyAllocationWorkerThread#4411"` -> `"MyAllocat..4411"`
>> 
>> This latter improvement now also applies to thread names in gdb, since they are subject to the same limitation.
>> 
>> -----
>> 
>> Some examples from ASAN report: 
>> 
>> Before:
>> 
>> WRITE of size 8 at 0x7b749d2d9190 thread T49
>> 
>> 
>> Now:
>> 
>> WRITE of size 8 at 0x7bfc2f0d8380 thread T49 (MyThread#0)
>> 
>> 
>> 
>> ==593899==ERROR: AddressSanitizer: attempting free .. in thread T76 (MyAllocati..29)
>
> Thomas Stuefe has updated the pull request incrementally with one additional commit since the last revision:
> 
>   windows build error

@dholmes-ora I may have found the answer to why `pthread_setname_np` won't work.

We call `pthread_setname_np` assuming that it causes libpthread to call prctl(PR_SET_NAME,...), which would set the thread name in the kernel task structure. We further assume that name would percolate through to all interested parties, like gdb and ASAN.

But it reproducibly does not work for ASAN, tested on Fedora with glibc 2.42 and on Debian 12 stable. I also often see JVM ASAN reports from unknown Linux versions that never show a Thread name. 

### glibc

glibc seems to do everything correctly:

https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=nptl/pthread_setname.c;hb=42aba9189557280ad367c35908cbdfe26f5aeeb1

specifically, in the Linux version of `pthread_setname_np`,  it calls eventually:


int
__pthread_setname_np (pthread_t th, const char *name)
{
...
  if (pd == THREAD_SELF)
    return __prctl (PR_SET_NAME, name) ? errno : 0;
...


Okay, so if `pthread_setname_np` in glibc 2.42 seems to do everything right, why does ASAN not display the name?.


### ASAN (in GCC)

#### prctl

ASAN intercepts `prctl(SET_NAME)`: it does the real `prctl` first, then calls `COMMON_INTERCEPTOR_SET_THREAD_NAME`:

https://gcc.gnu.org/git/?p=gcc.git;a=blob_plain;f=libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc;hb=HEAD


INTERCEPTOR(int, prctl, int option, unsigned long arg2, unsigned long arg3,
            unsigned long arg4, unsigned long arg5) {
  ...
  int res = REAL(prctl)(option, arg2, arg3, arg4, arg5);
  if (option == PR_SET_NAME) {
    char buff[16];
    internal_strncpy(buff, (char *)arg2, 15);
    buff[15] = 0;
    COMMON_INTERCEPTOR_SET_THREAD_NAME(ctx, buff);
  } else if (res == 0 && option == PR_GET_NAME) {


`COMMON_INTERCEPTOR_SET_THREAD_NAME` is defined as `SetThreadName` in ASAN here:

https://gcc.gnu.org/git/?p=gcc.git;a=blob_plain;f=libsanitizer/asan/asan_interceptors.h;hb=HEAD


#define COMMON_INTERCEPTOR_SET_THREAD_NAME(ctx, name) SetThreadName(name)


Which will register the thread name with the central ASAN thread registry:


void SetThreadName(const char *name) {
  ...
    asanThreadRegistry().SetThreadName(t->tid(), name);
}


And now the thread name is known to ASAN.

#### pthread_setname_np

ASAN also intercepts `pthread_setname_np`. It calls the interceptor first, then goes on calling the real `pthread_setname_np`:


INTERCEPTOR(int, pthread_setname_np, uptr thread, const char *name) {
  void *ctx;
  COMMON_INTERCEPTOR_ENTER(ctx, pthread_setname_np, thread, name);
  COMMON_INTERCEPTOR_READ_STRING(ctx, name, 0);
  COMMON_INTERCEPTOR_SET_PTHREAD_NAME(ctx, thread, name);
  return REAL(pthread_setname_np)(thread, name);
}


`COMMON_INTERCEPTOR_SET_PTHREAD_NAME`, however, is a noop:


#define COMMON_INTERCEPTOR_SET_PTHREAD_NAME(ctx, thread, name) \
  do {                                                         \
  } while (false)




#### ASAN thread registry

But where does ASAN in gcc get the thread name from, then?

Looking at the thread registry code in ASAN, I expected to find a call to something like `prctl(PR_GET_NAME)` or `pthread_getname_np` for ASAN to query the underlying OS thread name. But I did not find anything. 

The internal thread structure (class `ThreadContextBase`) carries the user-defined thread name. When we call  `pthread_setname_np`, the real glibc version is called correctly, which does `prctl(PR_SET_NAME)`, correctly. However, the name in the ASAN thread registry does not obtain its string from `prctl(PR_GET_NAME)`, the proc file system, or anywhere else. It only gets the name from intercepting `prctl(PR_SET_NAME)`. 

And intercepting `prctl(PR_SET_NAME)` only works **for an ASAN-instrumented binary**. 

So, if we call `prctl(PR_SET_NAME)` from within the hotspot, the ASAN interception kicks in and we modify the ASAN thread registry name alongside the kernel task structure name. All good.

If we just call glibc `pthread_setname_np`, the glibc does call `prctl(PR_SET_NAME)` too, but ASAN does not intercept that call since glibc is not ASAN-instrumented. And it is also never read from the kernel task structure.

It seems that the only way to set the ASAN thread name is to call `prctl` directly from the hotspot.

---

As an interesting side note, the `ThreadSanitizer` does it differently: here the interceptor for `COMMON_INTERCEPTOR_SET_PTHREAD_NAME` is not empty but also hooks into the thread registry:

https://gcc.gnu.org/git/?p=gcc.git;a=blob_plain;f=libsanitizer/tsan/tsan_interceptors_posix.cpp;hb=HEAD


#define COMMON_INTERCEPTOR_SET_PTHREAD_NAME(ctx, thread, name)         \
  if (pthread_equal(pthread_self(), reinterpret_cast<void *>(thread))) \
    COMMON_INTERCEPTOR_SET_THREAD_NAME(ctx, name);                     \
  else                                                                 \
    __tsan::ctx->thread_registry.SetThreadNameByUserId(thread, name)


Might be worth fixing that in ASAN, but I'd rather have a libjvm version that works now and for all versions for ASAN, even if this gets fixed in the future.

-------------

PR Comment: https://git.openjdk.org/jdk/pull/27395#issuecomment-3323890898


More information about the hotspot-dev mailing list