
Currently, only Android supports using a hard-code page size. Make this a bit more generic so any platform that wants to can use this. In addition, add a getPageSizeLogCached() function since this value is used in release.h and can avoid keeping this value around in objects. Finally, change some of the release.h page size multiplies to shifts using the new page size log value.
246 lines
7.2 KiB
C++
246 lines
7.2 KiB
C++
//===-- linux.cpp -----------------------------------------------*- 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "platform.h"
|
|
|
|
#if SCUDO_LINUX
|
|
|
|
#include "common.h"
|
|
#include "internal_defs.h"
|
|
#include "linux.h"
|
|
#include "mutex.h"
|
|
#include "report_linux.h"
|
|
#include "string_utils.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <linux/futex.h>
|
|
#include <sched.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#if SCUDO_ANDROID
|
|
#include <sys/prctl.h>
|
|
// Definitions of prctl arguments to set a vma name in Android kernels.
|
|
#define ANDROID_PR_SET_VMA 0x53564d41
|
|
#define ANDROID_PR_SET_VMA_ANON_NAME 0
|
|
#endif
|
|
|
|
namespace scudo {
|
|
|
|
#if !defined(SCUDO_PAGE_SIZE)
|
|
// This function is only used when page size is not hard-coded.
|
|
uptr getPageSize() { return static_cast<uptr>(sysconf(_SC_PAGESIZE)); }
|
|
#endif
|
|
|
|
void NORETURN die() { abort(); }
|
|
|
|
// TODO: Will be deprecated. Use the interfaces in MemMapLinux instead.
|
|
void *map(void *Addr, uptr Size, UNUSED const char *Name, uptr Flags,
|
|
UNUSED MapPlatformData *Data) {
|
|
int MmapFlags = MAP_PRIVATE | MAP_ANONYMOUS;
|
|
int MmapProt;
|
|
if (Flags & MAP_NOACCESS) {
|
|
MmapFlags |= MAP_NORESERVE;
|
|
MmapProt = PROT_NONE;
|
|
} else {
|
|
MmapProt = PROT_READ | PROT_WRITE;
|
|
}
|
|
#if defined(__aarch64__)
|
|
#ifndef PROT_MTE
|
|
#define PROT_MTE 0x20
|
|
#endif
|
|
if (Flags & MAP_MEMTAG)
|
|
MmapProt |= PROT_MTE;
|
|
#endif
|
|
if (Addr)
|
|
MmapFlags |= MAP_FIXED;
|
|
void *P = mmap(Addr, Size, MmapProt, MmapFlags, -1, 0);
|
|
if (P == MAP_FAILED) {
|
|
if (!(Flags & MAP_ALLOWNOMEM) || errno != ENOMEM)
|
|
reportMapError(errno == ENOMEM ? Size : 0);
|
|
return nullptr;
|
|
}
|
|
#if SCUDO_ANDROID
|
|
if (Name)
|
|
prctl(ANDROID_PR_SET_VMA, ANDROID_PR_SET_VMA_ANON_NAME, P, Size, Name);
|
|
#endif
|
|
return P;
|
|
}
|
|
|
|
// TODO: Will be deprecated. Use the interfaces in MemMapLinux instead.
|
|
void unmap(void *Addr, uptr Size, UNUSED uptr Flags,
|
|
UNUSED MapPlatformData *Data) {
|
|
if (munmap(Addr, Size) != 0)
|
|
reportUnmapError(reinterpret_cast<uptr>(Addr), Size);
|
|
}
|
|
|
|
// TODO: Will be deprecated. Use the interfaces in MemMapLinux instead.
|
|
void setMemoryPermission(uptr Addr, uptr Size, uptr Flags,
|
|
UNUSED MapPlatformData *Data) {
|
|
int Prot = (Flags & MAP_NOACCESS) ? PROT_NONE : (PROT_READ | PROT_WRITE);
|
|
if (mprotect(reinterpret_cast<void *>(Addr), Size, Prot) != 0)
|
|
reportProtectError(Addr, Size, Prot);
|
|
}
|
|
|
|
// TODO: Will be deprecated. Use the interfaces in MemMapLinux instead.
|
|
void releasePagesToOS(uptr BaseAddress, uptr Offset, uptr Size,
|
|
UNUSED MapPlatformData *Data) {
|
|
void *Addr = reinterpret_cast<void *>(BaseAddress + Offset);
|
|
|
|
while (madvise(Addr, Size, MADV_DONTNEED) == -1 && errno == EAGAIN) {
|
|
}
|
|
}
|
|
|
|
// Calling getenv should be fine (c)(tm) at any time.
|
|
const char *getEnv(const char *Name) { return getenv(Name); }
|
|
|
|
namespace {
|
|
enum State : u32 { Unlocked = 0, Locked = 1, Sleeping = 2 };
|
|
}
|
|
|
|
bool HybridMutex::tryLock() {
|
|
return atomic_compare_exchange_strong(&M, Unlocked, Locked,
|
|
memory_order_acquire) == Unlocked;
|
|
}
|
|
|
|
// The following is based on https://akkadia.org/drepper/futex.pdf.
|
|
void HybridMutex::lockSlow() {
|
|
u32 V = atomic_compare_exchange_strong(&M, Unlocked, Locked,
|
|
memory_order_acquire);
|
|
if (V == Unlocked)
|
|
return;
|
|
if (V != Sleeping)
|
|
V = atomic_exchange(&M, Sleeping, memory_order_acquire);
|
|
while (V != Unlocked) {
|
|
syscall(SYS_futex, reinterpret_cast<uptr>(&M), FUTEX_WAIT_PRIVATE, Sleeping,
|
|
nullptr, nullptr, 0);
|
|
V = atomic_exchange(&M, Sleeping, memory_order_acquire);
|
|
}
|
|
}
|
|
|
|
void HybridMutex::unlock() {
|
|
if (atomic_fetch_sub(&M, 1U, memory_order_release) != Locked) {
|
|
atomic_store(&M, Unlocked, memory_order_release);
|
|
syscall(SYS_futex, reinterpret_cast<uptr>(&M), FUTEX_WAKE_PRIVATE, 1,
|
|
nullptr, nullptr, 0);
|
|
}
|
|
}
|
|
|
|
void HybridMutex::assertHeldImpl() {
|
|
CHECK(atomic_load(&M, memory_order_acquire) != Unlocked);
|
|
}
|
|
|
|
u64 getMonotonicTime() {
|
|
timespec TS;
|
|
clock_gettime(CLOCK_MONOTONIC, &TS);
|
|
return static_cast<u64>(TS.tv_sec) * (1000ULL * 1000 * 1000) +
|
|
static_cast<u64>(TS.tv_nsec);
|
|
}
|
|
|
|
u64 getMonotonicTimeFast() {
|
|
#if defined(CLOCK_MONOTONIC_COARSE)
|
|
timespec TS;
|
|
clock_gettime(CLOCK_MONOTONIC_COARSE, &TS);
|
|
return static_cast<u64>(TS.tv_sec) * (1000ULL * 1000 * 1000) +
|
|
static_cast<u64>(TS.tv_nsec);
|
|
#else
|
|
return getMonotonicTime();
|
|
#endif
|
|
}
|
|
|
|
u32 getNumberOfCPUs() {
|
|
cpu_set_t CPUs;
|
|
// sched_getaffinity can fail for a variety of legitimate reasons (lack of
|
|
// CAP_SYS_NICE, syscall filtering, etc), in which case we shall return 0.
|
|
if (sched_getaffinity(0, sizeof(cpu_set_t), &CPUs) != 0)
|
|
return 0;
|
|
return static_cast<u32>(CPU_COUNT(&CPUs));
|
|
}
|
|
|
|
u32 getThreadID() {
|
|
#if SCUDO_ANDROID
|
|
return static_cast<u32>(gettid());
|
|
#else
|
|
return static_cast<u32>(syscall(SYS_gettid));
|
|
#endif
|
|
}
|
|
|
|
// Blocking is possibly unused if the getrandom block is not compiled in.
|
|
bool getRandom(void *Buffer, uptr Length, UNUSED bool Blocking) {
|
|
if (!Buffer || !Length || Length > MaxRandomLength)
|
|
return false;
|
|
ssize_t ReadBytes;
|
|
#if defined(SYS_getrandom)
|
|
#if !defined(GRND_NONBLOCK)
|
|
#define GRND_NONBLOCK 1
|
|
#endif
|
|
// Up to 256 bytes, getrandom will not be interrupted.
|
|
ReadBytes =
|
|
syscall(SYS_getrandom, Buffer, Length, Blocking ? 0 : GRND_NONBLOCK);
|
|
if (ReadBytes == static_cast<ssize_t>(Length))
|
|
return true;
|
|
#endif // defined(SYS_getrandom)
|
|
// Up to 256 bytes, a read off /dev/urandom will not be interrupted.
|
|
// Blocking is moot here, O_NONBLOCK has no effect when opening /dev/urandom.
|
|
const int FileDesc = open("/dev/urandom", O_RDONLY);
|
|
if (FileDesc == -1)
|
|
return false;
|
|
ReadBytes = read(FileDesc, Buffer, Length);
|
|
close(FileDesc);
|
|
return (ReadBytes == static_cast<ssize_t>(Length));
|
|
}
|
|
|
|
// Allocation free syslog-like API.
|
|
extern "C" WEAK int async_safe_write_log(int pri, const char *tag,
|
|
const char *msg);
|
|
|
|
void outputRaw(const char *Buffer) {
|
|
if (&async_safe_write_log) {
|
|
constexpr s32 AndroidLogInfo = 4;
|
|
constexpr uptr MaxLength = 1024U;
|
|
char LocalBuffer[MaxLength];
|
|
while (strlen(Buffer) > MaxLength) {
|
|
uptr P;
|
|
for (P = MaxLength - 1; P > 0; P--) {
|
|
if (Buffer[P] == '\n') {
|
|
memcpy(LocalBuffer, Buffer, P);
|
|
LocalBuffer[P] = '\0';
|
|
async_safe_write_log(AndroidLogInfo, "scudo", LocalBuffer);
|
|
Buffer = &Buffer[P + 1];
|
|
break;
|
|
}
|
|
}
|
|
// If no newline was found, just log the buffer.
|
|
if (P == 0)
|
|
break;
|
|
}
|
|
async_safe_write_log(AndroidLogInfo, "scudo", Buffer);
|
|
} else {
|
|
(void)write(2, Buffer, strlen(Buffer));
|
|
}
|
|
}
|
|
|
|
extern "C" WEAK void android_set_abort_message(const char *);
|
|
|
|
void setAbortMessage(const char *Message) {
|
|
if (&android_set_abort_message)
|
|
android_set_abort_message(Message);
|
|
}
|
|
|
|
} // namespace scudo
|
|
|
|
#endif // SCUDO_LINUX
|