[libc] update abort implementation and lift it for internal usage (#189756)

This commit is contained in:
Schrodinger ZHU Yifan 2026-04-03 18:40:52 -04:00 committed by GitHub
parent 18268ebc76
commit 463f6cb576
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 243 additions and 22 deletions

View File

@ -11,6 +11,7 @@ add_header_library(
libc.src.__support.OSUtil.linux.vdso
libc.src.__support.OSUtil.osutil
libc.src.__support.error_or
libc.src.__support.threads.raw_rwlock
)
add_entrypoint_object(

View File

@ -18,7 +18,7 @@ namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(int, sigaction,
(int signal, const struct sigaction *__restrict libc_new,
struct sigaction *__restrict libc_old)) {
ErrorOr<int> ret = do_sigaction(signal, libc_new, libc_old);
ErrorOr<int> ret = checked_sigaction(signal, libc_new, libc_old);
if (ret)
return ret.value();

View File

@ -19,6 +19,7 @@
#include "src/__support/common.h"
#include "src/__support/error_or.h"
#include "src/__support/macros/config.h"
#include "src/__support/threads/raw_rwlock.h"
#include <sys/syscall.h> // For syscall numbers.
@ -113,9 +114,50 @@ LIBC_INLINE int restore_signals(const sigset_t &set) {
&set, nullptr, sizeof(sigset_t));
}
LIBC_INLINE int unblock_signal(int signal) {
sigset_t set = empty_set();
if (!add_signal(set, signal))
return -EINVAL;
return LIBC_NAMESPACE::syscall_impl<int>(SYS_rt_sigprocmask, SIG_UNBLOCK,
&set, nullptr, sizeof(sigset_t));
}
// This guard is used to:
// 1. temporarily block the all signal, avoid post fork invalid state to be
// exposed to async signal handlers.
// 2. ensure the ordering between sigaction and fork/spawn, so that forked
// processes can see modification from a just returned concurrent call.
class SigAbortGuard {
private:
sigset_t old_mask;
LIBC_INLINE_VAR static RawRwLock abort_lock;
public:
LIBC_INLINE constexpr SigAbortGuard(bool exclusive) : old_mask{} {
RawRwLock::LockResult result = RawRwLock::LockResult::Success;
do {
if (exclusive)
result = abort_lock.write_lock(cpp::nullopt);
else
result = abort_lock.read_lock(cpp::nullopt);
} while (result == RawRwLock::LockResult::Overflow);
// This uses a valid sigset_t size and internal storage. A failure here
// would indicate a kernel ABI mismatch, which is not actionable here.
block_all_signals(old_mask);
}
LIBC_INLINE ~SigAbortGuard() {
// This restores a previously saved mask from internal storage. A failure
// here would likewise be a non-recoverable kernel ABI issue.
restore_signals(old_mask);
(void)abort_lock.unlock();
}
};
LIBC_INLINE ErrorOr<int>
do_sigaction(int signal, const struct sigaction *__restrict libc_new,
struct sigaction *__restrict libc_old) {
unchecked_sigaction(int signal, const struct sigaction *__restrict libc_new,
struct sigaction *__restrict libc_old) {
vdso::TypedSymbol<vdso::VDSOSym::RTSigReturn> rt_sigreturn;
KernelSigaction kernel_new;
if (libc_new) {
@ -138,6 +180,18 @@ do_sigaction(int signal, const struct sigaction *__restrict libc_new,
return 0;
}
LIBC_INLINE ErrorOr<int>
checked_sigaction(int signal, const struct sigaction *__restrict libc_new,
struct sigaction *__restrict libc_old) {
if (signal <= 0 || signal >= NSIG)
return Error(EINVAL);
if (signal == SIGABRT) {
SigAbortGuard guard(true);
return unchecked_sigaction(signal, libc_new, libc_old);
}
return unchecked_sigaction(signal, libc_new, libc_old);
}
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC_SIGNAL_LINUX_SIGNAL_UTILS_H

View File

@ -13,4 +13,5 @@ add_entrypoint_object(
libc.src.__support.CPP.optional
libc.src.__support.OSUtil.osutil
libc.src.spawn.file_actions
libc.src.signal.linux.signal_utils
)

View File

@ -16,6 +16,7 @@
#include "hdr/fcntl_macros.h"
#include "hdr/types/mode_t.h"
#include "src/signal/linux/signal_utils.h"
#include <signal.h> // For SIGCHLD
#include <spawn.h>
#include <sys/syscall.h> // For syscall numbers.
@ -25,6 +26,8 @@ namespace LIBC_NAMESPACE_DECL {
namespace {
pid_t fork() {
// Block signal and stop abort sigaction modification.
SigAbortGuard guard(/*exclusive=*/false);
// TODO: Use only the clone syscall and use a sperate small stack in the child
// to avoid duplicating the complete stack from the parent. A new stack will
// be created on exec anyway so duplicating the full stack is unnecessary.

View File

@ -1,3 +1,14 @@
add_header_library(
abort_utils
HDRS
abort_utils.h
DEPENDS
libc.src.__support.OSUtil.linux.syscall_wrappers.raise
libc.src.__support.OSUtil.osutil
libc.src.signal.linux.__restore
libc.src.signal.linux.signal_utils
)
add_entrypoint_object(
abort
SRCS
@ -5,7 +16,7 @@ add_entrypoint_object(
HDRS
../abort.h
DEPENDS
.abort_utils
libc.src.signal.linux.__restore
libc.include.stdlib
libc.src.signal.raise
libc.src.stdlib._Exit
)

View File

@ -6,26 +6,11 @@
//
//===----------------------------------------------------------------------===//
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/signal/raise.h"
#include "src/stdlib/_Exit.h"
#include "src/stdlib/abort.h"
#include "src/stdlib/linux/abort_utils.h"
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(void, abort, ()) {
// TODO: When sigprocmask and sigaction land:
// Unblock SIGABRT, raise it, if it was ignored or the handler returned,
// change its action to SIG_DFL, raise it again.
// TODO: When C11 mutexes land:
// Acquire recursive mutex (in case the current signal handler for SIGABRT
// itself calls abort we don't want to deadlock on the same thread trying
// to acquire it's own mutex.)
LIBC_NAMESPACE::raise(SIGABRT);
LIBC_NAMESPACE::raise(SIGKILL);
LIBC_NAMESPACE::_Exit(127);
}
LLVM_LIBC_FUNCTION(void, abort, ()) { abort_utils::abort(); }
} // namespace LIBC_NAMESPACE_DECL

View File

@ -0,0 +1,54 @@
//===-- Internal header for Linux abort -------------------------*- 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_STDLIB_LINUX_ABORT_UTILS_H
#define LLVM_LIBC_SRC_STDLIB_LINUX_ABORT_UTILS_H
#include "hdr/types/sigset_t.h"
#include "hdr/types/struct_sigaction.h"
#include "src/__support/OSUtil/exit.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/raise.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/signal/linux/signal_utils.h"
namespace LIBC_NAMESPACE_DECL {
namespace abort_utils {
[[noreturn]] LIBC_INLINE void abort() {
// Try to raise SIGABRT.
// If this fails, or if a handler returns, keep going with the hard-abort
// sequence below.
linux_syscalls::raise(SIGABRT);
// We get back from abort, potentially from a abort handler.
// We recover the handler to default and raise it again. Since this is the
// real abort routine, we demand exclusive access to the abort lock.
// We have already returned from the first raise, so it is okay to grab
// exclusive access.
SigAbortGuard guard(true);
struct sigaction sa{};
sa.sa_handler = SIG_DFL;
sa.sa_flags = 0;
// There is no recovery path from sigaction failure while aborting.
unchecked_sigaction(SIGABRT, &sa, nullptr);
// If this still returns, fall through to the final termination path.
linux_syscalls::raise(SIGABRT);
// Now unblock the signal. The pending abort signal is now unblocked and
// should be delivered to its default handler.
// If this fails, there is still no meaningful recovery path while aborting.
unblock_signal(SIGABRT);
internal::exit(127);
}
} // namespace abort_utils
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC_STDLIB_LINUX_ABORT_UTILS_H

View File

@ -16,3 +16,26 @@ add_integration_test(
FRANCE=Paris
GERMANY=Berlin
)
if(${LIBC_TARGET_OS} STREQUAL "linux")
add_integration_test(
abort_test
SUITE
stdlib-integration-tests
SRCS
abort_test.cpp
DEPENDS
libc.include.signal
libc.include.sys_wait
libc.include.unistd
libc.src.signal.signal
libc.src.stdlib._Exit
libc.src.stdlib.abort
libc.src.sys.wait.waitpid
libc.src.unistd.close
libc.src.unistd.fork
libc.src.unistd.pipe
libc.src.unistd.read
libc.src.unistd.write
)
endif()

View File

@ -0,0 +1,89 @@
//===-- Integration tests for abort --------------------------------------===//
//
// 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/signal/signal.h"
#include "src/stdlib/_Exit.h"
#include "src/stdlib/abort.h"
#include "src/stdlib/linux/abort_utils.h"
#include "src/sys/wait/waitpid.h"
#include "src/unistd/close.h"
#include "src/unistd/fork.h"
#include "src/unistd/pipe.h"
#include "src/unistd/read.h"
#include "src/unistd/write.h"
#include "test/IntegrationTest/test.h"
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
namespace {
constexpr char HANDLER_MARKER = 'A';
int handler_pipe_fd = -1;
void expect_child_died_with_signal(pid_t pid, int signal) {
int status = 0;
ASSERT_EQ(LIBC_NAMESPACE::waitpid(pid, &status, 0), pid);
ASSERT_TRUE(WIFSIGNALED(status));
ASSERT_EQ(WTERMSIG(status), signal);
}
void child_abort() { LIBC_NAMESPACE::abort(); }
void returning_sigabrt_handler(int) {
if (handler_pipe_fd >= 0)
LIBC_NAMESPACE::write(handler_pipe_fd, &HANDLER_MARKER, 1);
}
void child_abort_with_returning_handler() {
auto previous = LIBC_NAMESPACE::signal(SIGABRT, returning_sigabrt_handler);
ASSERT_NE(previous, SIG_ERR);
LIBC_NAMESPACE::abort();
}
void abort_kills_child_with_sigabrt() {
pid_t pid = LIBC_NAMESPACE::fork();
if (pid == 0)
child_abort();
ASSERT_TRUE(pid > 0);
expect_child_died_with_signal(pid, SIGABRT);
}
void abort_reraises_sigabrt_after_returning_handler() {
int pipefd[2];
ASSERT_EQ(LIBC_NAMESPACE::pipe(pipefd), 0);
pid_t pid = LIBC_NAMESPACE::fork();
if (pid == 0) {
LIBC_NAMESPACE::close(pipefd[0]);
handler_pipe_fd = pipefd[1];
child_abort_with_returning_handler();
}
ASSERT_TRUE(pid > 0);
ASSERT_EQ(LIBC_NAMESPACE::close(pipefd[1]), 0);
expect_child_died_with_signal(pid, SIGABRT);
char marker = 0;
ASSERT_EQ(LIBC_NAMESPACE::read(pipefd[0], &marker, 1), ssize_t(1));
ASSERT_EQ(marker, HANDLER_MARKER);
ASSERT_EQ(LIBC_NAMESPACE::close(pipefd[0]), 0);
}
} // namespace
TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
[[maybe_unused]] char **envp) {
abort_kills_child_with_sigabrt();
abort_reraises_sigabrt_after_returning_handler();
return 0;
}