| // SPDX-License-Identifier: LGPL-2.1 |
| /* |
| * rseq.c |
| * |
| * Copyright (C) 2016 Mathieu Desnoyers <mathieu.desnoyers@efficios.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; only |
| * version 2.1 of the License. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| */ |
| |
| #define _GNU_SOURCE |
| #include <errno.h> |
| #include <sched.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <syscall.h> |
| #include <assert.h> |
| #include <signal.h> |
| #include <limits.h> |
| #include <dlfcn.h> |
| #include <stddef.h> |
| |
| #include "../kselftest.h" |
| #include "rseq.h" |
| |
| static const ptrdiff_t *libc_rseq_offset_p; |
| static const unsigned int *libc_rseq_size_p; |
| static const unsigned int *libc_rseq_flags_p; |
| |
| /* Offset from the thread pointer to the rseq area. */ |
| ptrdiff_t rseq_offset; |
| |
| /* Size of the registered rseq area. 0 if the registration was |
| unsuccessful. */ |
| unsigned int rseq_size = -1U; |
| |
| /* Flags used during rseq registration. */ |
| unsigned int rseq_flags; |
| |
| static int rseq_ownership; |
| |
| static |
| __thread struct rseq_abi __rseq_abi __attribute__((tls_model("initial-exec"))) = { |
| .cpu_id = RSEQ_ABI_CPU_ID_UNINITIALIZED, |
| }; |
| |
| static int sys_rseq(struct rseq_abi *rseq_abi, uint32_t rseq_len, |
| int flags, uint32_t sig) |
| { |
| return syscall(__NR_rseq, rseq_abi, rseq_len, flags, sig); |
| } |
| |
| int rseq_available(void) |
| { |
| int rc; |
| |
| rc = sys_rseq(NULL, 0, 0, 0); |
| if (rc != -1) |
| abort(); |
| switch (errno) { |
| case ENOSYS: |
| return 0; |
| case EINVAL: |
| return 1; |
| default: |
| abort(); |
| } |
| } |
| |
| int rseq_register_current_thread(void) |
| { |
| int rc; |
| |
| if (!rseq_ownership) { |
| /* Treat libc's ownership as a successful registration. */ |
| return 0; |
| } |
| rc = sys_rseq(&__rseq_abi, sizeof(struct rseq_abi), 0, RSEQ_SIG); |
| if (rc) |
| return -1; |
| assert(rseq_current_cpu_raw() >= 0); |
| return 0; |
| } |
| |
| int rseq_unregister_current_thread(void) |
| { |
| int rc; |
| |
| if (!rseq_ownership) { |
| /* Treat libc's ownership as a successful unregistration. */ |
| return 0; |
| } |
| rc = sys_rseq(&__rseq_abi, sizeof(struct rseq_abi), RSEQ_ABI_FLAG_UNREGISTER, RSEQ_SIG); |
| if (rc) |
| return -1; |
| return 0; |
| } |
| |
| static __attribute__((constructor)) |
| void rseq_init(void) |
| { |
| libc_rseq_offset_p = dlsym(RTLD_NEXT, "__rseq_offset"); |
| libc_rseq_size_p = dlsym(RTLD_NEXT, "__rseq_size"); |
| libc_rseq_flags_p = dlsym(RTLD_NEXT, "__rseq_flags"); |
| if (libc_rseq_size_p && libc_rseq_offset_p && libc_rseq_flags_p && |
| *libc_rseq_size_p != 0) { |
| /* rseq registration owned by glibc */ |
| rseq_offset = *libc_rseq_offset_p; |
| rseq_size = *libc_rseq_size_p; |
| rseq_flags = *libc_rseq_flags_p; |
| return; |
| } |
| if (!rseq_available()) |
| return; |
| rseq_ownership = 1; |
| rseq_offset = (void *)&__rseq_abi - rseq_thread_pointer(); |
| rseq_size = sizeof(struct rseq_abi); |
| rseq_flags = 0; |
| } |
| |
| static __attribute__((destructor)) |
| void rseq_exit(void) |
| { |
| if (!rseq_ownership) |
| return; |
| rseq_offset = 0; |
| rseq_size = -1U; |
| rseq_ownership = 0; |
| } |
| |
| int32_t rseq_fallback_current_cpu(void) |
| { |
| int32_t cpu; |
| |
| cpu = sched_getcpu(); |
| if (cpu < 0) { |
| perror("sched_getcpu()"); |
| abort(); |
| } |
| return cpu; |
| } |