| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| //===--- timeout linux implementation ---------------------------*- 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___SUPPORT_TIME_LINUX_MONOTONICITY_H | ||
| #define LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_MONOTONICITY_H | ||
|
|
||
| #include "hdr/time_macros.h" | ||
| #include "src/__support/libc_assert.h" | ||
| #include "src/__support/time/linux/abs_timeout.h" | ||
| #include "src/__support/time/linux/clock_conversion.h" | ||
| namespace LIBC_NAMESPACE { | ||
| namespace internal { | ||
| // This function is separated from abs_timeout. | ||
| // This function pulls in the dependency to clock_conversion.h, | ||
| // which may transitively depend on vDSO hence futex. However, this structure | ||
| // would be passed to futex, so we need to avoid cyclic dependencies. | ||
| // This function is going to be used in timed locks. Pthread generally uses | ||
| // realtime clocks for timeouts. However, due to non-monotoncity, realtime | ||
| // clocks reportedly lead to undesired behaviors. Therefore, we also provide a | ||
| // method to convert the timespec to a monotonic clock relative to the time of | ||
| // function call. | ||
| LIBC_INLINE void ensure_monotonicity(AbsTimeout &timeout) { | ||
| if (timeout.is_realtime()) { | ||
| auto res = AbsTimeout::from_timespec( | ||
| convert_clock(timeout.get_timespec(), CLOCK_REALTIME, CLOCK_MONOTONIC), | ||
| false); | ||
|
|
||
| LIBC_ASSERT(res.has_value()); | ||
| if (!res.has_value()) | ||
| __builtin_unreachable(); | ||
|
|
||
| timeout = *res; | ||
| } | ||
| } | ||
| } // namespace internal | ||
| } // namespace LIBC_NAMESPACE | ||
|
|
||
| #endif // LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_MONOTONICITY_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| add_custom_target(libc-support-time-tests) | ||
|
|
||
| if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) | ||
| add_subdirectory(${LIBC_TARGET_OS}) | ||
| endif() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| add_libc_test( | ||
| timeout_test | ||
| SUITE libc-support-time-tests | ||
| SRCS timeout_test.cpp | ||
| DEPENDS | ||
| libc.src.__support.time.linux.abs_timeout | ||
| libc.src.__support.time.linux.monotonicity | ||
| libc.src.__support.CPP.expected | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| //===-- unit tests for linux's timeout utilities --------------------------===// | ||
| // | ||
| // 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/__support/CPP/expected.h" | ||
| #include "src/__support/time/linux/abs_timeout.h" | ||
| #include "src/__support/time/linux/monotonicity.h" | ||
| #include "test/UnitTest/Test.h" | ||
|
|
||
| template <class T, class E> | ||
| using expected = LIBC_NAMESPACE::cpp::expected<T, E>; | ||
| using AbsTimeout = LIBC_NAMESPACE::internal::AbsTimeout; | ||
|
|
||
| TEST(LlvmLibcSupportLinuxTimeoutTest, NegativeSecond) { | ||
| timespec ts = {-1, 0}; | ||
| expected<AbsTimeout, AbsTimeout::Error> result = | ||
| AbsTimeout::from_timespec(ts, false); | ||
| ASSERT_FALSE(result.has_value()); | ||
| ASSERT_EQ(result.error(), AbsTimeout::Error::BeforeEpoch); | ||
| } | ||
| TEST(LlvmLibcSupportLinuxTimeoutTest, OverflowNano) { | ||
| using namespace LIBC_NAMESPACE::time_units; | ||
| timespec ts = {0, 2_s_ns}; | ||
| expected<AbsTimeout, AbsTimeout::Error> result = | ||
| AbsTimeout::from_timespec(ts, false); | ||
| ASSERT_FALSE(result.has_value()); | ||
| ASSERT_EQ(result.error(), AbsTimeout::Error::Invalid); | ||
| } | ||
| TEST(LlvmLibcSupportLinuxTimeoutTest, UnderflowNano) { | ||
| timespec ts = {0, -1}; | ||
| expected<AbsTimeout, AbsTimeout::Error> result = | ||
| AbsTimeout::from_timespec(ts, false); | ||
| ASSERT_FALSE(result.has_value()); | ||
| ASSERT_EQ(result.error(), AbsTimeout::Error::Invalid); | ||
| } | ||
| TEST(LlvmLibcSupportLinuxTimeoutTest, NoChangeIfClockIsMonotonic) { | ||
| timespec ts = {10000, 0}; | ||
| expected<AbsTimeout, AbsTimeout::Error> result = | ||
| AbsTimeout::from_timespec(ts, false); | ||
| ASSERT_TRUE(result.has_value()); | ||
| ensure_monotonicity(*result); | ||
| ASSERT_FALSE(result->is_realtime()); | ||
| ASSERT_EQ(result->get_timespec().tv_sec, static_cast<time_t>(10000)); | ||
| ASSERT_EQ(result->get_timespec().tv_nsec, static_cast<time_t>(0)); | ||
| } | ||
| TEST(LlvmLibcSupportLinuxTimeoutTest, ValidAfterConversion) { | ||
| timespec ts; | ||
| LIBC_NAMESPACE::internal::clock_gettime(CLOCK_REALTIME, &ts); | ||
| expected<AbsTimeout, AbsTimeout::Error> result = | ||
| AbsTimeout::from_timespec(ts, true); | ||
| ASSERT_TRUE(result.has_value()); | ||
| ensure_monotonicity(*result); | ||
| ASSERT_FALSE(result->is_realtime()); | ||
| ASSERT_TRUE( | ||
| AbsTimeout::from_timespec(result->get_timespec(), false).has_value()); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| //===-- protected_pages.h -------------------------------------------------===// | ||
| // | ||
| // 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| // This file provides protected pages that fault when accessing prior or past | ||
| // it. This is useful to check memory functions that must not access outside of | ||
| // the provided size limited buffer. | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LIBC_TEST_SRC_STRING_MEMORY_UTILS_PROTECTED_PAGES_H | ||
| #define LIBC_TEST_SRC_STRING_MEMORY_UTILS_PROTECTED_PAGES_H | ||
|
|
||
| #include "src/__support/macros/properties/os.h" // LIBC_TARGET_OS_IS_LINUX | ||
| #if defined(LIBC_FULL_BUILD) || !defined(LIBC_TARGET_OS_IS_LINUX) | ||
| #error "Protected pages requires mmap and cannot be used in full build mode." | ||
| #endif // defined(LIBC_FULL_BUILD) || !defined(LIBC_TARGET_OS_IS_LINUX) | ||
|
|
||
| #include "src/__support/macros/attributes.h" // LIBC_INLINE | ||
| #include <stddef.h> // size_t | ||
| #include <stdint.h> // uint8_t | ||
| #include <sys/mman.h> // mmap, munmap | ||
| #include <unistd.h> // sysconf, _SC_PAGESIZE | ||
|
|
||
| // Returns mmap page size. | ||
| LIBC_INLINE size_t GetPageSize() { | ||
| static const size_t PAGE_SIZE = sysconf(_SC_PAGESIZE); | ||
| return PAGE_SIZE; | ||
| } | ||
|
|
||
| // Represents a page of memory whose access can be configured throught the | ||
| // 'WithAccess' function. Accessing data above or below this page will trap as | ||
| // it is sandwiched between two pages with no read / write access. | ||
| struct Page { | ||
| // Returns an aligned pointer that can be accessed up to page_size. Accessing | ||
| // data at ptr[-1] will fault. | ||
| LIBC_INLINE uint8_t *bottom(size_t size) const { | ||
| if (size >= page_size) | ||
| __builtin_trap(); | ||
| return page_ptr; | ||
| } | ||
| // Returns a pointer to a buffer that can be accessed up to size. Accessing | ||
| // data at ptr[size] will trap. | ||
| LIBC_INLINE uint8_t *top(size_t size) const { | ||
| return page_ptr + page_size - size; | ||
| } | ||
|
|
||
| // protection is one of PROT_READ / PROT_WRITE. | ||
| LIBC_INLINE Page &WithAccess(int protection) { | ||
| if (mprotect(page_ptr, page_size, protection) != 0) | ||
| __builtin_trap(); | ||
| return *this; | ||
| } | ||
|
|
||
| const size_t page_size; | ||
| uint8_t *const page_ptr; | ||
| }; | ||
|
|
||
| // Allocates 5 consecutive pages that will trap if accessed. | ||
| // | page layout | access | page name | | ||
| // |-------------|--------|:---------:| | ||
| // | 0 | trap | | | ||
| // | 1 | custom | A | | ||
| // | 2 | trap | | | ||
| // | 3 | custom | B | | ||
| // | 4 | trap | | | ||
| // | ||
| // The pages A and B can be retrieved as with 'GetPageA' / 'GetPageB' and their | ||
| // accesses can be customized through the 'WithAccess' function. | ||
| struct ProtectedPages { | ||
| static constexpr size_t PAGES = 5; | ||
|
|
||
| ProtectedPages() | ||
| : page_size(GetPageSize()), | ||
| ptr(mmap(/*address*/ nullptr, /*length*/ PAGES * page_size, | ||
| /*protection*/ PROT_NONE, | ||
| /*flags*/ MAP_PRIVATE | MAP_ANONYMOUS, /*fd*/ -1, | ||
| /*offset*/ 0)) { | ||
| if (reinterpret_cast<intptr_t>(ptr) == -1) | ||
| __builtin_trap(); | ||
| } | ||
| ~ProtectedPages() { munmap(ptr, PAGES * page_size); } | ||
|
|
||
| LIBC_INLINE Page GetPageA() const { return Page{page_size, page<1>()}; } | ||
| LIBC_INLINE Page GetPageB() const { return Page{page_size, page<3>()}; } | ||
|
|
||
| private: | ||
| template <size_t index> LIBC_INLINE uint8_t *page() const { | ||
| static_assert(index < PAGES); | ||
| return static_cast<uint8_t *>(ptr) + (index * page_size); | ||
| } | ||
|
|
||
| const size_t page_size; | ||
| void *const ptr = nullptr; | ||
| }; | ||
|
|
||
| #endif // LIBC_TEST_SRC_STRING_MEMORY_UTILS_PROTECTED_PAGES_H |