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..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 + +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 + +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 difficult + # 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