Skip to content

libunwind: data race in logAPIs when exception is thrown #167732

@briangreenery

Description

@briangreenery

When libunwind is built with -fsanitize=thread there is a data race reported on these static variables in logAPIs:

// Add logging hooks in Debug builds only
#ifndef NDEBUG
#include <stdlib.h>
_LIBUNWIND_HIDDEN
bool logAPIs() {
// do manual lock to avoid use of _cxa_guard_acquire or initializers
static bool checked = false;
static bool log = false;
if (!checked) {
log = (getenv("LIBUNWIND_PRINT_APIS") != NULL);
checked = true;

Additionally, the comment says "Add logging hooks in Debug builds only" however libunwind always builds without NDEBUG defined. This is because LIBUNWIND_ENABLE_ASSERTIONS defaults to ON.

option(LIBUNWIND_ENABLE_ASSERTIONS "Enable assertions independent of build mode." ON)

And the cmake build uses LIBUNWIND_ENABLE_ASSERTIONS to turn off NDEBUG:

if (LIBUNWIND_ENABLE_ASSERTIONS)
# MSVC doesn't like _DEBUG on release builds. See PR 4379.
if (NOT MSVC)
add_compile_flags(-D_DEBUG)
endif()
# On Release builds cmake automatically defines NDEBUG, so we
# explicitly undefine it:
if (NOT uppercase_CMAKE_BUILD_TYPE STREQUAL "DEBUG")
add_compile_flags(-UNDEBUG)
endif()

Here's the report from tsan:

WARNING: ThreadSanitizer: data race (pid=8520)
  Read of size 1 at 0x55e25a3ef811 by thread T2:
    #0 logAPIs /install/llvm-project-21.1.5.src/libunwind/src/libunwind.cpp:443:8 (a.out+0x18937a)
    #1 _Unwind_RaiseException /install/llvm-project-21.1.5.src/libunwind/src/UnwindLevel1.c:450:3 (a.out+0x187d94)
    #2 __cxa_throw /install/llvm-project-21.1.5.src/libcxxabi/src/cxa_exception.cpp:295:5 (a.out+0x185879)
    #3 ThrowException(void*) test.cpp (a.out+0x143ab5)

  Previous write of size 1 at 0x55e25a3ef811 by thread T1:
    #0 logAPIs /install/llvm-project-21.1.5.src/libunwind/src/libunwind.cpp:445:13 (a.out+0x1893cc)
    #1 _Unwind_RaiseException /install/llvm-project-21.1.5.src/libunwind/src/UnwindLevel1.c:450:3 (a.out+0x187d94)
    #2 __cxa_throw /install/llvm-project-21.1.5.src/libcxxabi/src/cxa_exception.cpp:295:5 (a.out+0x185879)
    #3 ThrowException(void*) test.cpp (a.out+0x143ab5)

  Location is global 'logAPIs::checked' of size 1 at 0x55e25a3ef811 (a.out+0x150a811)

  Thread T2 (tid=8523, running) created by main thread at:
    #0 pthread_create /install/llvm-project-21.1.5.src/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:1090:3 (a.out+0x9341a)
    #1 main <null> (a.out+0x14394b)

  Thread T1 (tid=8522, running) created by main thread at:
    #0 pthread_create /install/llvm-project-21.1.5.src/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:1090:3 (a.out+0x9341a)
    #1 main <null> (a.out+0x14394b)

SUMMARY: ThreadSanitizer: data race /install/llvm-project-21.1.5.src/libunwind/src/libunwind.cpp:443:8 in logAPIs

And here's a test program that can reproduce it (though tsan does not always flag it). I had to also set the env var LIBUNWIND_PRINT_UNWINDING=1 to get this test program to reliably reproduce (though I do not have to set that environment variable in the actual build where I first saw this problem):

#include <pthread.h>
#include <stdexcept>
#include <vector>

static void *ThrowException(void *) {
  try {
    throw std::runtime_error("nope");
  } catch (...) {
  }
  return nullptr;
}

int main() {
  std::vector<pthread_t> threads;
  for (int i = 0; i < 2; i++) {
    pthread_t thread;
    pthread_create(&thread, nullptr, ThrowException, nullptr);
    threads.push_back(thread);
  }

  for (auto thread : threads) {
    pthread_join(thread, nullptr);
  }
  return 0;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions