[libc-commits] [libc] b2e0403 - [libc] Implement getcontext and setcontext for x86_64 (#192343)
via libc-commits
libc-commits at lists.llvm.org
Thu Apr 16 13:58:45 PDT 2026
Author: Jeff Bailey
Date: 2026-04-16T21:58:41+01:00
New Revision: b2e0403b11de1ce60fbf2aadec0cc4efb44cd4c4
URL: https://github.com/llvm/llvm-project/commit/b2e0403b11de1ce60fbf2aadec0cc4efb44cd4c4
DIFF: https://github.com/llvm/llvm-project/commit/b2e0403b11de1ce60fbf2aadec0cc4efb44cd4c4.diff
LOG: [libc] Implement getcontext and setcontext for x86_64 (#192343)
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.
Added:
libc/src/ucontext/CMakeLists.txt
libc/src/ucontext/getcontext.h
libc/src/ucontext/setcontext.h
libc/src/ucontext/x86_64/CMakeLists.txt
libc/src/ucontext/x86_64/getcontext.cpp
libc/src/ucontext/x86_64/setcontext.cpp
libc/test/integration/src/ucontext/CMakeLists.txt
libc/test/integration/src/ucontext/ucontext_test.cpp
libc/test/src/ucontext/CMakeLists.txt
libc/test/src/ucontext/ucontext_test.cpp
Modified:
libc/config/linux/x86_64/entrypoints.txt
libc/include/llvm-libc-types/x86_64/ucontext_t.h
libc/src/CMakeLists.txt
libc/test/integration/src/CMakeLists.txt
libc/test/src/CMakeLists.txt
Removed:
################################################################################
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 92324118a51c8..fe5991c695e25 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..64b35296f36d3
--- /dev/null
+++ b/libc/src/ucontext/x86_64/CMakeLists.txt
@@ -0,0 +1,28 @@
+add_entrypoint_object(
+ getcontext
+ SRCS
+ getcontext.cpp
+ HDRS
+ ../getcontext.h
+ 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
+ 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..43f353b4556ca
--- /dev/null
+++ b/libc/src/ucontext/x86_64/getcontext.cpp
@@ -0,0 +1,98 @@
+//===-- 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 "include/llvm-libc-macros/signal-macros.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 $%c[sig_block], %%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), [sig_block] "i"(SIG_BLOCK),
+ [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", "rdi", "rsi", "rax", "r10");
+}
+
+} // 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..0fa7715ce0f65
--- /dev/null
+++ b/libc/src/ucontext/x86_64/setcontext.cpp
@@ -0,0 +1,97 @@
+//===-- 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 "include/llvm-libc-macros/signal-macros.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))
+ # Note: Restoring the signal mask early means that if a signal
+ # arrives before the context switch is complete, it will run on
+ # the old stack with the new mask. Doing this later is
diff icult
+ # because the syscall clobbers registers.
+ #
+ # Note: We could avoid these stack operations by saving rdi in a
+ # non-volatile register (like r12) across the syscall, since all
+ # registers will be overwritten anyway. We stick to the stack for
+ # simplicity and readability.
+ 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 $%c[sig_setmask], %%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
+ mov %c[rsp](%%rdi), %%rsp
+ # Push saved RIP onto the new stack to use ret later
+ pushq %c[rip](%%rdi)
+
+ # Restore RSI and RDI last
+ mov %c[rsi](%%rdi), %%rsi
+ mov %c[rdi](%%rdi), %%rdi
+
+ retq
+ )" ::[sigset_size] "i"(sizeof(sigset_t)),
+ [syscall_num] "i"(SYS_rt_sigprocmask), [sig_setmask] "i"(SIG_SETMASK),
+ [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..b7f83ba42f5dc
--- /dev/null
+++ b/libc/test/integration/src/ucontext/ucontext_test.cpp
@@ -0,0 +1,182 @@
+//===-- 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);
+}
+
+void register_preservation_test() {
+ ucontext_t ctx;
+ static volatile int jumped = 0;
+
+ long checked_r12, checked_r13, checked_r14, checked_r15;
+
+ {
+ register long r12_val asm("r12") = 0x1212121212121212;
+ register long r13_val asm("r13") = 0x1313131313131313;
+ register long r14_val asm("r14") = 0x1414141414141414;
+ register long r15_val asm("r15") = 0x1515151515151515;
+
+ register void *rdi_val asm("rdi") = &ctx;
+
+ asm volatile("call *%[getcontext_ptr]"
+ : "+r"(rdi_val), "+r"(r12_val), "+r"(r13_val), "+r"(r14_val),
+ "+r"(r15_val)
+ : [getcontext_ptr] "r"((void *)LIBC_NAMESPACE::getcontext)
+ : "memory", "rax", "rcx", "rdx", "rsi");
+
+ checked_r12 = r12_val;
+ checked_r13 = r13_val;
+ checked_r14 = r14_val;
+ checked_r15 = r15_val;
+ }
+
+ if (!jumped) {
+ jumped = 1;
+
+ // Modify registers to ensure they are restored from context
+ asm volatile("movq $0, %%r12\n\t"
+ "movq $0, %%r13\n\t"
+ "movq $0, %%r14\n\t"
+ "movq $0, %%r15\n\t" ::
+ : "r12", "r13", "r14", "r15");
+
+ register const ucontext_t *rdi_set asm("rdi") = &ctx;
+ asm volatile("call *%[setcontext_ptr]" ::"r"(rdi_set),
+ [setcontext_ptr] "r"((void *)LIBC_NAMESPACE::setcontext)
+ : "memory");
+
+ ASSERT_TRUE(false); // Should not reach here
+ }
+
+ ASSERT_EQ(checked_r12, (long)0x1212121212121212);
+ ASSERT_EQ(checked_r13, (long)0x1313131313131313);
+ ASSERT_EQ(checked_r14, (long)0x1414141414141414);
+ ASSERT_EQ(checked_r15, (long)0x1515151515151515);
+}
+
+void test_rbx_rdx() {
+ ucontext_t ctx;
+ static volatile int jumped = 0;
+
+ long checked_rbx, checked_rdx;
+
+ {
+ register long rbx_val asm("rbx") = 0xBBBBBBBBBBBBBBBB;
+ register long rdx_val asm("rdx") = 0xDDDDDDDDDDDDDDDD;
+
+ register void *rdi_val asm("rdi") = &ctx;
+
+ asm volatile("call *%[getcontext_ptr]"
+ : "+r"(rdi_val), "+r"(rbx_val), "+r"(rdx_val)
+ : [getcontext_ptr] "r"((void *)LIBC_NAMESPACE::getcontext)
+ : "memory", "rax", "rcx", "rsi");
+
+ checked_rbx = rbx_val;
+ checked_rdx = rdx_val;
+ }
+
+ if (!jumped) {
+ jumped = 1;
+
+ asm volatile("movq $0, %%rbx\n\t"
+ "movq $0, %%rdx\n\t" ::
+ : "rbx", "rdx");
+
+ register const ucontext_t *rdi_set asm("rdi") = &ctx;
+ asm volatile("call *%[setcontext_ptr]" ::"r"(rdi_set),
+ [setcontext_ptr] "r"((void *)LIBC_NAMESPACE::setcontext)
+ : "memory");
+
+ ASSERT_TRUE(false);
+ }
+
+ ASSERT_EQ(checked_rbx, (long)0xBBBBBBBBBBBBBBBB);
+ ASSERT_EQ(checked_rdx, (long)0xDDDDDDDDDDDDDDDD);
+}
+
+void test_r8_r11() {
+ ucontext_t ctx;
+ static volatile int jumped = 0;
+
+ long checked_r8, checked_r9, checked_r10, checked_r11;
+
+ {
+ register long r8_val asm("r8") = 0x0808080808080808;
+ register long r9_val asm("r9") = 0x0909090909090909;
+ register long r10_val asm("r10") = 0x1010101010101010;
+ register long r11_val asm("r11") = 0x1111111111111111;
+
+ register void *rdi_val asm("rdi") = &ctx;
+
+ asm volatile("call *%[getcontext_ptr]"
+ : "+r"(rdi_val), "+r"(r8_val), "+r"(r9_val), "+r"(r10_val),
+ "+r"(r11_val)
+ : [getcontext_ptr] "r"((void *)LIBC_NAMESPACE::getcontext)
+ : "memory", "rax", "rcx", "rdx", "rsi");
+
+ checked_r8 = r8_val;
+ checked_r9 = r9_val;
+ checked_r10 = r10_val;
+ checked_r11 = r11_val;
+ }
+
+ if (!jumped) {
+ jumped = 1;
+
+ asm volatile("movq $0, %%r8\n\t"
+ "movq $0, %%r9\n\t"
+ "movq $0, %%r10\n\t"
+ "movq $0, %%r11\n\t" ::
+ : "r8", "r9", "r10", "r11");
+
+ register const ucontext_t *rdi_set asm("rdi") = &ctx;
+ asm volatile("call *%[setcontext_ptr]" ::"r"(rdi_set),
+ [setcontext_ptr] "r"((void *)LIBC_NAMESPACE::setcontext)
+ : "memory");
+
+ ASSERT_TRUE(false);
+ }
+
+ ASSERT_EQ(checked_r8, (long)0x0808080808080808);
+ ASSERT_EQ(checked_r9, (long)0x0909090909090909);
+ ASSERT_EQ(checked_r10, (long)0x1010101010101010);
+ ASSERT_EQ(checked_r11, (long)0x1111111111111111);
+}
+
+TEST_MAIN() {
+ basic_stub_test();
+ register_preservation_test();
+ test_rbx_rdx();
+ test_r8_r11();
+ 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..1983c2f1ec5b2
--- /dev/null
+++ b/libc/test/src/ucontext/ucontext_test.cpp
@@ -0,0 +1,79 @@
+//===-- 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) {
+ // TODO: Replace this with sigismember once it is implemented.
+ // 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;
+}
+
+TEST(LlvmLibcUcontextTest, BasicStubTest) {
+ static volatile int jumped = 0;
+ 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, ¤t);
+
+ // Restore original mask for clean state
+ sigprocmask(SIG_SETMASK, &old_set, nullptr);
+
+ ASSERT_TRUE(is_signal_set(¤t, SIGUSR1));
+ ASSERT_FALSE(is_signal_set(¤t, SIGUSR2));
+}
+
+} // namespace LIBC_NAMESPACE
More information about the libc-commits
mailing list