[libc-commits] [libc] [libc] Implement getcontext and setcontext for x86_64 (PR #192343)

via libc-commits libc-commits at lists.llvm.org
Wed Apr 15 13:50:44 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libc

Author: Jeff Bailey (kaladron)

<details>
<summary>Changes</summary>

Implemented getcontext and setcontext for x86_64 architecture in LLVM libc. These functions use inline assembly with naked attributes to capture and restore the exact register state.

Added:
* src/ucontext/getcontext.h and setcontext.h
* src/ucontext/x86_64/getcontext.cpp and setcontext.cpp
* Hermetic integration test for register preservation.
* Unit tests for basic functionality and signal mask preservation.

Updated entrypoints for x86_64 Linux.

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


15 Files Affected:

- (modified) libc/config/linux/x86_64/entrypoints.txt (+4) 
- (modified) libc/include/llvm-libc-types/x86_64/ucontext_t.h (+3-3) 
- (modified) libc/src/CMakeLists.txt (+1) 
- (added) libc/src/ucontext/CMakeLists.txt (+18) 
- (added) libc/src/ucontext/getcontext.h (+21) 
- (added) libc/src/ucontext/setcontext.h (+21) 
- (added) libc/src/ucontext/x86_64/CMakeLists.txt (+34) 
- (added) libc/src/ucontext/x86_64/getcontext.cpp (+97) 
- (added) libc/src/ucontext/x86_64/setcontext.cpp (+86) 
- (modified) libc/test/integration/src/CMakeLists.txt (+2-1) 
- (added) libc/test/integration/src/ucontext/CMakeLists.txt (+12) 
- (added) libc/test/integration/src/ucontext/ucontext_test.cpp (+40) 
- (modified) libc/test/src/CMakeLists.txt (+1) 
- (added) libc/test/src/ucontext/CMakeLists.txt (+17) 
- (added) libc/test/src/ucontext/ucontext_test.cpp (+78) 


``````````diff
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 9476ebbad1517..259144816d4eb 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1251,6 +1251,10 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.setjmp.siglongjmp
     libc.src.setjmp.sigsetjmp
 
+    # ucontext.h entrypoints
+    libc.src.ucontext.getcontext
+    libc.src.ucontext.setcontext
+
     # stdio.h entrypoints
     libc.src.stdio.clearerr
     libc.src.stdio.clearerr_unlocked
diff --git a/libc/include/llvm-libc-types/x86_64/ucontext_t.h b/libc/include/llvm-libc-types/x86_64/ucontext_t.h
index 607c2d1a6b2a0..15d755050076f 100644
--- a/libc/include/llvm-libc-types/x86_64/ucontext_t.h
+++ b/libc/include/llvm-libc-types/x86_64/ucontext_t.h
@@ -8,8 +8,8 @@
 
 // Note: Definitions in this file are based on the Linux kernel ABI.
 
-#ifndef LLVM_LIBC_TYPES_UCONTEXT_T_H
-#define LLVM_LIBC_TYPES_UCONTEXT_T_H
+#ifndef LLVM_LIBC_TYPES_X86_64_UCONTEXT_T_H
+#define LLVM_LIBC_TYPES_X86_64_UCONTEXT_T_H
 
 #include "../sigset_t.h"
 #include "../stack_t.h"
@@ -40,4 +40,4 @@ typedef struct alignas(16) ucontext_t {
   unsigned long long __ssp[4];
 } ucontext_t;
 
-#endif // LLVM_LIBC_TYPES_UCONTEXT_T_H
+#endif // LLVM_LIBC_TYPES_X86_64_UCONTEXT_T_H
diff --git a/libc/src/CMakeLists.txt b/libc/src/CMakeLists.txt
index e4b0de3d5c75d..4ca42ddc4f870 100644
--- a/libc/src/CMakeLists.txt
+++ b/libc/src/CMakeLists.txt
@@ -48,3 +48,4 @@ add_subdirectory(setjmp)
 add_subdirectory(signal)
 add_subdirectory(spawn)
 add_subdirectory(threads)
+add_subdirectory(ucontext)
diff --git a/libc/src/ucontext/CMakeLists.txt b/libc/src/ucontext/CMakeLists.txt
new file mode 100644
index 0000000000000..d8316abf508a3
--- /dev/null
+++ b/libc/src/ucontext/CMakeLists.txt
@@ -0,0 +1,18 @@
+if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_ARCHITECTURE})
+  add_subdirectory(${LIBC_TARGET_ARCHITECTURE})
+endif()
+
+add_entrypoint_object(
+  getcontext
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_ARCHITECTURE}.getcontext
+)
+
+add_entrypoint_object(
+  setcontext
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_ARCHITECTURE}.setcontext
+)
+
diff --git a/libc/src/ucontext/getcontext.h b/libc/src/ucontext/getcontext.h
new file mode 100644
index 0000000000000..337e360f77d31
--- /dev/null
+++ b/libc/src/ucontext/getcontext.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for getcontext --------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_UCONTEXT_GETCONTEXT_H
+#define LLVM_LIBC_SRC_UCONTEXT_GETCONTEXT_H
+
+#include "include/llvm-libc-types/ucontext_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int getcontext(ucontext_t *ucp);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_UCONTEXT_GETCONTEXT_H
diff --git a/libc/src/ucontext/setcontext.h b/libc/src/ucontext/setcontext.h
new file mode 100644
index 0000000000000..fc4cc5f74ee8f
--- /dev/null
+++ b/libc/src/ucontext/setcontext.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for setcontext --------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_UCONTEXT_SETCONTEXT_H
+#define LLVM_LIBC_SRC_UCONTEXT_SETCONTEXT_H
+
+#include "include/llvm-libc-types/ucontext_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int setcontext(const ucontext_t *ucp);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_UCONTEXT_SETCONTEXT_H
diff --git a/libc/src/ucontext/x86_64/CMakeLists.txt b/libc/src/ucontext/x86_64/CMakeLists.txt
new file mode 100644
index 0000000000000..2a453f0ddda32
--- /dev/null
+++ b/libc/src/ucontext/x86_64/CMakeLists.txt
@@ -0,0 +1,34 @@
+add_entrypoint_object(
+  getcontext
+  SRCS
+    getcontext.cpp
+  HDRS
+    ../getcontext.h
+  COMPILE_OPTIONS
+    -O3
+    -fno-omit-frame-pointer
+  DEPENDS
+    libc.include.llvm-libc-types.ucontext_t
+    libc.include.sys_syscall
+    libc.src.__support.common
+    libc.src.__support.macros.config
+    libc.hdr.types.size_t
+)
+
+add_entrypoint_object(
+  setcontext
+  SRCS
+    setcontext.cpp
+  HDRS
+    ../setcontext.h
+  COMPILE_OPTIONS
+    -O3
+    -fno-omit-frame-pointer
+  DEPENDS
+    libc.include.llvm-libc-types.ucontext_t
+    libc.include.sys_syscall
+    libc.src.__support.common
+    libc.src.__support.macros.config
+    libc.hdr.types.size_t
+)
+
diff --git a/libc/src/ucontext/x86_64/getcontext.cpp b/libc/src/ucontext/x86_64/getcontext.cpp
new file mode 100644
index 0000000000000..87a6fbc982b1d
--- /dev/null
+++ b/libc/src/ucontext/x86_64/getcontext.cpp
@@ -0,0 +1,97 @@
+//===-- Implementation of getcontext for x86_64 ---------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/ucontext/getcontext.h"
+#include "include/llvm-libc-types/ucontext_t.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+
+#include "hdr/types/size_t.h"
+#include <sys/syscall.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+// We use naked because we need to capture the exact register state
+// at the moment of the function call, avoiding any compiler prologue/epilogue.
+__attribute__((naked)) LLVM_LIBC_FUNCTION(int, getcontext, (ucontext_t * ucp)) {
+  asm(R"(
+      # ucp is in rdi
+      
+      # Save general purpose registers
+      mov %%r8, %c[r8](%%rdi)
+      mov %%r9, %c[r9](%%rdi)
+      mov %%r10, %c[r10](%%rdi)
+      mov %%r11, %c[r11](%%rdi)
+      mov %%r12, %c[r12](%%rdi)
+      mov %%r13, %c[r13](%%rdi)
+      mov %%r14, %c[r14](%%rdi)
+      mov %%r15, %c[r15](%%rdi)
+      mov %%rdi, %c[rdi](%%rdi)
+      mov %%rsi, %c[rsi](%%rdi)
+      mov %%rbp, %c[rbp](%%rdi)
+      mov %%rbx, %c[rbx](%%rdi)
+      mov %%rdx, %c[rdx](%%rdi)
+      # getcontext should return 0 when resumed by setcontext.
+      # So we save 0 into the RAX register of the context.
+      movq $0, %c[rax](%%rdi)
+      mov %%rcx, %c[rcx](%%rdi)
+
+      # The stack pointer before the call is rsp + sizeof(void*).
+      # The return address was pushed when this function was called.
+      # Save instruction pointer and stack pointer
+      mov (%%rsp), %%rax
+      mov %%rax, %c[rip](%%rdi)
+      lea %c[ret_size](%%rsp), %%rax
+      mov %%rax, %c[rsp](%%rdi)
+
+      # Save floating point state
+      fxsaveq %c[fpregs_mem](%%rdi)
+      # Point mcontext.fpregs to our internal FP storage
+      lea %c[fpregs_mem](%%rdi), %%rax
+      mov %%rax, %c[fpregs_ptr](%%rdi)
+
+      # Capture the signal mask using rt_sigprocmask syscall.
+      # rt_sigprocmask(SIG_BLOCK, NULL, &ucp->uc_sigmask, sizeof(sigset_t))
+      leaq %c[sigmask](%%rdi), %%rdx # oldset = &ucp->uc_sigmask
+      xorq %%rsi, %%rsi # set = NULL
+      movq $0, %%rdi # SIG_BLOCK (captured mask in oldset)
+      movq $%c[sigset_size], %%r10
+      movq $%c[syscall_num], %%rax
+      syscall
+
+      # getcontext should return 0 on success
+      xor %%eax, %%eax
+
+      retq
+      )" ::[ret_size] "i"(sizeof(void *)),
+      [sigset_size] "i"(sizeof(sigset_t)),
+      [syscall_num] "i"(SYS_rt_sigprocmask),
+      [r8] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R8])),
+      [r9] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R9])),
+      [r10] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R10])),
+      [r11] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R11])),
+      [r12] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R12])),
+      [r13] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R13])),
+      [r14] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R14])),
+      [r15] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R15])),
+      [rdi] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RDI])),
+      [rsi] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RSI])),
+      [rbp] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RBP])),
+      [rbx] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RBX])),
+      [rdx] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RDX])),
+      [rax] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RAX])),
+      [rcx] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RCX])),
+      [rsp] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RSP])),
+      [rip] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RIP])),
+      [fpregs_mem] "i"(__builtin_offsetof(ucontext_t, __fpregs_mem)),
+      [fpregs_ptr] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.fpregs)),
+      [sigmask] "i"(__builtin_offsetof(ucontext_t, uc_sigmask))
+      : "memory", "rcx", "r11");
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/ucontext/x86_64/setcontext.cpp b/libc/src/ucontext/x86_64/setcontext.cpp
new file mode 100644
index 0000000000000..6db6bf28bbae0
--- /dev/null
+++ b/libc/src/ucontext/x86_64/setcontext.cpp
@@ -0,0 +1,86 @@
+//===-- Implementation of setcontext for x86_64 ---------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/ucontext/setcontext.h"
+#include "include/llvm-libc-types/ucontext_t.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+
+#include "hdr/types/size_t.h"
+#include <sys/syscall.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+__attribute__((naked)) LLVM_LIBC_FUNCTION(int, setcontext,
+                                          (const ucontext_t *ucp)) {
+  asm(R"(
+      # ucp is in rdi
+      
+      # Restore the signal mask using rt_sigprocmask syscall.
+      # rt_sigprocmask(SIG_SETMASK, &ucp->uc_sigmask, NULL, sizeof(sigset_t))
+      pushq %%rdi # Save ucp
+      leaq %c[sigmask](%%rdi), %%rsi # set = &ucp->uc_sigmask
+      xorq %%rdx, %%rdx # oldset = NULL
+      movq $%c[sigset_size], %%r10 # sigsetsize = sizeof(sigset_t)
+      movq $2, %%rdi # how = SIG_SETMASK
+      movq $%c[syscall_num], %%rax
+      syscall
+      popq %%rdi # Restore ucp
+
+      # Restore floating point state
+      fxrstorq %c[fpregs_mem](%%rdi)
+
+      # Restore other general purpose registers
+      mov %c[r8](%%rdi), %%r8
+      mov %c[r9](%%rdi), %%r9
+      mov %c[r10](%%rdi), %%r10
+      mov %c[r11](%%rdi), %%r11
+      mov %c[r12](%%rdi), %%r12
+      mov %c[r13](%%rdi), %%r13
+      mov %c[r14](%%rdi), %%r14
+      mov %c[r15](%%rdi), %%r15
+      mov %c[rbp](%%rdi), %%rbp
+      mov %c[rbx](%%rdi), %%rbx
+      mov %c[rdx](%%rdi), %%rdx
+      mov %c[rax](%%rdi), %%rax
+      mov %c[rcx](%%rdi), %%rcx
+
+      # Restore stack pointer and instruction pointer
+      mov %c[rsp](%%rdi), %%rsp
+      mov %c[rip](%%rdi), %%r11 # Use r11 as temp for rip
+      
+      # Restore RSI and RDI last
+      mov %c[rsi](%%rdi), %%rsi
+      mov %c[rdi](%%rdi), %%rdi
+
+      jmpq *%%r11 # Jump to the saved instruction pointer
+      )" ::[sigset_size] "i"(sizeof(sigset_t)),
+      [syscall_num] "i"(SYS_rt_sigprocmask),
+      [r8] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R8])),
+      [r9] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R9])),
+      [r10] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R10])),
+      [r11] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R11])),
+      [r12] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R12])),
+      [r13] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R13])),
+      [r14] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R14])),
+      [r15] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_R15])),
+      [rdi] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RDI])),
+      [rsi] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RSI])),
+      [rbp] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RBP])),
+      [rbx] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RBX])),
+      [rdx] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RDX])),
+      [rax] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RAX])),
+      [rcx] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RCX])),
+      [rsp] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RSP])),
+      [rip] "i"(__builtin_offsetof(ucontext_t, uc_mcontext.gregs[REG_RIP])),
+      [fpregs_mem] "i"(__builtin_offsetof(ucontext_t, __fpregs_mem)),
+      [sigmask] "i"(__builtin_offsetof(ucontext_t, uc_sigmask))
+      : "memory", "rcx", "r11");
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/integration/src/CMakeLists.txt b/libc/test/integration/src/CMakeLists.txt
index 47c0716dc22b4..c88353e2fd37e 100644
--- a/libc/test/integration/src/CMakeLists.txt
+++ b/libc/test/integration/src/CMakeLists.txt
@@ -3,6 +3,7 @@ add_subdirectory(pthread)
 add_subdirectory(spawn)
 add_subdirectory(stdio)
 add_subdirectory(stdlib)
-add_subdirectory(threads)
 add_subdirectory(sys)
+add_subdirectory(threads)
+add_subdirectory(ucontext)
 add_subdirectory(unistd)
diff --git a/libc/test/integration/src/ucontext/CMakeLists.txt b/libc/test/integration/src/ucontext/CMakeLists.txt
new file mode 100644
index 0000000000000..9e4d432ac9d6a
--- /dev/null
+++ b/libc/test/integration/src/ucontext/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_libc_integration_test_suite(libc-ucontext-integration-tests)
+
+add_integration_test(
+  ucontext_test
+  SUITE
+    libc-ucontext-integration-tests
+  SRCS
+    ucontext_test.cpp
+  DEPENDS
+    libc.src.ucontext.getcontext
+    libc.src.ucontext.setcontext
+)
diff --git a/libc/test/integration/src/ucontext/ucontext_test.cpp b/libc/test/integration/src/ucontext/ucontext_test.cpp
new file mode 100644
index 0000000000000..fae50878d1fdb
--- /dev/null
+++ b/libc/test/integration/src/ucontext/ucontext_test.cpp
@@ -0,0 +1,40 @@
+//===-- Hermetic integration test for ucontext routines -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// This is a hermetic integration test for getcontext and setcontext.
+// We use a hermetic test here because the heavier unit test infrastructure
+// (like GTest) interferes with context switching, stack frame management,
+// and floating-point state restoration, causing spurious failures.
+
+#include "test/IntegrationTest/test.h"
+
+#include "include/llvm-libc-types/ucontext_t.h"
+
+#include "src/ucontext/getcontext.h"
+#include "src/ucontext/setcontext.h"
+
+void basic_stub_test() {
+  ucontext_t ctx;
+  static volatile int jumped = 0;
+
+  int ret = LIBC_NAMESPACE::getcontext(&ctx);
+  ASSERT_EQ(ret, 0);
+
+  if (!jumped) {
+    jumped = 1;
+    LIBC_NAMESPACE::setcontext(&ctx);
+    ASSERT_TRUE(false); // Should not happen
+  }
+
+  ASSERT_TRUE(true);
+}
+
+TEST_MAIN() {
+  basic_stub_test();
+  return 0;
+}
diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt
index 236bee337d525..7bdafc6a85706 100644
--- a/libc/test/src/CMakeLists.txt
+++ b/libc/test/src/CMakeLists.txt
@@ -71,6 +71,7 @@ add_subdirectory(stdio)
 add_subdirectory(stdlib)
 add_subdirectory(string)
 add_subdirectory(strings)
+add_subdirectory(ucontext)
 add_subdirectory(wchar)
 add_subdirectory(wctype)
 add_subdirectory(time)
diff --git a/libc/test/src/ucontext/CMakeLists.txt b/libc/test/src/ucontext/CMakeLists.txt
new file mode 100644
index 0000000000000..4c8444393bd35
--- /dev/null
+++ b/libc/test/src/ucontext/CMakeLists.txt
@@ -0,0 +1,17 @@
+add_custom_target(libc_ucontext_unittests)
+
+if(TARGET libc.src.ucontext.getcontext)
+  add_libc_unittest(
+    ucontext_test
+    SUITE
+      libc_ucontext_unittests
+    SRCS
+      ucontext_test.cpp
+    DEPENDS
+      libc.src.ucontext.getcontext
+      libc.src.ucontext.setcontext
+      libc.src.signal.sigemptyset
+      libc.src.signal.sigaddset
+      libc.src.signal.sigprocmask
+  )
+endif()
diff --git a/libc/test/src/ucontext/ucontext_test.cpp b/libc/test/src/ucontext/ucontext_test.cpp
new file mode 100644
index 0000000000000..1258a8e54ed35
--- /dev/null
+++ b/libc/test/src/ucontext/ucontext_test.cpp
@@ -0,0 +1,78 @@
+//===-- Unittests for ucontext routines -----------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/ucontext/getcontext.h"
+#include "src/ucontext/setcontext.h"
+
+#include "src/signal/sigaddset.h"
+#include "src/signal/sigemptyset.h"
+#include "src/signal/sigprocmask.h"
+
+#include "test/UnitTest/Test.h"
+
+#include "include/llvm-libc-macros/signal-macros.h"
+
+namespace LIBC_NAMESPACE {
+
+static bool is_signal_set(const sigset_t *set, int signum) {
+  // NSIG is 64, sigset_t is an array of unsigned long.
+  // Signum is 1-indexed.
+  int word = (signum - 1) / (sizeof(unsigned long) * 8);
+  int bit = (signum - 1) % (sizeof(unsigned long) * 8);
+  return (set->__signals[word] & (1UL << bit)) != 0;
+}
+
+volatile int jumped = 0;
+TEST(LlvmLibcUcontextTest, BasicStubTest) {
+  ucontext_t ctx;
+  ASSERT_EQ(getcontext(&ctx), 0);
+  if (!jumped) {
+    jumped = 1;
+    setcontext(&ctx);
+    ASSERT_TRUE(false && "setcontext should not return on success");
+  }
+}
+
+TEST(LlvmLibcUcontextTest, SignalMaskTest) {
+  sigset_t set, old_set;
+  sigemptyset(&set);
+  sigaddset(&set, SIGUSR1);
+
+  // Set mask to [SIGUSR1] using sigprocmask
+  sigprocmask(SIG_SETMASK, &set, &old_set);
+
+  ucontext_t ctx;
+  getcontext(&ctx);
+
+  // Verify that getcontext captured the mask
+  ASSERT_TRUE(is_signal_set(&ctx.uc_sigmask, SIGUSR1));
+  ASSERT_FALSE(is_signal_set(&ctx.uc_sigmask, SIGUSR2));
+
+  sigset_t new_set;
+  static volatile int mask_jumped = 0;
+  if (mask_jumped == 0) {
+    mask_jumped = 1;
+    sigemptyset(&new_set);
+    sigaddset(&new_set, SIGUSR2);
+    sigprocmask(SIG_SETMASK, &new_set, nullptr);
+
+    setcontext(&ctx);
+  }
+
+  // Check current mask
+  sigset_t current;
+  sigprocmask(SIG_BLOCK, nullptr, &current);
+
+  // Restore original mask for clean state
+  sigprocmask(SIG_SETMASK, &old_set, nullptr);
+
+  ASSERT_TRUE(is_signal_set(&current, SIGUSR1));
+  ASSERT_FALSE(is_signal_set(&current, SIGUSR2));
+}
+
+} // namespace LIBC_NAMESPACE

``````````

</details>


https://github.com/llvm/llvm-project/pull/192343


More information about the libc-commits mailing list