123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- // Copyright 2017 The Abseil Authors.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // https://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- // Allow dynamic symbol lookup in the kernel VDSO page.
- //
- // VDSOSupport -- a class representing kernel VDSO (if present).
- #include "y_absl/debugging/internal/vdso_support.h"
- #ifdef Y_ABSL_HAVE_VDSO_SUPPORT // defined in vdso_support.h
- #if !defined(__has_include)
- #define __has_include(header) 0
- #endif
- #include <errno.h>
- #include <fcntl.h>
- #if __has_include(<syscall.h>)
- #include <syscall.h>
- #elif __has_include(<sys/syscall.h>)
- #include <sys/syscall.h>
- #endif
- #include <unistd.h>
- #if !defined(__UCLIBC__) && defined(__GLIBC__) && \
- (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 16))
- #define Y_ABSL_HAVE_GETAUXVAL
- #endif
- #ifdef Y_ABSL_HAVE_GETAUXVAL
- #include <sys/auxv.h>
- #endif
- #include "y_absl/base/dynamic_annotations.h"
- #include "y_absl/base/internal/raw_logging.h"
- #include "y_absl/base/port.h"
- #ifndef AT_SYSINFO_EHDR
- #define AT_SYSINFO_EHDR 33 // for crosstoolv10
- #endif
- #if defined(__NetBSD__)
- using Elf32_auxv_t = Aux32Info;
- using Elf64_auxv_t = Aux64Info;
- #endif
- #if defined(__FreeBSD__)
- #if defined(__ELF_WORD_SIZE) && __ELF_WORD_SIZE == 64
- using Elf64_auxv_t = Elf64_Auxinfo;
- #endif
- using Elf32_auxv_t = Elf32_Auxinfo;
- #endif
- namespace y_absl {
- Y_ABSL_NAMESPACE_BEGIN
- namespace debugging_internal {
- Y_ABSL_CONST_INIT
- std::atomic<const void *> VDSOSupport::vdso_base_(
- debugging_internal::ElfMemImage::kInvalidBase);
- Y_ABSL_CONST_INIT std::atomic<VDSOSupport::GetCpuFn> VDSOSupport::getcpu_fn_(
- &InitAndGetCPU);
- VDSOSupport::VDSOSupport()
- // If vdso_base_ is still set to kInvalidBase, we got here
- // before VDSOSupport::Init has been called. Call it now.
- : image_(vdso_base_.load(std::memory_order_relaxed) ==
- debugging_internal::ElfMemImage::kInvalidBase
- ? Init()
- : vdso_base_.load(std::memory_order_relaxed)) {}
- // NOTE: we can't use GoogleOnceInit() below, because we can be
- // called by tcmalloc, and none of the *once* stuff may be functional yet.
- //
- // In addition, we hope that the VDSOSupportHelper constructor
- // causes this code to run before there are any threads, and before
- // InitGoogle() has executed any chroot or setuid calls.
- //
- // Finally, even if there is a race here, it is harmless, because
- // the operation should be idempotent.
- const void *VDSOSupport::Init() {
- const auto kInvalidBase = debugging_internal::ElfMemImage::kInvalidBase;
- #ifdef Y_ABSL_HAVE_GETAUXVAL
- if (vdso_base_.load(std::memory_order_relaxed) == kInvalidBase) {
- errno = 0;
- const void *const sysinfo_ehdr =
- reinterpret_cast<const void *>(getauxval(AT_SYSINFO_EHDR));
- if (errno == 0) {
- vdso_base_.store(sysinfo_ehdr, std::memory_order_relaxed);
- }
- }
- #endif // Y_ABSL_HAVE_GETAUXVAL
- if (vdso_base_.load(std::memory_order_relaxed) == kInvalidBase) {
- int fd = open("/proc/self/auxv", O_RDONLY);
- if (fd == -1) {
- // Kernel too old to have a VDSO.
- vdso_base_.store(nullptr, std::memory_order_relaxed);
- getcpu_fn_.store(&GetCPUViaSyscall, std::memory_order_relaxed);
- return nullptr;
- }
- ElfW(auxv_t) aux;
- while (read(fd, &aux, sizeof(aux)) == sizeof(aux)) {
- if (aux.a_type == AT_SYSINFO_EHDR) {
- #if defined(__NetBSD__)
- vdso_base_.store(reinterpret_cast<void *>(aux.a_v),
- std::memory_order_relaxed);
- #else
- vdso_base_.store(reinterpret_cast<void *>(aux.a_un.a_val),
- std::memory_order_relaxed);
- #endif
- break;
- }
- }
- close(fd);
- if (vdso_base_.load(std::memory_order_relaxed) == kInvalidBase) {
- // Didn't find AT_SYSINFO_EHDR in auxv[].
- vdso_base_.store(nullptr, std::memory_order_relaxed);
- }
- }
- GetCpuFn fn = &GetCPUViaSyscall; // default if VDSO not present.
- if (vdso_base_.load(std::memory_order_relaxed)) {
- VDSOSupport vdso;
- SymbolInfo info;
- if (vdso.LookupSymbol("__vdso_getcpu", "LINUX_2.6", STT_FUNC, &info)) {
- fn = reinterpret_cast<GetCpuFn>(const_cast<void *>(info.address));
- }
- }
- // Subtle: this code runs outside of any locks; prevent compiler
- // from assigning to getcpu_fn_ more than once.
- getcpu_fn_.store(fn, std::memory_order_relaxed);
- return vdso_base_.load(std::memory_order_relaxed);
- }
- const void *VDSOSupport::SetBase(const void *base) {
- Y_ABSL_RAW_CHECK(base != debugging_internal::ElfMemImage::kInvalidBase,
- "internal error");
- const void *old_base = vdso_base_.load(std::memory_order_relaxed);
- vdso_base_.store(base, std::memory_order_relaxed);
- image_.Init(base);
- // Also reset getcpu_fn_, so GetCPU could be tested with simulated VDSO.
- getcpu_fn_.store(&InitAndGetCPU, std::memory_order_relaxed);
- return old_base;
- }
- bool VDSOSupport::LookupSymbol(const char *name,
- const char *version,
- int type,
- SymbolInfo *info) const {
- return image_.LookupSymbol(name, version, type, info);
- }
- bool VDSOSupport::LookupSymbolByAddress(const void *address,
- SymbolInfo *info_out) const {
- return image_.LookupSymbolByAddress(address, info_out);
- }
- // NOLINT on 'long' because this routine mimics kernel api.
- long VDSOSupport::GetCPUViaSyscall(unsigned *cpu, // NOLINT(runtime/int)
- void *, void *) {
- #ifdef SYS_getcpu
- return syscall(SYS_getcpu, cpu, nullptr, nullptr);
- #else
- // x86_64 never implemented sys_getcpu(), except as a VDSO call.
- static_cast<void>(cpu); // Avoid an unused argument compiler warning.
- errno = ENOSYS;
- return -1;
- #endif
- }
- // Use fast __vdso_getcpu if available.
- long VDSOSupport::InitAndGetCPU(unsigned *cpu, // NOLINT(runtime/int)
- void *x, void *y) {
- Init();
- GetCpuFn fn = getcpu_fn_.load(std::memory_order_relaxed);
- Y_ABSL_RAW_CHECK(fn != &InitAndGetCPU, "Init() did not set getcpu_fn_");
- return (*fn)(cpu, x, y);
- }
- // This function must be very fast, and may be called from very
- // low level (e.g. tcmalloc). Hence I avoid things like
- // GoogleOnceInit() and ::operator new.
- Y_ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY
- int GetCPU() {
- unsigned cpu;
- long ret_code = // NOLINT(runtime/int)
- (*VDSOSupport::getcpu_fn_)(&cpu, nullptr, nullptr);
- return ret_code == 0 ? static_cast<int>(cpu) : static_cast<int>(ret_code);
- }
- } // namespace debugging_internal
- Y_ABSL_NAMESPACE_END
- } // namespace y_absl
- #endif // Y_ABSL_HAVE_VDSO_SUPPORT
|