Reland "[libc] build fix for sigsetjmp (#137047)" (#137214)

Reland `sigsetjmp` patches with build fixes.

We wrap every target replying on the epilogue library into conditional
checks.

---------

Co-authored-by: Petr Hosek <phosek@google.com>
This commit is contained in:
Schrodinger ZHU Yifan 2025-04-29 09:28:42 -04:00 committed by GitHub
parent cc0cf72539
commit 6695976d16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 428 additions and 8 deletions

View File

@ -1060,6 +1060,8 @@ if(LLVM_LIBC_FULL_BUILD)
# setjmp.h entrypoints
libc.src.setjmp.longjmp
libc.src.setjmp.setjmp
libc.src.setjmp.siglongjmp
libc.src.setjmp.sigsetjmp
# stdio.h entrypoints
libc.src.stdio.clearerr

View File

@ -223,5 +223,14 @@ add_proxy_header_library(
libc.include.wchar
)
# offsetof is a macro inside compiler resource header stddef.h
add_proxy_header_library(
offsetof_macros
HDRS
offsetof_macros.h
FULL_BUILD_DEPENDS
libc.include.llvm-libc-macros.offsetof_macro
)
add_subdirectory(types)
add_subdirectory(func)

View File

@ -0,0 +1,23 @@
//===-- Definition of macros for offsetof ---------------------------------===//
//
// 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_HDR_OFFSETOF_MACROS_H
#define LLVM_LIBC_HDR_OFFSETOF_MACROS_H
#ifdef LIBC_FULL_BUILD
#include "include/llvm-libc-macros/offsetof-macro.h"
#else // Overlay mode
#define __need_offsetof
#include <stddef.h>
#endif // LLVM_LIBC_FULL_BUILD
#endif // LLVM_LIBC_HDR_OFFSETOF_MACROS_H

View File

@ -39,7 +39,6 @@ add_header(gid_t HDR gid_t.h)
add_header(uid_t HDR uid_t.h)
add_header(imaxdiv_t HDR imaxdiv_t.h)
add_header(ino_t HDR ino_t.h)
add_header(jmp_buf HDR jmp_buf.h)
add_header(mbstate_t HDR mbstate_t.h)
add_header(mode_t HDR mode_t.h)
add_header(mtx_t HDR mtx_t.h DEPENDS .__futex_word .__mutex_type)
@ -83,6 +82,7 @@ add_header(union_sigval HDR union_sigval.h)
add_header(siginfo_t HDR siginfo_t.h DEPENDS .union_sigval .pid_t .uid_t .clock_t)
add_header(sig_atomic_t HDR sig_atomic_t.h)
add_header(sigset_t HDR sigset_t.h DEPENDS libc.include.llvm-libc-macros.signal_macros)
add_header(jmp_buf HDR jmp_buf.h DEPENDS .sigset_t)
add_header(struct_sigaction HDR struct_sigaction.h DEPENDS .sigset_t .siginfo_t)
add_header(struct_timespec HDR struct_timespec.h DEPENDS .time_t)
add_header(

View File

@ -9,6 +9,18 @@
#ifndef LLVM_LIBC_TYPES_JMP_BUF_H
#define LLVM_LIBC_TYPES_JMP_BUF_H
// TODO: implement sigjmp_buf related functions for other architectures
// Issue: https://github.com/llvm/llvm-project/issues/136358
#if defined(__linux__)
#if defined(__i386__) || defined(__x86_64__)
#define __LIBC_HAS_SIGJMP_BUF
#endif
#endif
#if defined(__LIBC_HAS_SIGJMP_BUF)
#include "sigset_t.h"
#endif
typedef struct {
#ifdef __x86_64__
__UINT64_TYPE__ rbx;
@ -50,8 +62,22 @@ typedef struct {
#else
#error "__jmp_buf not available for your target architecture."
#endif
#if defined(__LIBC_HAS_SIGJMP_BUF)
// return address
void *sig_retaddr;
// extra register buffer to avoid indefinite stack growth in sigsetjmp
void *sig_extra;
// signal masks
sigset_t sigmask;
#endif
} __jmp_buf;
typedef __jmp_buf jmp_buf[1];
#if defined(__LIBC_HAS_SIGJMP_BUF)
typedef __jmp_buf sigjmp_buf[1];
#endif
#undef __LIBC_HAS_SIGJMP_BUF
#endif // LLVM_LIBC_TYPES_JMP_BUF_H

View File

@ -21,3 +21,19 @@ functions:
- _Returns_twice
arguments:
- type: jmp_buf
- name: sigsetjmp
standards:
- POSIX
return_type: int
attributes:
- _Returns_twice
arguments:
- type: sigjmp_buf
- type: int
- name: siglongjmp
standards:
- POSIX
return_type: _Noreturn void
arguments:
- type: sigjmp_buf
- type: int

View File

@ -1,3 +1,13 @@
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
add_object_library(
sigsetjmp_epilogue
ALIAS
DEPENDS
.${LIBC_TARGET_OS}.sigsetjmp_epilogue
)
endif()
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_ARCHITECTURE})
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_ARCHITECTURE})
endif()
@ -15,3 +25,22 @@ add_entrypoint_object(
DEPENDS
.${LIBC_TARGET_ARCHITECTURE}.longjmp
)
if (TARGET libc.src.setjmp.sigsetjmp_epilogue)
add_entrypoint_object(
siglongjmp
SRCS
siglongjmp.cpp
HDRS
siglongjmp.h
DEPENDS
.longjmp
)
add_entrypoint_object(
sigsetjmp
ALIAS
DEPENDS
.${LIBC_TARGET_ARCHITECTURE}.sigsetjmp
)
endif()

View File

@ -0,0 +1,12 @@
add_object_library(
sigsetjmp_epilogue
HDRS
../sigsetjmp_epilogue.h
SRCS
sigsetjmp_epilogue.cpp
DEPENDS
libc.src.__support.common
libc.src.__support.OSUtil.osutil
libc.hdr.types.jmp_buf
libc.hdr.types.sigset_t
)

View File

@ -0,0 +1,25 @@
//===-- Implementation of sigsetjmp_epilogue ------------------------------===//
//
// 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/setjmp/sigsetjmp_epilogue.h"
#include "src/__support/OSUtil/syscall.h"
#include "src/__support/common.h"
#include <sys/syscall.h> // For syscall numbers.
namespace LIBC_NAMESPACE_DECL {
[[gnu::returns_twice]] int sigsetjmp_epilogue(jmp_buf buffer, int retval) {
// If set is NULL, then the signal mask is unchanged (i.e., how is
// ignored), but the current value of the signal mask is nevertheless
// returned in oldset (if it is not NULL).
syscall_impl<long>(SYS_rt_sigprocmask, SIG_SETMASK,
/* set= */ retval ? &buffer->sigmask : nullptr,
/* old_set= */ retval ? nullptr : &buffer->sigmask,
sizeof(sigset_t));
return retval;
}
} // namespace LIBC_NAMESPACE_DECL

View File

@ -29,7 +29,8 @@ namespace LIBC_NAMESPACE_DECL {
#ifdef LIBC_COMPILER_IS_GCC
[[gnu::nothrow]]
#endif
__attribute__((returns_twice)) int setjmp(jmp_buf buf);
[[gnu::returns_twice]] int
setjmp(jmp_buf buf);
} // namespace LIBC_NAMESPACE_DECL

View File

@ -0,0 +1,23 @@
//===-- Implementation of siglongjmp --------------------------------------===//
//
// 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/setjmp/siglongjmp.h"
#include "src/__support/common.h"
#include "src/setjmp/longjmp.h"
namespace LIBC_NAMESPACE_DECL {
// siglongjmp is the same as longjmp. The additional recovery work is done in
// the epilogue of the sigsetjmp function.
// TODO: move this inside the TU of longjmp and making it an alias after
// sigsetjmp is implemented for all architectures.
LLVM_LIBC_FUNCTION(void, siglongjmp, (jmp_buf buf, int val)) {
return LIBC_NAMESPACE::longjmp(buf, val);
}
} // namespace LIBC_NAMESPACE_DECL

View File

@ -0,0 +1,25 @@
//===-- Implementation header for siglongjmp --------------------*- 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_SETJMP_SIGLONGJMP_H
#define LLVM_LIBC_SRC_SETJMP_SIGLONGJMP_H
#include "hdr/types/jmp_buf.h"
#include "src/__support/macros/config.h"
#include "src/__support/macros/properties/compiler.h"
namespace LIBC_NAMESPACE_DECL {
#ifdef LIBC_COMPILER_IS_GCC
[[gnu::nothrow]]
#endif
void siglongjmp(jmp_buf buf, int val);
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC_SETJMP_SIGLONGJMP_H

View File

@ -0,0 +1,26 @@
//===-- Implementation header for sigsetjmp ---------------------*- 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_SETJMP_SIGSETJMP_H
#define LLVM_LIBC_SRC_SETJMP_SIGSETJMP_H
#include "hdr/types/jmp_buf.h"
#include "src/__support/macros/config.h"
#include "src/__support/macros/properties/compiler.h"
namespace LIBC_NAMESPACE_DECL {
#ifdef LIBC_COMPILER_IS_GCC
[[gnu::nothrow]]
#endif
[[gnu::returns_twice]] int
sigsetjmp(sigjmp_buf buf, int savesigs);
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC_SETJMP_SIGSETJMP_H

View File

@ -0,0 +1,19 @@
//===-- Implementation header for sigsetjmp epilogue ------------*- 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_SETJMP_SIGSETJMP_EPILOGUE_H
#define LLVM_LIBC_SRC_SETJMP_SIGSETJMP_EPILOGUE_H
#include "hdr/types/jmp_buf.h"
#include "src/__support/common.h"
namespace LIBC_NAMESPACE_DECL {
[[gnu::returns_twice]] int sigsetjmp_epilogue(jmp_buf buffer, int retval);
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC_SETJMP_SIGSETJMP_EPILOGUE_H

View File

@ -5,10 +5,24 @@ add_entrypoint_object(
HDRS
../setjmp_impl.h
DEPENDS
libc.hdr.offsetof_macros
libc.hdr.types.jmp_buf
COMPILE_OPTIONS
${libc_opt_high_flag}
)
if (TARGET libc.src.setjmp.sigsetjmp_epilogue)
add_entrypoint_object(
sigsetjmp
SRCS
sigsetjmp.cpp
HDRS
../sigsetjmp.h
DEPENDS
libc.hdr.types.jmp_buf
libc.hdr.types.sigset_t
libc.hdr.offsetof_macros
libc.src.setjmp.sigsetjmp_epilogue
libc.src.setjmp.setjmp
)
endif()
add_entrypoint_object(
longjmp
@ -18,7 +32,4 @@ add_entrypoint_object(
../longjmp.h
DEPENDS
libc.hdr.types.jmp_buf
COMPILE_OPTIONS
${libc_opt_high_flag}
-fomit-frame-pointer
)

View File

@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
#include "include/llvm-libc-macros/offsetof-macro.h"
#include "hdr/offsetof_macros.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/setjmp/setjmp_impl.h"

View File

@ -0,0 +1,68 @@
//===-- Implementation of sigsetjmp ---------------------------------------===//
//
// 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/setjmp/sigsetjmp.h"
#include "hdr/offsetof_macros.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/setjmp/setjmp_impl.h"
#include "src/setjmp/sigsetjmp_epilogue.h"
#if !defined(LIBC_TARGET_ARCH_IS_X86)
#error "Invalid file include"
#endif
namespace LIBC_NAMESPACE_DECL {
#ifdef __i386__
[[gnu::naked]]
LLVM_LIBC_FUNCTION(int, sigsetjmp, (sigjmp_buf buf)) {
asm(R"(
mov 8(%%esp), %%ecx
jecxz .Lnosave
mov 4(%%esp), %%eax
pop %c[retaddr](%%eax)
mov %%ebx, %c[extra](%%eax)
mov %%eax, %%ebx
call %P[setjmp]
push %c[retaddr](%%ebx)
mov %%ebx,4(%%esp)
mov %%eax,8(%%esp)
mov %c[extra](%%ebx), %%ebx
jmp %P[epilogue]
.Lnosave:
jmp %P[setjmp])" ::[retaddr] "i"(offsetof(__jmp_buf, sig_retaddr)),
[extra] "i"(offsetof(__jmp_buf, sig_extra)), [setjmp] "X"(setjmp),
[epilogue] "X"(sigsetjmp_epilogue)
: "eax", "ebx", "ecx");
}
#endif
[[gnu::naked]]
LLVM_LIBC_FUNCTION(int, sigsetjmp, (sigjmp_buf, int)) {
asm(R"(
test %%esi, %%esi
jz .Lnosave
pop %c[retaddr](%%rdi)
mov %%rbx, %c[extra](%%rdi)
mov %%rdi, %%rbx
call %P[setjmp]
push %c[retaddr](%%rbx)
mov %%rbx, %%rdi
mov %%eax, %%esi
mov %c[extra](%%rdi), %%rbx
jmp %P[epilogue]
.Lnosave:
jmp %P[setjmp])" ::[retaddr] "i"(offsetof(__jmp_buf, sig_retaddr)),
[extra] "i"(offsetof(__jmp_buf, sig_extra)), [setjmp] "X"(setjmp),
[epilogue] "X"(sigsetjmp_epilogue)
: "rax", "rbx");
}
} // namespace LIBC_NAMESPACE_DECL

View File

@ -17,3 +17,20 @@ add_libc_unittest(
libc.src.setjmp.longjmp
libc.src.setjmp.setjmp
)
add_libc_unittest(
sigsetjmp_test
SUITE
libc_setjmp_unittests
SRCS
sigsetjmp_test.cpp
CXX_STANDARD
20
DEPENDS
libc.src.setjmp.sigsetjmp
libc.src.setjmp.siglongjmp
libc.src.signal.sigprocmask
libc.src.string.memset
libc.src.string.memcmp
libc.hdr.types.sigset_t
)

View File

@ -0,0 +1,88 @@
//===-- Unittests for sigsetjmp and siglongjmp ----------------------------===//
//
// 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/setjmp/siglongjmp.h"
#include "src/setjmp/sigsetjmp.h"
#include "src/signal/sigprocmask.h"
#include "src/string/memcmp.h"
#include "src/string/memset.h"
#include "test/UnitTest/Test.h"
constexpr int MAX_LOOP = 123;
int longjmp_called = 0;
void jump_back(jmp_buf buf, int n) {
longjmp_called++;
LIBC_NAMESPACE::siglongjmp(buf, n); // Will return |n| out of setjmp
}
TEST(LlvmLibcSetJmpTest, SigSetAndJumpBackSaveSigs) {
jmp_buf buf;
longjmp_called = 0;
volatile int n = 0;
sigset_t old;
sigset_t mask_all;
sigset_t recovered;
LIBC_NAMESPACE::memset(&mask_all, 0xFF, sizeof(mask_all));
LIBC_NAMESPACE::memset(&old, 0, sizeof(old));
LIBC_NAMESPACE::memset(&recovered, 0, sizeof(recovered));
LIBC_NAMESPACE::sigprocmask(0, nullptr, &old);
if (LIBC_NAMESPACE::sigsetjmp(buf, 1) <= MAX_LOOP) {
LIBC_NAMESPACE::sigprocmask(0, nullptr, &recovered);
ASSERT_EQ(0, LIBC_NAMESPACE::memcmp(&old, &recovered, sizeof(old)));
n = n + 1;
LIBC_NAMESPACE::sigprocmask(SIG_BLOCK, &mask_all, nullptr);
jump_back(buf, n);
}
ASSERT_EQ(longjmp_called, n);
ASSERT_EQ(n, MAX_LOOP + 1);
}
TEST(LlvmLibcSetJmpTest, SigSetAndJumpBackValOneSaveSigs) {
jmp_buf buf;
longjmp_called = 0;
sigset_t old;
sigset_t mask_all;
sigset_t recovered;
LIBC_NAMESPACE::memset(&mask_all, 0xFF, sizeof(mask_all));
LIBC_NAMESPACE::memset(&old, 0, sizeof(old));
LIBC_NAMESPACE::memset(&recovered, 0, sizeof(recovered));
LIBC_NAMESPACE::sigprocmask(0, nullptr, &old);
int val = LIBC_NAMESPACE::sigsetjmp(buf, 1);
if (val == 0) {
LIBC_NAMESPACE::sigprocmask(SIG_BLOCK, &mask_all, nullptr);
jump_back(buf, val);
}
LIBC_NAMESPACE::sigprocmask(0, nullptr, &recovered);
ASSERT_EQ(0, LIBC_NAMESPACE::memcmp(&old, &recovered, sizeof(old)));
ASSERT_EQ(longjmp_called, 1);
ASSERT_EQ(val, 1);
}
TEST(LlvmLibcSetJmpTest, SigSetAndJumpBackNoSaveSigs) {
jmp_buf buf;
longjmp_called = 0;
volatile int n = 0;
if (LIBC_NAMESPACE::sigsetjmp(buf, 0) <= MAX_LOOP) {
n = n + 1;
jump_back(buf, n);
}
ASSERT_EQ(longjmp_called, n);
ASSERT_EQ(n, MAX_LOOP + 1);
}
TEST(LlvmLibcSetJmpTest, SigSetAndJumpBackValOneNoSaveSigs) {
jmp_buf buf;
longjmp_called = 0;
int val = LIBC_NAMESPACE::sigsetjmp(buf, 0);
if (val == 0) {
jump_back(buf, val);
}
ASSERT_EQ(longjmp_called, 1);
ASSERT_EQ(val, 1);
}