[libc-commits] [libc] [libc] Implement pthread_getattr_np (PR #221231)

Pavel Labath via libc-commits libc-commits at lists.llvm.org
Fri Sep 4 07:19:47 PDT 2026


https://github.com/labath created https://github.com/llvm/llvm-project/pull/221231

Implement the GNU extension pthread_getattr_np.

For the stack address and size, the function returns the same value as pthread_getstack_np. Specifically, this means returning PTHREAD_STACK_DYNAMIC_NP for the main thread. Since we're not promising async-signal-safety of this function, we could parse /proc/self/maps (like glibc does), to get the actual stack VMA, but:
- that's a lot of code
- I'm not particularly fond of relying on /proc for libc features
- the consistency with pthread_getattr_np is nice

For these reasons, I'm not implementing this (or other alternatives like RLIMIT_STACK) right now. This incidentally means that the current implementation of this function is async-signal-safe, but that is likely to change once we add support for additional attributes.

For consistency with glibc, I report the guard size of newly created threads as zero, regardless of what was present in the thread creation attributes. I zero the attribute directly during thread creation as it is not used for managed stacks.

Assisted-by: Gemini

>From d8862215f400bdf1070685c2e7f5c2154285cd8e Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Fri, 4 Sep 2026 13:21:28 +0000
Subject: [PATCH] [libc] Implement pthread_getattr_np

Implement the GNU extension pthread_getattr_np.

For the stack address and size, the function returns the same value as
pthread_getstack_np. Specifically, this means returning
PTHREAD_STACK_DYNAMIC_NP for the main thread. Since we're not promising
async-signal-safety of this function, we could parse /proc/self/maps
(like glibc does), to get the actual stack VMA, but:
- that's a lot of code
- I'm not particularly fond of relying on /proc for libc features
- the consistency with pthread_getattr_np is nice

For these reasons, I'm not implementing this (or other alternatives
like RLIMIT_STACK) right now. This incidentally means that the
current implementation of this function is async-signal-safe, but
that is likely to change once we add support for additional
attributes.

For consistency with glibc, I report the guard size of newly created
threads as zero, regardless of what was present in the thread creation
attributes. I zero the attribute directly during thread creation as it
is not used for managed stacks.

Assisted-by: Gemini
---
 libc/config/linux/aarch64/entrypoints.txt     |   1 +
 libc/config/linux/riscv/entrypoints.txt       |   1 +
 libc/config/linux/x86_64/entrypoints.txt      |   1 +
 libc/hdr/types/CMakeLists.txt                 |   8 +
 libc/hdr/types/pthread_attr_t.h               |  27 ++
 libc/src/__support/threads/linux/thread.cpp   |   4 +
 libc/src/pthread/CMakeLists.txt               |  17 +
 libc/src/pthread/pthread_getattr_np.cpp       |  46 +++
 libc/src/pthread/pthread_getattr_np.h         |  14 +-
 .../integration/src/pthread/CMakeLists.txt    |  30 ++
 .../src/pthread/pthread_getattr_np_test.cpp   | 358 ++++++++++++++++++
 11 files changed, 503 insertions(+), 4 deletions(-)
 create mode 100644 libc/hdr/types/pthread_attr_t.h
 create mode 100644 libc/src/pthread/pthread_getattr_np.cpp
 create mode 100644 libc/test/integration/src/pthread/pthread_getattr_np_test.cpp

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 9340f49ed6bc7..be1d086416e81 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1128,6 +1128,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.pthread.pthread_detach
     libc.src.pthread.pthread_equal
     libc.src.pthread.pthread_exit
+    libc.src.pthread.pthread_getattr_np
     libc.src.pthread.pthread_getname_np
     libc.src.pthread.pthread_getschedparam
     libc.src.pthread.pthread_getspecific
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 82b125c561766..ccf7bd353fe54 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1316,6 +1316,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.pthread.pthread_detach
     libc.src.pthread.pthread_equal
     libc.src.pthread.pthread_exit
+    libc.src.pthread.pthread_getattr_np
     libc.src.pthread.pthread_getname_np
     libc.src.pthread.pthread_getschedparam
     libc.src.pthread.pthread_getspecific
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 85b8dffbd828d..2d970a57c7b95 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1325,6 +1325,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.pthread.pthread_detach
     libc.src.pthread.pthread_equal
     libc.src.pthread.pthread_exit
+    libc.src.pthread.pthread_getattr_np
     libc.src.pthread.pthread_getname_np
     libc.src.pthread.pthread_getschedparam
     libc.src.pthread.pthread_getspecific
diff --git a/libc/hdr/types/CMakeLists.txt b/libc/hdr/types/CMakeLists.txt
index 33be74c1420c5..b0e37012b294a 100644
--- a/libc/hdr/types/CMakeLists.txt
+++ b/libc/hdr/types/CMakeLists.txt
@@ -406,6 +406,14 @@ add_proxy_header_library(
     libc.include.llvm-libc-types.pthread_barrierattr_t
 )
 
+add_proxy_header_library(
+  pthread_attr_t
+  HDRS
+    pthread_attr_t.h
+  FULL_BUILD_DEPENDS
+    libc.include.llvm-libc-types.pthread_attr_t
+)
+
 add_proxy_header_library(
   pthread_t
   HDRS
diff --git a/libc/hdr/types/pthread_attr_t.h b/libc/hdr/types/pthread_attr_t.h
new file mode 100644
index 0000000000000..314692da3ab56
--- /dev/null
+++ b/libc/hdr/types/pthread_attr_t.h
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Proxy for pthread_attr_t.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_HDR_TYPES_PTHREAD_ATTR_T_H
+#define LLVM_LIBC_HDR_TYPES_PTHREAD_ATTR_T_H
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-types/pthread_attr_t.h"
+
+#else // Overlay mode
+
+#include <pthread.h>
+
+#endif // LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_TYPES_PTHREAD_ATTR_T_H
diff --git a/libc/src/__support/threads/linux/thread.cpp b/libc/src/__support/threads/linux/thread.cpp
index 231b7cf2903ca..20b3e5b88fa8d 100644
--- a/libc/src/__support/threads/linux/thread.cpp
+++ b/libc/src/__support/threads/linux/thread.cpp
@@ -223,6 +223,10 @@ int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
     else
       stack = alloc.value();
     owned_stack = true;
+  } else {
+    // The user is responsible for setting up the stack guard (or not) for the
+    // provided stack.
+    guardsize = 0;
   }
 
   // Validate that stack/stacksize are validly aligned.
diff --git a/libc/src/pthread/CMakeLists.txt b/libc/src/pthread/CMakeLists.txt
index 94af498779291..5007ba206d391 100644
--- a/libc/src/pthread/CMakeLists.txt
+++ b/libc/src/pthread/CMakeLists.txt
@@ -968,6 +968,23 @@ add_entrypoint_object(
     libc.src.__support.threads.thread
 )
 
+add_entrypoint_object(
+  pthread_getattr_np
+  SRCS
+    pthread_getattr_np.cpp
+  HDRS
+    pthread_getattr_np.h
+  DEPENDS
+    libc.hdr.pthread_macros
+    libc.hdr.types.pthread_attr_t
+    libc.hdr.types.pthread_t
+    libc.src.__support.CPP.atomic
+    libc.src.__support.common
+    libc.src.__support.macros.config
+    libc.src.__support.macros.null_check
+    libc.src.__support.threads.thread
+)
+
 add_entrypoint_object(
   pthread_getstack_np
   SRCS
diff --git a/libc/src/pthread/pthread_getattr_np.cpp b/libc/src/pthread/pthread_getattr_np.cpp
new file mode 100644
index 0000000000000..e63d468c16aac
--- /dev/null
+++ b/libc/src/pthread/pthread_getattr_np.cpp
@@ -0,0 +1,46 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of the pthread_getattr_np function (GNU extension).
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/pthread_getattr_np.h"
+#include "hdr/pthread_macros.h"
+#include "hdr/types/pthread_attr_t.h"
+#include "hdr/types/pthread_t.h"
+#include "src/__support/CPP/atomic.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/__support/threads/thread.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+static_assert(sizeof(pthread_t) == sizeof(LIBC_NAMESPACE::Thread),
+              "Mismatch between pthread_t and internal Thread.");
+
+LLVM_LIBC_FUNCTION(int, pthread_getattr_np,
+                   (pthread_t th, pthread_attr_t *attr)) {
+  LIBC_CRASH_ON_NULLPTR(attr);
+  auto *thread = reinterpret_cast<Thread *>(&th);
+
+  uint32_t detach_state =
+      thread->attrib->detach_state.load(cpp::MemoryOrder::RELAXED);
+  attr->__detachstate =
+      (detach_state == static_cast<uint32_t>(DetachState::DETACHED))
+          ? PTHREAD_CREATE_DETACHED
+          : PTHREAD_CREATE_JOINABLE;
+  attr->__stack = thread->attrib->stack;
+  attr->__stacksize = thread->attrib->stacksize;
+  attr->__guardsize = thread->attrib->guardsize;
+  return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pthread/pthread_getattr_np.h b/libc/src/pthread/pthread_getattr_np.h
index 7dada95681dcd..8daf223ddbef5 100644
--- a/libc/src/pthread/pthread_getattr_np.h
+++ b/libc/src/pthread/pthread_getattr_np.h
@@ -1,20 +1,26 @@
-//===-- Implementation header for pthread_getattr_np function -*- C++ -*-===//
+//===----------------------------------------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
-//===---------------------------------------------------------------------===//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header for pthread_getattr_np (GNU extension).
+///
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_SRC_PTHREAD_PTHREAD_GETATTR_NP_H
 #define LLVM_LIBC_SRC_PTHREAD_PTHREAD_GETATTR_NP_H
 
+#include "hdr/types/pthread_attr_t.h"
+#include "hdr/types/pthread_t.h"
 #include "src/__support/macros/config.h"
-#include <pthread.h>
 
 namespace LIBC_NAMESPACE_DECL {
 
-int pthread_getattr_np(pthread_t, pthread_attr_t *);
+int pthread_getattr_np(pthread_t th, pthread_attr_t *attr);
 
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/test/integration/src/pthread/CMakeLists.txt b/libc/test/integration/src/pthread/CMakeLists.txt
index 19d02bbc3f0c4..210f0e4500d84 100644
--- a/libc/test/integration/src/pthread/CMakeLists.txt
+++ b/libc/test/integration/src/pthread/CMakeLists.txt
@@ -292,6 +292,36 @@ add_integration_test(
     libc.src.__support.CPP.new
 )
 
+add_integration_test(
+  pthread_getattr_np_test
+  SUITE
+    libc-pthread-integration-tests
+  SRCS
+    pthread_getattr_np_test.cpp
+  DEPENDS
+    libc.hdr.pthread_macros
+    libc.hdr.stdint_proxy
+    libc.hdr.sys_mman_macros
+    libc.src.pthread.pthread_attr_destroy
+    libc.src.pthread.pthread_attr_getdetachstate
+    libc.src.pthread.pthread_attr_getguardsize
+    libc.src.pthread.pthread_attr_getstack
+    libc.src.pthread.pthread_attr_init
+    libc.src.pthread.pthread_attr_setdetachstate
+    libc.src.pthread.pthread_attr_setstack
+    libc.src.pthread.pthread_barrier_destroy
+    libc.src.pthread.pthread_barrier_init
+    libc.src.pthread.pthread_barrier_wait
+    libc.src.pthread.pthread_create
+    libc.src.pthread.pthread_detach
+    libc.src.pthread.pthread_getattr_np
+    libc.src.pthread.pthread_join
+    libc.src.pthread.pthread_self
+    libc.src.sys.mman.mmap
+    libc.src.sys.mman.munmap
+    libc.src.unistd.sysconf
+)
+
 add_integration_test(
   pthread_getstack_np_test
   SUITE
diff --git a/libc/test/integration/src/pthread/pthread_getattr_np_test.cpp b/libc/test/integration/src/pthread/pthread_getattr_np_test.cpp
new file mode 100644
index 0000000000000..b02a784e688d1
--- /dev/null
+++ b/libc/test/integration/src/pthread/pthread_getattr_np_test.cpp
@@ -0,0 +1,358 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Integration tests for pthread_getattr_np.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/pthread_macros.h"
+#include "hdr/stdint_proxy.h"
+#include "hdr/sys_mman_macros.h"
+#include "src/pthread/pthread_attr_destroy.h"
+#include "src/pthread/pthread_attr_getdetachstate.h"
+#include "src/pthread/pthread_attr_getguardsize.h"
+#include "src/pthread/pthread_attr_getstack.h"
+#include "src/pthread/pthread_attr_init.h"
+#include "src/pthread/pthread_attr_setdetachstate.h"
+#include "src/pthread/pthread_attr_setstack.h"
+#include "src/pthread/pthread_barrier_destroy.h"
+#include "src/pthread/pthread_barrier_init.h"
+#include "src/pthread/pthread_barrier_wait.h"
+#include "src/pthread/pthread_create.h"
+#include "src/pthread/pthread_detach.h"
+#include "src/pthread/pthread_getattr_np.h"
+#include "src/pthread/pthread_join.h"
+#include "src/pthread/pthread_self.h"
+#include "src/sys/mman/mmap.h"
+#include "src/sys/mman/munmap.h"
+#include "src/unistd/sysconf.h"
+#include "test/IntegrationTest/test.h"
+
+static void check_readable(const void *start, size_t size) {
+  size_t pagesize = LIBC_NAMESPACE::sysconf(_SC_PAGESIZE);
+  auto *bytes = static_cast<const volatile char *>(start);
+  for (size_t offset = 0; offset < size; offset += pagesize)
+    (void)bytes[offset];
+  if (size > 0)
+    (void)bytes[size - 1];
+}
+
+static void wait_barrier(pthread_barrier_t &barrier) {
+  int res = LIBC_NAMESPACE::pthread_barrier_wait(&barrier);
+  ASSERT_TRUE(res == 0 || res == PTHREAD_BARRIER_SERIAL_THREAD);
+}
+
+// Test 1: Main thread attributes
+// Verifies that pthread_getattr_np on the main thread reports a detached state,
+// a dynamic stack size (PTHREAD_STACK_DYNAMIC_NP), and a zero guard size.
+static void test_main_thread() {
+  pthread_attr_t attr;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_getattr_np(LIBC_NAMESPACE::pthread_self(), &attr),
+      0);
+
+  int detachstate = -1;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_getdetachstate(&attr, &detachstate),
+            0);
+  ASSERT_EQ(detachstate, static_cast<int>(PTHREAD_CREATE_DETACHED));
+
+  void *stackaddr = nullptr;
+  size_t stacksize = 1234;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_attr_getstack(&attr, &stackaddr, &stacksize), 0);
+  ASSERT_NE(stackaddr, static_cast<void *>(nullptr));
+  ASSERT_EQ(stacksize, static_cast<size_t>(PTHREAD_STACK_DYNAMIC_NP));
+  ASSERT_EQ(reinterpret_cast<uintptr_t>(stackaddr) %
+                LIBC_NAMESPACE::sysconf(_SC_PAGESIZE),
+            static_cast<uintptr_t>(0));
+
+  uintptr_t local_var_addr = reinterpret_cast<uintptr_t>(&attr);
+  ASSERT_TRUE(local_var_addr < reinterpret_cast<uintptr_t>(stackaddr));
+  check_readable(&attr,
+                 reinterpret_cast<uintptr_t>(stackaddr) - local_var_addr);
+
+  size_t guardsize = 1234;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_getguardsize(&attr, &guardsize), 0);
+  ASSERT_EQ(guardsize, static_cast<size_t>(0));
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_destroy(&attr), 0);
+}
+
+struct ChildDefaultArgs {
+  pthread_barrier_t ready_barrier;
+  pthread_barrier_t done_barrier;
+  void *stackaddr{nullptr};
+  size_t stacksize{0};
+  size_t guardsize{0};
+  int detachstate{-1};
+};
+
+static void *child_default_func(void *arg) {
+  auto *args = static_cast<ChildDefaultArgs *>(arg);
+
+  pthread_attr_t attr;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_getattr_np(LIBC_NAMESPACE::pthread_self(), &attr),
+      0);
+
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_attr_getdetachstate(&attr, &args->detachstate),
+      0);
+  ASSERT_EQ(args->detachstate, static_cast<int>(PTHREAD_CREATE_JOINABLE));
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_getstack(&attr, &args->stackaddr,
+                                                  &args->stacksize),
+            0);
+  ASSERT_NE(args->stackaddr, static_cast<void *>(nullptr));
+  ASSERT_NE(args->stacksize, static_cast<size_t>(PTHREAD_STACK_DYNAMIC_NP));
+  ASSERT_TRUE(args->stacksize > 0);
+
+  uintptr_t local_var_addr = reinterpret_cast<uintptr_t>(&attr);
+  uintptr_t stack_low = reinterpret_cast<uintptr_t>(args->stackaddr);
+  uintptr_t stack_high = stack_low + args->stacksize;
+  ASSERT_TRUE(local_var_addr >= stack_low);
+  ASSERT_TRUE(local_var_addr < stack_high);
+
+  check_readable(args->stackaddr, args->stacksize);
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_getguardsize(&attr, &args->guardsize),
+            0);
+  ASSERT_EQ(args->guardsize,
+            static_cast<size_t>(LIBC_NAMESPACE::sysconf(_SC_PAGESIZE)));
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_destroy(&attr), 0);
+
+  wait_barrier(args->ready_barrier);
+  wait_barrier(args->done_barrier);
+
+  return nullptr;
+}
+
+// Test 2: Child thread with default attributes
+// Verifies that a joinable thread created with default attributes reports
+// joinable state, an implementation-allocated stack, and a page-sized guard,
+// both when queried by the thread itself and by the parent thread.
+static void test_child_thread_default() {
+  ChildDefaultArgs args;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_barrier_init(&args.ready_barrier, nullptr, 2), 0);
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_barrier_init(&args.done_barrier, nullptr, 2), 0);
+
+  pthread_t th;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_create(&th, nullptr, child_default_func, &args),
+      0);
+
+  wait_barrier(args.ready_barrier);
+
+  // Query from the parent thread while child is still running.
+  pthread_attr_t attr;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_getattr_np(th, &attr), 0);
+
+  int detachstate = -1;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_getdetachstate(&attr, &detachstate),
+            0);
+  ASSERT_EQ(detachstate, static_cast<int>(PTHREAD_CREATE_JOINABLE));
+
+  void *stackaddr = nullptr;
+  size_t stacksize = 0;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_attr_getstack(&attr, &stackaddr, &stacksize), 0);
+  ASSERT_EQ(stackaddr, args.stackaddr);
+  ASSERT_EQ(stacksize, args.stacksize);
+
+  size_t guardsize = 0;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_getguardsize(&attr, &guardsize), 0);
+  ASSERT_EQ(guardsize, args.guardsize);
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_destroy(&attr), 0);
+
+  wait_barrier(args.done_barrier);
+
+  void *retval = nullptr;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_barrier_destroy(&args.ready_barrier), 0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_barrier_destroy(&args.done_barrier), 0);
+}
+
+struct ChildCustomArgs {
+  void *stackaddr{nullptr};
+  size_t stacksize{0};
+  size_t guardsize{0};
+  int detachstate{-1};
+};
+
+static void *child_custom_func(void *arg) {
+  auto *args = static_cast<ChildCustomArgs *>(arg);
+
+  pthread_attr_t attr;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_getattr_np(LIBC_NAMESPACE::pthread_self(), &attr),
+      0);
+
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_attr_getdetachstate(&attr, &args->detachstate),
+      0);
+  ASSERT_EQ(args->detachstate, static_cast<int>(PTHREAD_CREATE_JOINABLE));
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_getstack(&attr, &args->stackaddr,
+                                                  &args->stacksize),
+            0);
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_getguardsize(&attr, &args->guardsize),
+            0);
+  ASSERT_EQ(args->guardsize, static_cast<size_t>(0));
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_destroy(&attr), 0);
+
+  return nullptr;
+}
+
+// Test 3: Child thread with custom stack
+// Verifies that a thread created with a user-allocated stack reports the exact
+// stack address and size, and reports a guard size of 0.
+static void test_child_thread_custom_stack() {
+  size_t custom_stacksize = PTHREAD_STACK_MIN * 2;
+  void *custom_stack =
+      LIBC_NAMESPACE::mmap(nullptr, custom_stacksize, PROT_READ | PROT_WRITE,
+                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  ASSERT_NE(custom_stack, MAP_FAILED);
+  ASSERT_NE(custom_stack, static_cast<void *>(nullptr));
+
+  pthread_attr_t attr;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_init(&attr), 0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_setstack(&attr, custom_stack,
+                                                  custom_stacksize),
+            0);
+
+  ChildCustomArgs args;
+  pthread_t th;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_create(&th, &attr, child_custom_func, &args), 0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_destroy(&attr), 0);
+
+  void *retval = nullptr;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
+
+  ASSERT_EQ(args.stackaddr, custom_stack);
+  ASSERT_EQ(args.stacksize, custom_stacksize);
+  ASSERT_EQ(args.guardsize, static_cast<size_t>(0));
+  ASSERT_EQ(args.detachstate, static_cast<int>(PTHREAD_CREATE_JOINABLE));
+
+  ASSERT_EQ(LIBC_NAMESPACE::munmap(custom_stack, custom_stacksize), 0);
+}
+
+static void *child_detached_func(void *arg) {
+  auto &barrier = *static_cast<pthread_barrier_t *>(arg);
+
+  pthread_attr_t attr;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_getattr_np(LIBC_NAMESPACE::pthread_self(), &attr),
+      0);
+
+  int detachstate = -1;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_getdetachstate(&attr, &detachstate),
+            0);
+  ASSERT_EQ(detachstate, static_cast<int>(PTHREAD_CREATE_DETACHED));
+
+  size_t guardsize = 0;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_getguardsize(&attr, &guardsize), 0);
+  ASSERT_EQ(guardsize,
+            static_cast<size_t>(LIBC_NAMESPACE::sysconf(_SC_PAGESIZE)));
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_destroy(&attr), 0);
+
+  wait_barrier(barrier);
+  return nullptr;
+}
+
+// Test 4: Child thread created detached
+// Verifies that a thread created with PTHREAD_CREATE_DETACHED reports a
+// detached state.
+static void test_child_thread_detached() {
+  pthread_barrier_t barrier;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_barrier_init(&barrier, nullptr, 2), 0);
+
+  pthread_attr_t attr;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_init(&attr), 0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_setdetachstate(
+                &attr, PTHREAD_CREATE_DETACHED),
+            0);
+
+  pthread_t th;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_create(&th, &attr, child_detached_func, &barrier),
+      0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_destroy(&attr), 0);
+
+  wait_barrier(barrier);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_barrier_destroy(&barrier), 0);
+}
+
+struct DynamicDetachArgs {
+  pthread_barrier_t ready_barrier;
+  pthread_barrier_t done_barrier;
+};
+
+static void *child_dynamic_detach_func(void *arg) {
+  auto *args = static_cast<DynamicDetachArgs *>(arg);
+  wait_barrier(args->ready_barrier);
+  wait_barrier(args->done_barrier);
+  return nullptr;
+}
+
+// Test 5: Dynamically detached child thread
+// Verifies that detaching a running joinable thread transitions its reported
+// detach state from PTHREAD_CREATE_JOINABLE to PTHREAD_CREATE_DETACHED.
+static void test_child_thread_dynamic_detach() {
+  DynamicDetachArgs args;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_barrier_init(&args.ready_barrier, nullptr, 2), 0);
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_barrier_init(&args.done_barrier, nullptr, 2), 0);
+
+  pthread_t th;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr,
+                                           child_dynamic_detach_func, &args),
+            0);
+
+  wait_barrier(args.ready_barrier);
+
+  pthread_attr_t attr;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_getattr_np(th, &attr), 0);
+  int detachstate = -1;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_getdetachstate(&attr, &detachstate),
+            0);
+  ASSERT_EQ(detachstate, static_cast<int>(PTHREAD_CREATE_JOINABLE));
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_destroy(&attr), 0);
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_detach(th), 0);
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_getattr_np(th, &attr), 0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_getdetachstate(&attr, &detachstate),
+            0);
+  ASSERT_EQ(detachstate, static_cast<int>(PTHREAD_CREATE_DETACHED));
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_destroy(&attr), 0);
+
+  wait_barrier(args.done_barrier);
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_barrier_destroy(&args.ready_barrier), 0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_barrier_destroy(&args.done_barrier), 0);
+}
+
+TEST_MAIN() {
+  test_main_thread();
+  test_child_thread_default();
+  test_child_thread_custom_stack();
+  test_child_thread_detached();
+  test_child_thread_dynamic_detach();
+  return 0;
+}



More information about the libc-commits mailing list