include/DetourModKit/address.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef DETOURMODKIT_ADDRESS_HPP | ||
| 2 | #define DETOURMODKIT_ADDRESS_HPP | ||
| 3 | |||
| 4 | /** | ||
| 5 | * @file address.hpp | ||
| 6 | * @brief The Address value type, the single addressing vocabulary of the public surface. | ||
| 7 | * @details Only `rip()` reads process memory. Only the pointer constructor, `as<T>()`, `ptr<T>()`, and `rip()` cast | ||
| 8 | * between an integer and a pointer. | ||
| 9 | */ | ||
| 10 | |||
| 11 | #include "DetourModKit/defines.hpp" | ||
| 12 | |||
| 13 | #include <compare> | ||
| 14 | #include <cstddef> | ||
| 15 | #include <cstdint> | ||
| 16 | #include <cstring> | ||
| 17 | #include <type_traits> | ||
| 18 | |||
| 19 | namespace DetourModKit | ||
| 20 | { | ||
| 21 | /** | ||
| 22 | * @brief A strongly-typed machine address with constexpr arithmetic and an explicit cast surface. | ||
| 23 | * @details Address is trivially copyable and exactly one machine pointer in size and alignment. | ||
| 24 | */ | ||
| 25 | class Address | ||
| 26 | { | ||
| 27 | 208 | std::uintptr_t m_value{0}; | |
| 28 | |||
| 29 | public: | ||
| 30 | /// Constructs a null address (numeric value zero). | ||
| 31 | 3017 | constexpr Address() noexcept = default; | |
| 32 | |||
| 33 | /** @brief Constructs from a raw numeric address. */ | ||
| 34 | 26718 | constexpr explicit Address(std::uintptr_t value) noexcept : m_value{value} {} | |
| 35 | |||
| 36 | /** @brief Constructs a null address from `nullptr`. */ | ||
| 37 | 7 | constexpr Address(std::nullptr_t) noexcept : m_value{0} {} | |
| 38 | |||
| 39 | /** @brief Constructs from any object or function pointer. */ | ||
| 40 | 222450 | template <class T> explicit Address(T *pointer) noexcept : m_value{reinterpret_cast<std::uintptr_t>(pointer)} {} | |
| 41 | |||
| 42 | /// Returns the underlying numeric address. | ||
| 43 | 263349 | [[nodiscard]] constexpr std::uintptr_t raw() const noexcept { return m_value; } | |
| 44 | |||
| 45 | /// Tests whether the address is non-null. | ||
| 46 | 6990 | [[nodiscard]] constexpr explicit operator bool() const noexcept { return m_value != 0; } | |
| 47 | |||
| 48 | /** | ||
| 49 | * @brief Returns this address advanced by a signed byte delta. | ||
| 50 | * @details The arithmetic wraps modulo 2^64, so a negative delta moves backward. A null base is also valid. | ||
| 51 | */ | ||
| 52 | 3955 | [[nodiscard]] constexpr Address offset(std::ptrdiff_t delta) const noexcept | |
| 53 | { | ||
| 54 | 3955 | return Address{m_value + static_cast<std::uintptr_t>(delta)}; | |
| 55 | } | ||
| 56 | |||
| 57 | /** | ||
| 58 | * @brief Returns this address rounded up to a multiple of @p alignment. | ||
| 59 | * @param alignment A power of two in bytes. Any other value yields a meaningless result, not a diagnostic. | ||
| 60 | * @details An address within `alignment - 1` bytes of the address-space top wraps modulo 2^64, so the result | ||
| 61 | * is then numerically below this address. | ||
| 62 | */ | ||
| 63 | 3 | [[nodiscard]] constexpr Address align_up(std::size_t alignment) const noexcept | |
| 64 | { | ||
| 65 | 3 | const std::uintptr_t mask = static_cast<std::uintptr_t>(alignment) - 1U; | |
| 66 | 3 | return Address{(m_value + mask) & ~mask}; | |
| 67 | } | ||
| 68 | |||
| 69 | /** | ||
| 70 | * @brief Resolves a RIP-relative reference whose displacement is embedded in the instruction at this address. | ||
| 71 | * @param displacement_at Byte offset from this address to the signed 4-byte displacement field. | ||
| 72 | * @param instruction_length Total length of the instruction in bytes. | ||
| 73 | * @return The absolute target: (this + instruction_length) + sign-extended disp32. | ||
| 74 | * @details The read is unchecked. Use it only for a located and validated instruction whose bytes are mapped. | ||
| 75 | * For a guarded read, use scan::resolve_rip_relative(). | ||
| 76 | */ | ||
| 77 | 1 | [[nodiscard]] Address rip(std::ptrdiff_t displacement_at, std::size_t instruction_length) const noexcept | |
| 78 | { | ||
| 79 | // The disp32 field is rarely 4-byte aligned, so memcpy is the defined unaligned load. | ||
| 80 | 1 | std::int32_t displacement = 0; | |
| 81 | 1 | std::memcpy( | |
| 82 | &displacement, | ||
| 83 | 1 | reinterpret_cast<const void *>(m_value + static_cast<std::uintptr_t>(displacement_at)), | |
| 84 | sizeof(displacement) | ||
| 85 | ); | ||
| 86 | 1 | const std::uintptr_t next_instruction = m_value + static_cast<std::uintptr_t>(instruction_length); | |
| 87 | 1 | return Address{next_instruction + static_cast<std::uintptr_t>(static_cast<std::intptr_t>(displacement))}; | |
| 88 | } | ||
| 89 | |||
| 90 | /** | ||
| 91 | * @brief Reinterprets the address as a value of type @p T. | ||
| 92 | * @tparam T A pointer type, or an integral type at least as wide as `std::uintptr_t`. For a narrower integer, | ||
| 93 | * use @ref raw(). For a typed view of the addressed bytes, use @ref ptr(). | ||
| 94 | * @note Callback-safe: a pure cast, with no allocation, lock, or I/O. | ||
| 95 | */ | ||
| 96 | template <class T> | ||
| 97 | requires(std::is_pointer_v<T> || (std::is_integral_v<T> && sizeof(T) >= sizeof(std::uintptr_t))) | ||
| 98 | 2382 | [[nodiscard]] T as() const noexcept | |
| 99 | { | ||
| 100 | if constexpr (std::is_integral_v<T>) | ||
| 101 | { | ||
| 102 | 5 | return static_cast<T>(m_value); | |
| 103 | } | ||
| 104 | else | ||
| 105 | { | ||
| 106 | 2377 | return reinterpret_cast<T>(m_value); | |
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | /// Reinterprets the address as a `T*`, the shorthand for `as<T*>()`. | ||
| 111 | 291 | template <class T> [[nodiscard]] T *ptr() const noexcept { return reinterpret_cast<T *>(m_value); } | |
| 112 | |||
| 113 | /// Orders and compares addresses by their numeric value. | ||
| 114 | 45 | [[nodiscard]] constexpr auto operator<=>(const Address &) const noexcept = default; | |
| 115 | 170 | [[nodiscard]] constexpr bool operator==(const Address &) const noexcept = default; | |
| 116 | }; | ||
| 117 | |||
| 118 | static_assert( | ||
| 119 | sizeof(Address) == sizeof(void *) && alignof(Address) == alignof(void *), | ||
| 120 | "Address must be exactly a machine pointer in size and alignment." | ||
| 121 | ); | ||
| 122 | static_assert(std::is_trivially_copyable_v<Address>, "Address must stay trivially copyable."); | ||
| 123 | |||
| 124 | } // namespace DetourModKit | ||
| 125 | |||
| 126 | #endif // DETOURMODKIT_ADDRESS_HPP | ||
| 127 |