diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 0000000..c9615f0 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,41 @@ +name: Benchmark + +on: workflow_dispatch + +jobs: + windows: + strategy: + matrix: + toolset: [ v145, ClangCL ] + runs-on: windows-11-vs2026-arm + steps: + - uses: actions/checkout@v6 + + - name: CPM Cache + uses: actions/cache@v5 + with: + path: ${{github.workspace}}/.cpmcache + key: cpm-${{runner.os}}-ARM64-${{hashFiles('test/CMakeLists.txt')}} + restore-keys: | + cpm-${{runner.os}}-ARM64- + + - name: Check Hardware + run: (Get-CimInstance Win32_Processor).Name + + - name: Locate Visual Studio + run: | + $vsInstall = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath + echo "VS_INSTALL_DIR=$vsInstall" >> $env:GITHUB_ENV + + - name: Configure + run: cmake -B ${{github.workspace}}/build -DCMAKE_CXX_STANDARD=23 -DLIBHAT_SHARED_C_LIB=ON -DLIBHAT_TESTING_SDE=OFF -A ARM64 -T ${{matrix.toolset}} + + - name: Build + run: cmake --build ${{github.workspace}}/build -j 4 --config Release --target libhat_benchmark_compare_impl libhat_benchmark_chromium + + - name: Test + working-directory: ${{github.workspace}}/build + shell: cmd + run: | + call "${{env.VS_INSTALL_DIR}}\VC\Auxiliary\Build\vcvarsarm64.bat" + ctest --verbose -C Release -R "(libhat_benchmark_compare_impl|libhat_benchmark_chromium)" diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 8d39a37..78fc536 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -18,20 +18,38 @@ jobs: steps: - uses: actions/checkout@v6 + - name: CPM Cache + uses: actions/cache@v5 + with: + path: ${{github.workspace}}/.cpmcache + key: cpm-${{runner.os}}-${{hashFiles('test/CMakeLists.txt')}} + restore-keys: | + cpm-${{runner.os}}- + - name: Configure - run: cmake -B ${{github.workspace}}/build -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBHAT_TESTING=OFF + run: cmake -B ${{github.workspace}}/build -DCPM_SOURCE_CACHE=${{github.workspace}}/.cpmcache -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBHAT_TESTING_SAMPLE_BIN=OFF -DLIBHAT_TESTING_SDE=OFF - name: Build run: cmake --build ${{github.workspace}}/build -j 4 + - name: Test + working-directory: ${{github.workspace}}/build + run: ctest --verbose -C ${{env.BUILD_TYPE}} -R libhat_test_.* + linux: strategy: matrix: + target: [ x64, ARM64 ] + cxx_standard: [ 20, 23 ] compiler: - { pkg: g++, exe: g++, version: 14 } - { pkg: clang, exe: clang++, version: 18 } - cxx_standard: [ 20, 23 ] - runs-on: ubuntu-24.04 + include: + - target: x64 + os: ubuntu-26.04 + - target: ARM64 + os: ubuntu-26.04-arm + runs-on: ${{matrix.os}} steps: - uses: actions/checkout@v6 @@ -51,7 +69,7 @@ jobs: - name: Configure env: CXX: ${{matrix.compiler.exe}}-${{matrix.compiler.version}} - run: cmake -B ${{github.workspace}}/build -DCPM_SOURCE_CACHE=${{github.workspace}}/.cpmcache -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBHAT_TESTING=ON -DLIBHAT_TESTING_SAMPLE_BIN=OFF -DLIBHAT_TESTING_SANITIZE=OFF + run: cmake -B ${{github.workspace}}/build -DCPM_SOURCE_CACHE=${{github.workspace}}/.cpmcache -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBHAT_TESTING_SDE=${{startsWith(matrix.target, 'ARM') && 'OFF' || 'ON'}} -DLIBHAT_TESTING_SAMPLE_BIN=OFF -DLIBHAT_TESTING_SANITIZE=OFF - name: Build run: cmake --build ${{github.workspace}}/build -j 4 @@ -60,6 +78,57 @@ jobs: working-directory: ${{github.workspace}}/build run: ctest --verbose -C ${{env.BUILD_TYPE}} -R libhat_test_.* + android: + strategy: + matrix: + cxx_standard: [ 20 ] + abi: [ armeabi-v7a, arm64-v8a, x86, x86_64 ] + sdk: [ 24 ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: AVD Cache + if: ${{!startsWith(matrix.abi, 'arm')}} + uses: actions/cache@v5 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{matrix.sdk}}-${{matrix.abi}} + + - name: Configure + run: cmake -B ${{github.workspace}}/build -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake -DANDROID_ABI=${{matrix.abi}} -DANDROID_PLATFORM=${{matrix.sdk}} -DCPM_SOURCE_CACHE=${{github.workspace}}/.cpmcache -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBHAT_TESTING_SAMPLE_BIN=OFF -DLIBHAT_TESTING_SDE=OFF -DLIBHAT_TESTING_SANITIZE=OFF + + - name: Build + run: cmake --build ${{github.workspace}}/build -j 4 + + - name: Create AVD and generate snapshot for caching + if: ${{!startsWith(matrix.abi, 'arm') && steps.avd-cache.outputs.cache-hit != 'true'}} + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{matrix.sdk}} + arch: ${{matrix.abi}} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + script: echo "Generated AVD snapshot for caching." + + - name: Test + if: ${{!startsWith(matrix.abi, 'arm')}} + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{matrix.sdk}} + arch: ${{matrix.abi}} + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: | + adb push ${{github.workspace}}/build/test /data/local/tmp/ + adb shell chmod +x /data/local/tmp/test/libhat_test_process + adb shell /data/local/tmp/test/libhat_test_process + windows: strategy: matrix: diff --git a/CMakeLists.txt b/CMakeLists.txt index 2421079..ad16d32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ option(LIBHAT_USE_STD_MODULE "Compile the module target using the std module" OF option(LIBHAT_EXAMPLES "Include example targets" ${PROJECT_IS_TOP_LEVEL}) option(LIBHAT_HINT_X86_64 "Enables support for the x86_64 scan hint, requires a small (2KB) data table" ON) +option(LIBHAT_HINT_AARCH64 "Enables support for the aarch64 scan hint, requires a small (2KB) data table" ON) if(LIBHAT_TESTING AND LIBHAT_TESTING_SANITIZE) if(MSVC) @@ -42,6 +43,8 @@ set(LIBHAT_SRC src/Scanner.cpp src/System.cpp + src/os/mac/Process.cpp + src/os/linux/MemoryProtector.cpp src/os/linux/Process.cpp @@ -57,7 +60,9 @@ set(LIBHAT_SRC src/arch/x86/AVX512.cpp src/arch/x86/System.cpp - src/arch/arm/System.cpp) + src/arch/arm/Neon.cpp + src/arch/arm/System.cpp +) add_library(libhat STATIC ${LIBHAT_SRC}) add_library(libhat::libhat ALIAS libhat) @@ -75,10 +80,7 @@ if (MSVC) target_compile_options(libhat PRIVATE /W3 /WX) endif() elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - target_compile_options(libhat PRIVATE -Wall -Wextra -Wconversion -Werror - # temp fix for macOS CI failing due to incorrect LIBHAT_COMPILER_X86_OPTIONS value - -Wno-unused-command-line-argument - ) + target_compile_options(libhat PRIVATE -Wall -Wextra -Wconversion -Werror) endif() target_include_directories(libhat PUBLIC @@ -90,6 +92,7 @@ target_compile_definitions(libhat PUBLIC "$<$:LIBHAT_DISABLE_SSE>" "$<$:LIBHAT_DISABLE_AVX512>" "$<$:LIBHAT_HINT_X86_64>" + "$<$:LIBHAT_HINT_AARCH64>" ) if(LIBHAT_STATIC_C_LIB OR LIBHAT_SHARED_C_LIB) diff --git a/README.md b/README.md index b5964ba..a330894 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,15 @@ A modern, high-performance library for C++20 designed around game hacking ## Feature overview -- Windows x86/x64 support -- Partial Linux and macOS support - Vectorized scanning for byte patterns - SSE 4.1 and AVX2 on x86/x64 - AVX-512 on x64 + - Neon on ARM/ARM64 - RAII memory protector - Convenience wrappers over OS APIs - Language bindings (C, C#, Java) +- Full Windows support +- Partial (WIP) Linux, macOS, and Android support ## Versioning This project adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html). Any declaration that @@ -100,19 +101,21 @@ BM_Throughput_UC2/256MiB 261157833 ns 261160714 ns Below is a summary of the current support for libhat's platform-dependent APIs: -| | Windows | Linux | macOS | -|--------------------------------|:-------:|:-----:|:-----:| -| `hat::get_system` | ✅ | ✅ | ✅ | -| `hat::memory_protector` | ✅ | ✅ | | -| `hp::get_process_module` | ✅ | ✅ | | -| `hp::get_module` | ✅ | ✅ | | -| `hp::module_at` | ✅ | | | -| `hp::is_readable` | ✅ | ✅ | | -| `hp::is_writable` | ✅ | ✅ | | -| `hp::is_executable` | ✅ | ✅ | | -| `hp::module::get_module_data` | ✅ | ✅ | | -| `hp::module::get_section_data` | ✅ | | | -| `hp::module::for_each_segment` | ✅ | ✅ | | +### APIs + +| | Windows | Linux | macOS | Android | +|--------------------------------|:-------:|:-----:|:-----:|:-------:| +| `hat::get_system` | ✅ | ✅ | ✅ | ✅ | +| `hat::memory_protector` | ✅ | ✅ | | ✅ | +| `hp::get_process_module` | ✅ | ✅ | ✅ | ✅ | +| `hp::get_module` | ✅ | ✅ | ✅ | ✅ | +| `hp::module_at` | ✅ | | | | +| `hp::is_readable` | ✅ | ✅ | | ✅ | +| `hp::is_writable` | ✅ | ✅ | | ✅ | +| `hp::is_executable` | ✅ | ✅ | | ✅ | +| `hp::module::get_module_data` | ✅ | ✅ | | ✅ | +| `hp::module::get_section_data` | ✅ | | | | +| `hp::module::for_each_segment` | ✅ | ✅ | ✅ | ✅ | ## Quick start ### Defining patterns @@ -189,19 +192,24 @@ const std::byte* address = result.get(); const std::byte* relative_address = result.rel(3); ``` -libhat has a few optimizations for searching for patterns in `x86_64` machine code: +libhat has a few optimizations for searching for patterns in `x86_64` and `AArch64` machine code: ```cpp #include -// If a byte pattern matches at the start of a function, the result will be aligned on 16-bytes. -// This can be indicated via the defaulted `alignment` parameter (all overloads have this parameter): +// Compilers will often align the start address of a function on 16-bytes. Scanning for patterns that +// match the start of a function can take advantage of this by specifying the defaulted `alignment` +// parameter (all overloads have this parameter): std::span range = /* ... */; hat::signature_view pattern = /* ... */; hat::scan_result result = hat::find_pattern(range, pattern, hat::scan_alignment::X16); -// Additionally, x86_64 contains a non-uniform distribution of byte pairs. By passing the `x86_64` -// scan hint, the search can be based on the least common byte pair that is found in the pattern. -hat::scan_result result = hat::find_pattern(range, pattern, hat::scan_alignment::X1, hat::scan_hint::x86_64); +// Or, if the architecture has byte-aligned instructions (such as ARM and AArch64): +hat::scan_result result = hat::find_pattern(range, pattern, hat::scan_alignment::X4); + +// Additionally, machine code contains a non-uniform distribution of bytes. By passing the respective +// scan hint (either `x86_64` or `aarch64`), the search anchor can be tuned to the least frequent +// bytes that are present in the pattern. +hat::scan_result result = hat::find_pattern(range, pattern, hat::scan_alignment::X1, hat::scan_hint::x86_64); ``` ### Accessing members diff --git a/bindings/java/src/main/java/me/zero/libhat/Hat.java b/bindings/java/src/main/java/me/zero/libhat/Hat.java index a3a5d5a..bfedafb 100644 --- a/bindings/java/src/main/java/me/zero/libhat/Hat.java +++ b/bindings/java/src/main/java/me/zero/libhat/Hat.java @@ -127,7 +127,7 @@ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNu Objects.requireNonNull(signature.handle), new Pointer(start), count, - alignment.ordinal() + alignment.alignment() ); if (result == Pointer.NULL) { @@ -175,7 +175,7 @@ public static Optional findPattern(@NotNull final Signature signature, Objects.requireNonNull(signature.handle), module.handle, section, - alignment.ordinal() + alignment.alignment() ); return Optional.ofNullable(result); diff --git a/bindings/java/src/main/java/me/zero/libhat/ScanAlignment.java b/bindings/java/src/main/java/me/zero/libhat/ScanAlignment.java index b79547d..a2f7863 100644 --- a/bindings/java/src/main/java/me/zero/libhat/ScanAlignment.java +++ b/bindings/java/src/main/java/me/zero/libhat/ScanAlignment.java @@ -7,10 +7,28 @@ public enum ScanAlignment { /** * No byte alignment */ - X1, + X1(1), + + /** + * 4 byte alignment + */ + X4(4), /** * 16 byte alignment */ - X16 + X16(16); + + private final int alignment; + + ScanAlignment(final int alignment) { + this.alignment = alignment; + } + + /** + * @return The scan result alignment requirement, in bytes + */ + public final int alignment() { + return this.alignment; + } } diff --git a/include/libhat/c/libhat.h b/include/libhat/c/libhat.h index 6503c8b..bcaa44c 100644 --- a/include/libhat/c/libhat.h +++ b/include/libhat/c/libhat.h @@ -37,8 +37,9 @@ typedef enum libhat_status_t { } libhat_status_t; typedef enum scan_alignment { - scan_alignment_x1, - scan_alignment_x16, + scan_alignment_x1 = 1, + scan_alignment_x4 = 4, + scan_alignment_x16 = 16, } scan_alignment_t; typedef struct signature { diff --git a/include/libhat/platform.h b/include/libhat/platform.h index a4434b3..6c09272 100644 --- a/include/libhat/platform.h +++ b/include/libhat/platform.h @@ -13,12 +13,21 @@ #error Unsupported Architecture #endif +#if defined(__LP64__) || defined(_M_X64) + #define LIBHAT_LP64 +#else + #define LIBHAT_LP32 +#endif + // Detect Operating System #if defined(_WIN32) #define LIBHAT_WINDOWS #elif defined(linux) || defined(__linux__) || defined(__linux) #define LIBHAT_UNIX #define LIBHAT_LINUX + #if defined(__ANDROID__) + #define LIBHAT_ANDROID + #endif #elif defined(__APPLE__) && defined(__MACH__) #define LIBHAT_UNIX #define LIBHAT_MAC diff --git a/include/libhat/scanner.hpp b/include/libhat/scanner.hpp index 6350130..21b87da 100644 --- a/include/libhat/scanner.hpp +++ b/include/libhat/scanner.hpp @@ -103,13 +103,15 @@ LIBHAT_EXPORT namespace hat { enum class scan_alignment : uint8_t { X1 = 1, + X4 = 4, X16 = 16 }; enum class scan_hint : uint64_t { - none = 0, // no hints - x86_64 = 1 << 0, // The data being scanned is x86_64 machine code - pair0 = 1 << 1, // Only utilize byte pair based scanning if the signature starts with a byte pair + none = 0, // no hints + x86_64 = 1 << 0, // The data being scanned is x86_64 machine code + pair0 = 1 << 1, // Only utilize byte pair based scanning if the signature starts with a byte pair + aarch64 = 1 << 2, // The data being scanned is AArch64 machine code }; constexpr scan_hint operator|(scan_hint lhs, scan_hint rhs) { @@ -149,6 +151,7 @@ namespace hat::detail { SSE, // x86/x64 SSE 4.1 AVX2, // x86/x64 AVX2 AVX512, // x64 AVX512 + Neon, // ARMv7+ Neon }; class scan_context { @@ -249,8 +252,31 @@ namespace hat::detail { template<> scan_function_t resolve_scanner(scan_context&); - template - const_scan_result find_pattern_single(const std::byte* begin, const std::byte* end, const scan_context&); + template + const_scan_result find_pattern_single(const std::byte* begin, const std::byte* end, const scan_context& context) { + static constexpr auto stride = alignment_stride; + const auto signature = context.signature; + const auto cmpByte = *signature[context.cmpIndex]; + + const auto scanBegin = align_up(begin) + context.cmpIndex; + const auto scanEnd = align_up(end - signature.size() + 1) + context.cmpIndex; + + if (scanBegin >= scanEnd) { + return nullptr; + } + + for (auto i = scanBegin; i != scanEnd; i += stride) { + if (*i == cmpByte) { + const auto start = i - context.cmpIndex; + const auto match = std::equal(signature.begin(), signature.end(), start); + if (match) LIBHAT_UNLIKELY { + return start; + } + } + } + + return nullptr; + } template<> constexpr const_scan_result find_pattern_single(const std::byte* begin, const std::byte* end, const scan_context& context) { @@ -285,36 +311,11 @@ namespace hat::detail { return nullptr; } - template<> - inline const_scan_result find_pattern_single(const std::byte* begin, const std::byte* end, const scan_context& context) { - static constexpr auto stride = alignment_stride; - const auto signature = context.signature; - const auto cmpByte = *signature[context.cmpIndex]; - - const auto scanBegin = align_up(begin) + context.cmpIndex; - const auto scanEnd = align_up(end - signature.size() + 1) + context.cmpIndex; - - if (scanBegin >= scanEnd) { - return nullptr; - } - - for (auto i = scanBegin; i != scanEnd; i += stride) { - if (*i == cmpByte) { - const auto start = i - context.cmpIndex; - const auto match = std::equal(signature.begin(), signature.end(), start); - if (match) LIBHAT_UNLIKELY { - return start; - } - } - } - - return nullptr; - } - template<> constexpr scan_function_t resolve_scanner(scan_context& context) { switch (context.alignment) { case scan_alignment::X1: return &find_pattern_single; + case scan_alignment::X4: return &find_pattern_single; case scan_alignment::X16: return &find_pattern_single; } LIBHAT_UNREACHABLE(); diff --git a/include/libhat/system.hpp b/include/libhat/system.hpp index 80c14e8..8a8a4e5 100644 --- a/include/libhat/system.hpp +++ b/include/libhat/system.hpp @@ -23,42 +23,120 @@ LIBHAT_EXPORT namespace hat { #if defined(LIBHAT_X86) || defined(LIBHAT_X86_64) LIBHAT_EXPORT namespace hat { + struct cpu_extensions { + bool sse; + bool sse2; + bool sse3; + bool ssse3; + bool sse41; + bool sse42; + bool avx; + bool avx2; + bool avx512f; + bool avx512bw; + bool popcnt; + bool bmi; + }; + struct system_info_x86 : hat::system_info { std::string cpu_vendor{}; std::string cpu_brand{}; - struct { - bool sse; - bool sse2; - bool sse3; - bool ssse3; - bool sse41; - bool sse42; - bool avx; - bool avx2; - bool avx512f; - bool avx512bw; - bool popcnt; - bool bmi; - } extensions{}; + cpu_extensions extensions{}; private: system_info_x86(); friend const system_info_x86& get_system(); static const system_info_x86 instance; }; + inline constexpr cpu_extensions compiled_extensions{ +#if defined(__SSE__) || _M_IX86_FP >= 1 + .sse = true, +#else + .sse = false, +#endif +#if defined(__SSE2__) || _M_IX86_FP >= 2 + .sse2 = true, +#else + .sse2 = false, +#endif +#if defined(__SSE3__) + .sse3 = true, +#else + .sse3 = false, +#endif +#if defined(__SSSE3__) + .ssse3 = true, +#else + .ssse3 = false, +#endif +#if defined(__SSE4_1__) + .sse41 = true, +#else + .sse41 = false, +#endif +#if defined(__SSE4_2__) + .sse42 = true, +#else + .sse42 = false, +#endif +#if defined(__AVX__) + .avx = true, +#else + .avx = false, +#endif +#if defined(__AVX2__) + .avx2 = true, +#else + .avx2 = false, +#endif +#if defined(__AVX512F__) + .avx512f = true, +#else + .avx512f = false, +#endif +#if defined(__AVX512BW__) + .avx512bw = true, +#else + .avx512bw = false, +#endif +#if defined(__POPCNT__) + .popcnt = true, +#else + .popcnt = false, +#endif +#if defined(__BMI__) + .bmi = true, +#else + .bmi = false, +#endif + }; + using system_info_impl = system_info_x86; } #elif defined(LIBHAT_ARM) || defined(LIBHAT_AARCH64) LIBHAT_EXPORT namespace hat { + struct cpu_extensions { + bool neon; + }; + struct system_info_arm : hat::system_info { + cpu_extensions extensions{}; private: - system_info_arm() = default; + system_info_arm(); friend const system_info_arm& get_system(); static const system_info_arm instance; }; using system_info_impl = system_info_arm; + + inline constexpr cpu_extensions compiled_extensions{ +#if defined(__ARM_NEON) || defined(LIBHAT_WINDOWS) || defined(LIBHAT_MAC) + .neon = true, +#else + .neon = false, +#endif + }; } #endif diff --git a/scripts/frequency.py b/scripts/frequency.py index 9f75422..258fa7e 100644 --- a/scripts/frequency.py +++ b/scripts/frequency.py @@ -1,23 +1,28 @@ import itertools import sys +import numpy as np import pefile def main(): files = [pefile.PE(path, fast_load=True) for path in sys.argv[1:]] - pair_counts = [0 for _ in range(2 ** 16)] + pair_counts = np.zeros(1 << 16, dtype=np.int64) total_pairs_count = 0 for pe in files: for section in pe.sections: if not section.IMAGE_SCN_MEM_EXECUTE: continue - data = section.get_data() + data = memoryview(section.get_data()) total_pairs_count += len(data) - 1 - for a, b in zip(data[:], data[1:]): - pair_counts[a * 0x100 + b] += 1 + + a = np.frombuffer(data[:-1], dtype=np.uint8).astype(np.uint16) + b = np.frombuffer(data[1:], dtype=np.uint8) + pairs = (a << 8) | b + count = np.bincount(pairs, minlength=1 << 16) + pair_counts += count top_n_pairs = 512 sorted_pairs = sorted( diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 04a2c9b..54ea379 100644 Binary files a/scripts/requirements.txt and b/scripts/requirements.txt differ diff --git a/src/Scanner.cpp b/src/Scanner.cpp index 091fa5b..af55124 100644 --- a/src/Scanner.cpp +++ b/src/Scanner.cpp @@ -7,25 +7,44 @@ #include "arch/x86/Frequency.hpp" #endif +#ifdef LIBHAT_HINT_AARCH64 +#include "arch/arm/Frequency.hpp" +#endif + namespace hat::detail { - void scan_context::apply_hints(const scanner_context& scanner) { - const bool pair0 = static_cast(this->hints & scan_hint::pair0); + static constexpr uint16_t NUM_PAIRS = 512; + + using pair_hint_t = std::tuple< + const std::array, NUM_PAIRS>&, + const std::array& + >; + static constexpr auto get_pair_hint(const scan_hint hints) -> std::optional { #ifdef LIBHAT_HINT_X86_64 - const bool x86_64 = static_cast(this->hints & scan_hint::x86_64); - if (x86_64 && !pair0 && scanner.vectorSize) { - static constexpr auto getScore = [](const std::byte a, const std::byte b) -> uint16_t { - constexpr auto& pairs = hat::detail::x86_64::pairs_x1; - constexpr auto& scores = hat::detail::x86_64::scores_x1; - static_assert(pairs.size() == scores.size()); + if (static_cast(hints & scan_hint::x86_64)) { + return std::tie(hat::detail::x86_64::pairs_x1, hat::detail::x86_64::scores_x1); + } +#endif +#ifdef LIBHAT_HINT_AARCH64 + if (static_cast(hints & scan_hint::aarch64)) { + return std::tie(hat::detail::aarch64::pairs_x1, hat::detail::aarch64::scores_x1); + } +#endif + return std::nullopt; + } - constexpr auto max = static_cast(scores.size()); + void scan_context::apply_hints(const scanner_context& scanner) { + const bool pair0 = static_cast(this->hints & scan_hint::pair0); + const auto pair_hint = get_pair_hint(this->hints); + if (pair_hint && !pair0 && scanner.vectorSize) { + const auto getScore = [&](const std::byte a, const std::byte b) -> uint16_t { + const auto& [pairs, scores] = *pair_hint; const std::pair pair{a, b}; const auto it = std::ranges::lower_bound(pairs, pair); const auto index = static_cast(it - pairs.begin()); - return it != pairs.end() && *it == pair ? scores[index] : max; + return it != pairs.end() && *it == pair ? scores[index] : NUM_PAIRS; }; std::optional> bestPair{}; @@ -46,7 +65,6 @@ namespace hat::detail { this->pairIndex = bestPair->first; } } -#endif // If no "optimal" pair was found, find the first byte pair in the signature if (!this->pairIndex.has_value()) { @@ -68,23 +86,29 @@ namespace hat::detail { template<> scan_function_t resolve_scanner(scan_context& context) { -#if defined(LIBHAT_X86) || defined(LIBHAT_X86_64) const auto& ext = get_system().extensions; - if (ext.bmi) { +#if defined(LIBHAT_X86) || defined(LIBHAT_X86_64) + if (compiled_extensions.bmi || ext.bmi) { #if defined(LIBHAT_X86_64) && !defined(LIBHAT_DISABLE_AVX512) - if (ext.avx512f && ext.avx512bw) { + if ((compiled_extensions.avx512f || ext.avx512f) + && (compiled_extensions.avx512bw || ext.avx512bw)) { return resolve_scanner(context); } #endif - if (ext.avx2) { + if (compiled_extensions.avx2 || ext.avx2) { return resolve_scanner(context); } } #if !defined(LIBHAT_DISABLE_SSE) - if (ext.sse41) { + if (compiled_extensions.sse41 || ext.sse41) { return resolve_scanner(context); } #endif +#endif +#if defined(LIBHAT_ARM) || defined(LIBHAT_AARCH64) + if (compiled_extensions.neon || ext.neon) { + return resolve_scanner(context); + } #endif // If none of the vectorized implementations are available/supported, then fallback to scanning per-byte return resolve_scanner(context); diff --git a/src/Utils.hpp b/src/Utils.hpp index 105f279..3aa1456 100644 --- a/src/Utils.hpp +++ b/src/Utils.hpp @@ -3,6 +3,8 @@ #include #include +#include + namespace hat::detail { constexpr uintptr_t fast_align_down(uintptr_t address, size_t alignment) { @@ -12,4 +14,22 @@ namespace hat::detail { constexpr uintptr_t fast_align_up(uintptr_t address, size_t alignment) { return (address + alignment - 1) & ~static_cast(alignment - 1); } + + template + auto* find_specialization_switch(const scan_alignment alignment, const bool cmpeq2, const bool veccmp) { + const auto with_alignment = [&](std::integral_constant) { + if (cmpeq2 && veccmp) return impl.template operator()(); + if (cmpeq2) return impl.template operator()(); + if (veccmp) return impl.template operator()(); + return impl.template operator()(); + }; + + switch (alignment) { + using enum scan_alignment; + case X1: return with_alignment(std::integral_constant{}); + case X4: return with_alignment(std::integral_constant{}); + case X16: return with_alignment(std::integral_constant{}); + } + LIBHAT_UNREACHABLE(); + } } diff --git a/src/arch/arm/Frequency.hpp b/src/arch/arm/Frequency.hpp new file mode 100644 index 0000000..c95ebc3 --- /dev/null +++ b/src/arch/arm/Frequency.hpp @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include +#include + +namespace hat::detail::aarch64 { + + static constexpr auto p(uint8_t a, uint8_t b) { + return std::pair{std::byte{a}, std::byte{b}}; + } + + // Top 512 byte pair occurrences on 1 byte alignment, sorted. Sourced from all PE (.exe and .dll) files included in + // the "clang+llvm-22.1.8-aarch64-pc-windows-msvc" archive. This list accounts for ~50.9% of all byte pairs. + static constexpr inline auto pairs_x1 = std::to_array>({ + p(0x00, 0x00), p(0x00, 0x01), p(0x00, 0x08), p(0x00, 0x11), p(0x00, 0x12), p(0x00, 0x14), p(0x00, 0x20), p(0x00, 0x2A), + p(0x00, 0x34), p(0x00, 0x35), p(0x00, 0x36), p(0x00, 0x37), p(0x00, 0x39), p(0x00, 0x40), p(0x00, 0x51), p(0x00, 0x54), + p(0x00, 0x71), p(0x00, 0x79), p(0x00, 0x80), p(0x00, 0x90), p(0x00, 0x91), p(0x00, 0x94), p(0x00, 0xA9), p(0x00, 0xAA), + p(0x00, 0xB0), p(0x00, 0xB4), p(0x00, 0xB5), p(0x00, 0xB9), p(0x00, 0xD0), p(0x00, 0xD1), p(0x00, 0xF0), p(0x00, 0xF1), + p(0x00, 0xF9), p(0x00, 0xFD), p(0x01, 0x00), p(0x01, 0x01), p(0x01, 0x05), p(0x01, 0x08), p(0x01, 0x09), p(0x01, 0x0A), + p(0x01, 0x0B), p(0x01, 0x0C), p(0x01, 0x15), p(0x01, 0x3F), p(0x01, 0x40), p(0x01, 0x54), p(0x01, 0x71), p(0x01, 0x80), + p(0x01, 0x90), p(0x01, 0x91), p(0x01, 0xA9), p(0x01, 0xAA), p(0x01, 0xB0), p(0x01, 0xB9), p(0x01, 0xD0), p(0x01, 0xD1), + p(0x01, 0xF0), p(0x01, 0xF9), p(0x02, 0x00), p(0x02, 0x01), p(0x02, 0x05), p(0x02, 0x08), p(0x02, 0x40), p(0x02, 0x80), + p(0x02, 0x91), p(0x02, 0xA9), p(0x02, 0xAA), p(0x02, 0xF9), p(0x03, 0x00), p(0x03, 0x01), p(0x03, 0x02), p(0x03, 0x03), + p(0x03, 0x04), p(0x03, 0x05), p(0x03, 0x08), p(0x03, 0x09), p(0x03, 0x13), p(0x03, 0x14), p(0x03, 0x15), p(0x03, 0x16), + p(0x03, 0x17), p(0x03, 0x18), p(0x03, 0x19), p(0x03, 0x1A), p(0x03, 0x1B), p(0x03, 0x1C), p(0x03, 0x1F), p(0x03, 0x40), + p(0x03, 0x5F), p(0x03, 0x80), p(0x03, 0x91), p(0x03, 0xA9), p(0x03, 0xAA), p(0x04, 0x00), p(0x04, 0x40), p(0x04, 0x80), + p(0x04, 0x91), p(0x04, 0xA9), p(0x05, 0x00), p(0x05, 0x01), p(0x05, 0x40), p(0x05, 0x91), p(0x06, 0x00), p(0x06, 0x40), + p(0x06, 0x80), p(0x06, 0x91), p(0x07, 0x00), p(0x07, 0x40), p(0x07, 0x80), p(0x07, 0x91), p(0x08, 0x00), p(0x08, 0x01), + p(0x08, 0x05), p(0x08, 0x09), p(0x08, 0x10), p(0x08, 0x11), p(0x08, 0x21), p(0x08, 0x40), p(0x08, 0x41), p(0x08, 0x61), + p(0x08, 0x81), p(0x08, 0x8B), p(0x08, 0x91), p(0x08, 0xA1), p(0x08, 0xAA), p(0x08, 0xC1), p(0x08, 0xCB), p(0x08, 0xEB), + p(0x09, 0x00), p(0x09, 0x01), p(0x09, 0x05), p(0x09, 0x40), p(0x09, 0x6B), p(0x09, 0x8B), p(0x09, 0x91), p(0x09, 0xEB), + p(0x0A, 0x00), p(0x0A, 0x01), p(0x0A, 0x40), p(0x0A, 0xEB), p(0x0B, 0x00), p(0x0B, 0x40), p(0x0C, 0x00), p(0x0D, 0x00), + p(0x0D, 0x40), p(0x0E, 0x00), p(0x0E, 0x40), p(0x0F, 0x00), p(0x0F, 0x40), p(0x10, 0x00), p(0x10, 0x40), p(0x11, 0x00), + p(0x11, 0x40), p(0x12, 0x00), p(0x12, 0x40), p(0x13, 0x00), p(0x13, 0x40), p(0x13, 0xAA), p(0x14, 0x00), p(0x14, 0xAA), + p(0x14, 0xE0), p(0x14, 0xE8), p(0x14, 0xEB), p(0x15, 0x00), p(0x15, 0xAA), p(0x15, 0xEB), p(0x16, 0x40), p(0x16, 0xAA), + p(0x17, 0x00), p(0x17, 0x08), p(0x17, 0x40), p(0x17, 0x9F), p(0x17, 0xAA), p(0x17, 0xE0), p(0x17, 0xE8), p(0x18, 0xAA), + p(0x19, 0x00), p(0x19, 0xAA), p(0x1A, 0x00), p(0x1A, 0x40), p(0x1A, 0xAA), p(0x1B, 0x00), p(0x1B, 0x40), p(0x1B, 0xAA), + p(0x1C, 0xAA), p(0x1D, 0x00), p(0x1F, 0x00), p(0x1F, 0x01), p(0x1F, 0x05), p(0x1F, 0x09), p(0x1F, 0x20), p(0x1F, 0x2A), + p(0x1F, 0x40), p(0x1F, 0x41), p(0x1F, 0x7C), p(0x1F, 0xAA), p(0x20, 0x00), p(0x20, 0x01), p(0x20, 0x80), p(0x20, 0xD4), + p(0x21, 0x00), p(0x21, 0x40), p(0x22, 0x00), p(0x22, 0x40), p(0x23, 0x00), p(0x23, 0x01), p(0x23, 0x40), p(0x27, 0x00), + p(0x27, 0x40), p(0x28, 0x00), p(0x28, 0x01), p(0x29, 0x00), p(0x29, 0x01), p(0x29, 0x05), p(0x2A, 0x00), p(0x2A, 0xE0), + p(0x2B, 0x00), p(0x2B, 0x40), p(0x2F, 0x00), p(0x2F, 0x40), p(0x33, 0x00), p(0x33, 0x40), p(0x39, 0x08), p(0x39, 0x1F), + p(0x39, 0xE8), p(0x3D, 0x00), p(0x3D, 0xE0), p(0x3F, 0x00), p(0x3F, 0x01), p(0x3F, 0x05), p(0x3F, 0xD6), p(0x40, 0x00), + p(0x40, 0x01), p(0x40, 0x39), p(0x40, 0x79), p(0x40, 0x91), p(0x40, 0x92), p(0x40, 0xA9), p(0x40, 0xB1), p(0x40, 0xB9), + p(0x40, 0xF2), p(0x40, 0xF8), p(0x40, 0xF9), p(0x40, 0xFD), p(0x41, 0x00), p(0x41, 0x39), p(0x41, 0xA9), p(0x41, 0xB9), + p(0x41, 0xF8), p(0x41, 0xF9), p(0x42, 0x00), p(0x42, 0xA9), p(0x42, 0xB9), p(0x42, 0xF9), p(0x43, 0x00), p(0x43, 0x01), + p(0x43, 0x40), p(0x43, 0xA9), p(0x43, 0xF9), p(0x44, 0xA9), p(0x44, 0xF9), p(0x45, 0xF9), p(0x4A, 0x01), p(0x51, 0x1F), + p(0x52, 0x08), p(0x52, 0x09), p(0x52, 0x1F), p(0x52, 0xE0), p(0x52, 0xE3), p(0x52, 0xE8), p(0x53, 0xC2), p(0x54, 0x08), + p(0x54, 0x09), p(0x54, 0x1F), p(0x54, 0x28), p(0x54, 0x29), p(0x54, 0x3F), p(0x54, 0x68), p(0x54, 0x88), p(0x54, 0xE0), + p(0x54, 0xE8), p(0x54, 0xE9), p(0x54, 0xFE), p(0x5B, 0x01), p(0x5B, 0x41), p(0x5F, 0x01), p(0x5F, 0xD6), p(0x5F, 0xF8), + p(0x60, 0x00), p(0x60, 0x02), p(0x61, 0x00), p(0x62, 0x00), p(0x63, 0x00), p(0x68, 0x00), p(0x68, 0x02), p(0x68, 0x0A), + p(0x69, 0x02), p(0x6B, 0x01), p(0x71, 0x61), p(0x7C, 0x92), p(0x7C, 0xD3), p(0x7D, 0x92), p(0x7D, 0xD3), p(0x7F, 0x01), + p(0x7F, 0x02), p(0x80, 0x00), p(0x80, 0x3D), p(0x80, 0x52), p(0x81, 0x00), p(0x82, 0x00), p(0x83, 0x00), p(0x83, 0x01), + p(0x83, 0x02), p(0x88, 0x00), p(0x88, 0x02), p(0x88, 0x9A), p(0x89, 0x9A), p(0x90, 0x08), p(0x91, 0x00), p(0x91, 0x01), + p(0x91, 0x02), p(0x91, 0x03), p(0x91, 0x08), p(0x91, 0x1F), p(0x91, 0x22), p(0x91, 0xC0), p(0x91, 0xE0), p(0x91, 0xE1), + p(0x91, 0xE2), p(0x91, 0xE3), p(0x91, 0xE4), p(0x91, 0xE8), p(0x91, 0xE9), p(0x91, 0xFF), p(0x94, 0x20), p(0x94, 0x68), + p(0x94, 0xE0), p(0x94, 0xE1), p(0x94, 0xE8), p(0x97, 0x20), p(0x97, 0x68), p(0x97, 0xE0), p(0x97, 0xE1), p(0x97, 0xE8), + p(0x9F, 0x02), p(0x9F, 0x1A), p(0xA0, 0x00), p(0xA1, 0x00), p(0xA3, 0x00), p(0xA8, 0x02), p(0xA8, 0xC0), p(0xA9, 0x08), + p(0xA9, 0x1F), p(0xA9, 0xE0), p(0xA9, 0xE8), p(0xA9, 0xF3), p(0xA9, 0xF5), p(0xA9, 0xF7), p(0xA9, 0xF9), p(0xA9, 0xFE), + p(0xA9, 0xFF), p(0xAA, 0x00), p(0xAA, 0x01), p(0xAA, 0x02), p(0xAA, 0x03), p(0xAA, 0x08), p(0xAA, 0x1F), p(0xAA, 0xE0), + p(0xAA, 0xE1), p(0xAA, 0xE2), p(0xAA, 0xE3), p(0xAA, 0xE4), p(0xAA, 0xE5), p(0xAA, 0xE8), p(0xAA, 0xF3), p(0xAA, 0xF4), + p(0xAA, 0xF5), p(0xAA, 0xF6), p(0xAA, 0xF7), p(0xAA, 0xFE), p(0xAA, 0xFF), p(0xB0, 0x08), p(0xB4, 0x08), p(0xB4, 0xE0), + p(0xB9, 0x08), p(0xB9, 0x09), p(0xB9, 0x1F), p(0xB9, 0x29), p(0xB9, 0x3F), p(0xB9, 0x68), p(0xB9, 0xE0), p(0xB9, 0xE1), + p(0xB9, 0xE8), p(0xB9, 0xE9), p(0xB9, 0xFF), p(0xBF, 0x02), p(0xC0, 0x03), p(0xC0, 0x3D), p(0xC1, 0x00), p(0xC2, 0xA8), + p(0xC3, 0x00), p(0xC3, 0x01), p(0xC8, 0x02), p(0xD0, 0x08), p(0xD1, 0xF3), p(0xD4, 0xFF), p(0xD6, 0xE0), p(0xDF, 0x02), + p(0xE0, 0x03), p(0xE0, 0x07), p(0xE0, 0x0F), p(0xE0, 0x23), p(0xE0, 0x43), p(0xE0, 0x63), p(0xE0, 0x83), p(0xE0, 0xC3), + p(0xE1, 0x03), p(0xE1, 0x23), p(0xE1, 0x43), p(0xE1, 0x63), p(0xE1, 0x83), p(0xE1, 0xC3), p(0xE2, 0x03), p(0xE3, 0x00), + p(0xE3, 0x03), p(0xE4, 0x00), p(0xE4, 0x03), p(0xE5, 0x03), p(0xE6, 0x03), p(0xE7, 0x03), p(0xE8, 0x03), p(0xE8, 0x07), + p(0xE8, 0x0B), p(0xE8, 0x0F), p(0xE8, 0x13), p(0xE8, 0x17), p(0xE8, 0x23), p(0xE8, 0x27), p(0xE8, 0x43), p(0xE9, 0x03), + p(0xEA, 0x03), p(0xEB, 0x01), p(0xEB, 0x40), p(0xEB, 0x61), p(0xEB, 0xA1), p(0xEB, 0xC1), p(0xEB, 0xE1), p(0xED, 0x7C), + p(0xF0, 0x08), p(0xF1, 0x7D), p(0xF3, 0x03), p(0xF3, 0x53), p(0xF4, 0x03), p(0xF5, 0x03), p(0xF5, 0x5B), p(0xF5, 0x7B), + p(0xF6, 0x03), p(0xF7, 0x03), p(0xF7, 0x63), p(0xF8, 0x03), p(0xF9, 0x00), p(0xF9, 0x01), p(0xF9, 0x02), p(0xF9, 0x03), + p(0xF9, 0x08), p(0xF9, 0x09), p(0xF9, 0x0A), p(0xF9, 0x1F), p(0xF9, 0x20), p(0xF9, 0x21), p(0xF9, 0x28), p(0xF9, 0x29), + p(0xF9, 0x2A), p(0xF9, 0x3F), p(0xF9, 0x40), p(0xF9, 0x48), p(0xF9, 0x68), p(0xF9, 0x69), p(0xF9, 0x6B), p(0xF9, 0x7F), + p(0xF9, 0x88), p(0xF9, 0xA8), p(0xF9, 0xC8), p(0xF9, 0xE0), p(0xF9, 0xE1), p(0xF9, 0xE2), p(0xF9, 0xE8), p(0xF9, 0xE9), + p(0xF9, 0xEA), p(0xF9, 0xF3), p(0xF9, 0xF4), p(0xF9, 0xF5), p(0xF9, 0xF7), p(0xF9, 0xFB), p(0xF9, 0xFF), p(0xFA, 0x03), + p(0xFA, 0xFF), p(0xFB, 0x73), p(0xFB, 0xFF), p(0xFC, 0xFF), p(0xFD, 0xFF), p(0xFE, 0x0B), p(0xFE, 0xFF), p(0xFF, 0x03), + p(0xFF, 0x17), p(0xFF, 0x43), p(0xFF, 0x54), p(0xFF, 0x83), p(0xFF, 0x97), p(0xFF, 0xB4), p(0xFF, 0xC3), p(0xFF, 0xFF), + }); + + static constexpr inline auto scores_x1 = std::to_array({ + 0x002, 0x040, 0x0D9, 0x04F, 0x0A3, 0x00E, 0x06F, 0x12A, 0x048, 0x0F0, 0x07E, 0x0AE, 0x06E, 0x032, 0x05F, 0x001, + 0x011, 0x0F4, 0x00D, 0x087, 0x006, 0x043, 0x092, 0x018, 0x086, 0x024, 0x0B8, 0x019, 0x08F, 0x0B5, 0x074, 0x01B, + 0x004, 0x0ED, 0x00A, 0x078, 0x14C, 0x073, 0x021, 0x071, 0x0C9, 0x18B, 0x1C0, 0x08C, 0x01C, 0x1A0, 0x05E, 0x046, + 0x10B, 0x027, 0x0A2, 0x05D, 0x114, 0x156, 0x112, 0x145, 0x110, 0x0A6, 0x016, 0x0EE, 0x12C, 0x137, 0x01E, 0x0CA, + 0x04E, 0x0DF, 0x0C6, 0x1A6, 0x009, 0x028, 0x05C, 0x0CF, 0x149, 0x1E7, 0x057, 0x16E, 0x013, 0x01A, 0x02A, 0x033, + 0x054, 0x076, 0x0A1, 0x0D3, 0x106, 0x171, 0x010, 0x072, 0x036, 0x0DB, 0x081, 0x126, 0x195, 0x050, 0x14E, 0x15D, + 0x0B7, 0x184, 0x00C, 0x155, 0x03E, 0x0E2, 0x04C, 0x066, 0x1D5, 0x101, 0x04A, 0x056, 0x1FE, 0x152, 0x02E, 0x026, + 0x058, 0x15A, 0x17B, 0x16D, 0x0D1, 0x0E6, 0x119, 0x1D7, 0x12D, 0x0CC, 0x13D, 0x150, 0x098, 0x136, 0x10E, 0x089, + 0x038, 0x096, 0x191, 0x08B, 0x0EC, 0x138, 0x1FD, 0x03B, 0x077, 0x1FF, 0x06A, 0x190, 0x068, 0x09E, 0x12B, 0x0D2, + 0x1A2, 0x133, 0x0E3, 0x0C5, 0x097, 0x117, 0x153, 0x0B2, 0x0F6, 0x0FB, 0x0E4, 0x0A9, 0x0D5, 0x014, 0x179, 0x01F, + 0x120, 0x1EB, 0x11D, 0x130, 0x02C, 0x180, 0x1E3, 0x03D, 0x0D4, 0x1E2, 0x0DC, 0x1CF, 0x064, 0x05A, 0x16A, 0x093, + 0x129, 0x0BF, 0x199, 0x13A, 0x0F9, 0x100, 0x108, 0x15F, 0x1CB, 0x11A, 0x03F, 0x015, 0x053, 0x1B9, 0x1EC, 0x02D, + 0x0E1, 0x167, 0x1CA, 0x025, 0x045, 0x10F, 0x146, 0x080, 0x052, 0x11B, 0x065, 0x13C, 0x04D, 0x1CD, 0x0DD, 0x169, + 0x139, 0x0AB, 0x10C, 0x0E0, 0x095, 0x0FA, 0x18F, 0x1DF, 0x148, 0x16B, 0x1F1, 0x19E, 0x18D, 0x1C3, 0x1F9, 0x0A4, + 0x1E1, 0x14F, 0x1E4, 0x161, 0x061, 0x160, 0x085, 0x0A7, 0x1F5, 0x029, 0x0D0, 0x1DD, 0x143, 0x0B3, 0x1A1, 0x007, + 0x1CE, 0x144, 0x000, 0x1B3, 0x079, 0x1FB, 0x07B, 0x0C7, 0x15B, 0x039, 0x0BA, 0x0C0, 0x1AC, 0x0AD, 0x088, 0x111, + 0x1ED, 0x103, 0x1A8, 0x165, 0x1BD, 0x1AD, 0x16C, 0x1AF, 0x0D6, 0x1BE, 0x19D, 0x173, 0x1DC, 0x13B, 0x1C7, 0x09D, + 0x0FF, 0x0B0, 0x172, 0x1A4, 0x187, 0x124, 0x17A, 0x044, 0x082, 0x125, 0x170, 0x1FA, 0x1C1, 0x0D7, 0x037, 0x185, + 0x11F, 0x142, 0x0A5, 0x14A, 0x0AA, 0x192, 0x0AF, 0x183, 0x1DE, 0x198, 0x197, 0x19C, 0x182, 0x132, 0x109, 0x188, + 0x1DA, 0x164, 0x069, 0x003, 0x0F1, 0x11C, 0x06B, 0x131, 0x1CC, 0x1B6, 0x0FD, 0x17D, 0x196, 0x1D3, 0x17E, 0x1EA, + 0x116, 0x1F7, 0x0C1, 0x0CB, 0x1A7, 0x099, 0x020, 0x02F, 0x07F, 0x0EB, 0x1F8, 0x094, 0x1D1, 0x1BA, 0x1AA, 0x151, + 0x060, 0x123, 0x063, 0x09A, 0x1B5, 0x09B, 0x186, 0x075, 0x1A3, 0x0B4, 0x1C6, 0x11E, 0x113, 0x166, 0x14D, 0x1D2, + 0x0AC, 0x1DB, 0x1F0, 0x06D, 0x03A, 0x083, 0x0D8, 0x0B6, 0x070, 0x12E, 0x134, 0x1BB, 0x1B0, 0x059, 0x1BC, 0x07C, + 0x030, 0x049, 0x0C4, 0x121, 0x1B1, 0x09C, 0x14B, 0x12F, 0x13F, 0x1E8, 0x1F3, 0x1E6, 0x15C, 0x1E0, 0x122, 0x105, + 0x062, 0x154, 0x07D, 0x1B8, 0x1C9, 0x1C4, 0x0E8, 0x1F6, 0x0DE, 0x128, 0x174, 0x19B, 0x035, 0x08A, 0x15E, 0x1B7, + 0x0BC, 0x19F, 0x1C5, 0x1D9, 0x0B1, 0x127, 0x0E5, 0x1D8, 0x005, 0x0F2, 0x194, 0x0C8, 0x0F7, 0x1D0, 0x0CD, 0x135, + 0x00F, 0x118, 0x0F8, 0x193, 0x104, 0x163, 0x01D, 0x177, 0x042, 0x1C8, 0x09F, 0x0DA, 0x13E, 0x1E5, 0x034, 0x0FC, + 0x1BF, 0x181, 0x176, 0x1C2, 0x168, 0x10A, 0x189, 0x0C3, 0x18C, 0x1AE, 0x102, 0x1D4, 0x18E, 0x1AB, 0x1FC, 0x140, + 0x1EE, 0x0BB, 0x03C, 0x023, 0x04B, 0x067, 0x041, 0x162, 0x091, 0x0BE, 0x084, 0x0E9, 0x051, 0x0C2, 0x1F4, 0x107, + 0x012, 0x06C, 0x157, 0x022, 0x18A, 0x1EF, 0x0A0, 0x0BD, 0x1E9, 0x10D, 0x1A9, 0x158, 0x0A8, 0x1F2, 0x0B9, 0x17C, + 0x0FE, 0x16F, 0x1B2, 0x02B, 0x047, 0x115, 0x031, 0x090, 0x1B4, 0x08D, 0x175, 0x0EF, 0x17F, 0x19A, 0x07A, 0x1A5, + 0x1D6, 0x147, 0x178, 0x0F3, 0x08E, 0x159, 0x055, 0x0CE, 0x008, 0x0F5, 0x017, 0x0E7, 0x05B, 0x141, 0x0EA, 0x00B, + }); +} diff --git a/src/arch/arm/Neon.cpp b/src/arch/arm/Neon.cpp new file mode 100644 index 0000000..74ecdb1 --- /dev/null +++ b/src/arch/arm/Neon.cpp @@ -0,0 +1,139 @@ +#include + +#if defined(LIBHAT_ARM) || defined(LIBHAT_AARCH64) + +#include + +#include "../../Utils.hpp" + +#include + +#ifdef _MSC_VER +#include + + namespace hat::detail { + inline unsigned long bsf(unsigned __int64 num) noexcept { + unsigned long offset; + _BitScanForward64(&offset, num); + return offset; + } + } + +#define LIBHAT_BSF64(num) hat::detail::bsf(num) +#else +#define LIBHAT_BSF64(num) __builtin_ctzll(num) +#endif + +#ifdef LIBHAT_AARCH64 +#define LIBHAT_TEST_ZERO(x) (vmaxvq_u32(vreinterpretq_u32_u8(x)) == 0) +#else +#define LIBHAT_TEST_ZERO(x) (!(vgetq_lane_u64(vreinterpretq_u64_u8(x), 0) | vgetq_lane_u64(vreinterpretq_u64_u8(x), 1))) +#endif + +namespace hat::detail { + + inline void load_signature_128(const signature_view signature, uint8x16_t& bytes, uint8x16_t& mask) { + uint8_t byteBuffer[16]{}; // The remaining signature bytes + uint8_t maskBuffer[16]{}; // A bitmask for the signature bytes we care about + for (size_t i = 0; i < signature.size(); i++) { + byteBuffer[i] = std::to_integer(signature[i].value()); + maskBuffer[i] = std::to_integer(signature[i].mask()); + } + bytes = vld1q_u8(static_cast(byteBuffer)); + mask = vld1q_u8(static_cast(maskBuffer)); + } + + template + LIBHAT_FORCEINLINE consteval uint64_t create_alignment_mask_neon() { + uint64_t mask{}; + for (size_t i = 0; i < 16; i += alignment_stride) { + mask |= (static_cast(0xF) << (i * 4)); + } + return mask; + } + + template + const_scan_result find_pattern_neon(const std::byte* begin, const std::byte* end, const scan_context& context) { + const auto signature = context.signature; + const auto cmpIndex = cmpeq2 ? *context.pairIndex : context.cmpIndex; + + // 128 bit vector containing first signature byte repeated + const auto firstByte = vdupq_n_u8(static_cast(*signature[cmpIndex])); + + uint8x16_t secondByte; + if constexpr (cmpeq2) { + secondByte = vdupq_n_u8(static_cast(*signature[cmpIndex + 1])); + } + + uint8x16_t signatureBytes, signatureMask; + if constexpr (veccmp) { + load_signature_128(signature, signatureBytes, signatureMask); + } + + auto [pre, vec, post] = segment_scan(begin, end, signature.size(), cmpIndex); + + if (!pre.empty()) { + const auto result = find_pattern_single(pre.data(), pre.data() + pre.size(), context); + if (result.has_result()) { + return result; + } + } + + const auto vec_begin = std::to_address(vec.begin()); + const auto vec_end = std::to_address(vec.end()); + for (auto it = vec_begin; it != vec_end; it++) { + auto cmp = vceqq_u8(firstByte, vld1q_u8(reinterpret_cast(it))); + + if constexpr (cmpeq2) { + const auto cmp2 = vceqq_u8(secondByte, vld1q_u8(reinterpret_cast(it) + 1)); + cmp = vandq_u8(cmp, cmp2); + } + + auto mask = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(cmp), 4)), 0); + if constexpr (alignment != scan_alignment::X1) { + mask &= std::rotl(create_alignment_mask_neon(), static_cast(cmpIndex) * 4); + } + + while (mask) { + const auto offset = LIBHAT_BSF64(mask); + const auto i = reinterpret_cast(it) + (offset >> 2) - cmpIndex; + if constexpr (veccmp) { + const auto data = vld1q_u8(reinterpret_cast(i)); + const auto neqBits = veorq_u8(data, signatureBytes); + const auto match = vandq_u8(neqBits, signatureMask); + if (LIBHAT_TEST_ZERO(match)) LIBHAT_UNLIKELY { + return i; + } + } else { + const auto match = std::equal(signature.begin(), signature.end(), i); + if (match) LIBHAT_UNLIKELY { + return i; + } + } + // thanks msvc? + // mask &= ~(0xF * (mask & (~mask + 1))); + mask ^= (uint64_t{0xF} << offset); + } + } + + if (!post.empty()) { + return find_pattern_single(post.data(), post.data() + post.size(), context); + } + return {}; + } + + template<> + scan_function_t resolve_scanner(scan_context& context) { + context.apply_hints({.vectorSize = 16}); + + const auto alignment = context.alignment; + const auto signature = context.signature; + const bool cmpeq2 = context.pairIndex.has_value(); + const bool veccmp = signature.size() <= 16; + + return find_specialization_switch<[]() consteval { + return &find_pattern_neon; + }>(alignment, cmpeq2, veccmp); + } +} +#endif diff --git a/src/arch/arm/System.cpp b/src/arch/arm/System.cpp index ad76b8f..f6c439a 100644 --- a/src/arch/arm/System.cpp +++ b/src/arch/arm/System.cpp @@ -1,9 +1,32 @@ #include -#ifdef LIBHAT_ARM +#if defined(LIBHAT_ARM) || defined(LIBHAT_AARCH64) #include +#if defined(LIBHAT_WINDOWS) || defined(LIBHAT_MAC) namespace hat { + system_info_arm::system_info_arm() { + this->extensions.neon = true; + } +} +#endif + +#if defined(LIBHAT_LINUX) + +#include +#include +namespace hat { + system_info_arm::system_info_arm() { +#if defined(LIBHAT_ARM) + unsigned long hwcap = getauxval(AT_HWCAP); + this->extensions.neon = (hwcap & HWCAP_NEON) != 0; +#else // AARCH64 + unsigned long hwcap = getauxval(AT_HWCAP); + this->extensions.neon = (hwcap & HWCAP_ASIMD) != 0; +#endif + } } #endif + +#endif diff --git a/src/arch/x86/AVX2.cpp b/src/arch/x86/AVX2.cpp index fecd6fb..58f402f 100644 --- a/src/arch/x86/AVX2.cpp +++ b/src/arch/x86/AVX2.cpp @@ -4,6 +4,8 @@ #include +#include "../../Utils.hpp" + #include namespace hat::detail { @@ -102,28 +104,9 @@ namespace hat::detail { const bool cmpeq2 = context.pairIndex.has_value(); const bool veccmp = signature.size() <= 32; - if (alignment == scan_alignment::X1) { - if (cmpeq2 && veccmp) { - return &find_pattern_avx2; - } else if (cmpeq2) { - return &find_pattern_avx2; - } else if (veccmp) { - return &find_pattern_avx2; - } else { - return &find_pattern_avx2; - } - } else if (alignment == scan_alignment::X16) { - if (cmpeq2 && veccmp) { - return &find_pattern_avx2; - } else if (cmpeq2) { - return &find_pattern_avx2; - } else if (veccmp) { - return &find_pattern_avx2; - } else { - return &find_pattern_avx2; - } - } - LIBHAT_UNREACHABLE(); + return find_specialization_switch<[]() consteval { + return &find_pattern_avx2; + }>(alignment, cmpeq2, veccmp); } } #endif diff --git a/src/arch/x86/AVX512.cpp b/src/arch/x86/AVX512.cpp index 7cbdcbf..daf0d24 100644 --- a/src/arch/x86/AVX512.cpp +++ b/src/arch/x86/AVX512.cpp @@ -4,6 +4,8 @@ #include +#include "../../Utils.hpp" + #include namespace hat::detail { @@ -99,28 +101,9 @@ namespace hat::detail { const bool cmpeq2 = context.pairIndex.has_value(); const bool veccmp = signature.size() <= 64; - if (alignment == scan_alignment::X1) { - if (cmpeq2 && veccmp) { - return &find_pattern_avx512; - } else if (cmpeq2) { - return &find_pattern_avx512; - } else if (veccmp) { - return &find_pattern_avx512; - } else { - return &find_pattern_avx512; - } - } else if (alignment == scan_alignment::X16) { - if (cmpeq2 && veccmp) { - return &find_pattern_avx512; - } else if (cmpeq2) { - return &find_pattern_avx512; - } else if (veccmp) { - return &find_pattern_avx512; - } else { - return &find_pattern_avx512; - } - } - LIBHAT_UNREACHABLE(); + return find_specialization_switch<[]() consteval { + return &find_pattern_avx512; + }>(alignment, cmpeq2, veccmp); } } #endif diff --git a/src/arch/x86/Frequency.hpp b/src/arch/x86/Frequency.hpp index 8729832..2f61da1 100644 --- a/src/arch/x86/Frequency.hpp +++ b/src/arch/x86/Frequency.hpp @@ -1,5 +1,10 @@ #pragma once +#include +#include +#include +#include + namespace hat::detail::x86_64 { static constexpr auto p(uint8_t a, uint8_t b) { diff --git a/src/arch/x86/SSE.cpp b/src/arch/x86/SSE.cpp index fd2b473..37fd384 100644 --- a/src/arch/x86/SSE.cpp +++ b/src/arch/x86/SSE.cpp @@ -4,6 +4,8 @@ #include +#include "../../Utils.hpp" + #include #ifdef _MSC_VER @@ -114,28 +116,9 @@ namespace hat::detail { const bool cmpeq2 = context.pairIndex.has_value(); const bool veccmp = signature.size() <= 16; - if (alignment == scan_alignment::X1) { - if (cmpeq2 && veccmp) { - return &find_pattern_sse; - } else if (cmpeq2) { - return &find_pattern_sse; - } else if (veccmp) { - return &find_pattern_sse; - } else { - return &find_pattern_sse; - } - } else if (alignment == scan_alignment::X16) { - if (cmpeq2 && veccmp) { - return &find_pattern_sse; - } else if (cmpeq2) { - return &find_pattern_sse; - } else if (veccmp) { - return &find_pattern_sse; - } else { - return &find_pattern_sse; - } - } - LIBHAT_UNREACHABLE(); + return find_specialization_switch<[]() consteval { + return &find_pattern_sse; + }>(alignment, cmpeq2, veccmp); } } #endif diff --git a/src/c/libhat.cpp b/src/c/libhat.cpp index ba52b48..3e20022 100644 --- a/src/c/libhat.cpp +++ b/src/c/libhat.cpp @@ -17,6 +17,8 @@ static hat::scan_alignment to_cpp_align(const scan_alignment align) { switch (align) { case scan_alignment_x1: return hat::scan_alignment::X1; + case scan_alignment_x4: + return hat::scan_alignment::X4; case scan_alignment_x16: return hat::scan_alignment::X16; } diff --git a/src/os/linux/Process.cpp b/src/os/linux/Process.cpp index 7fc1eaa..959ba4e 100644 --- a/src/os/linux/Process.cpp +++ b/src/os/linux/Process.cpp @@ -32,7 +32,7 @@ namespace hat::process { for (size_t i = 0; i < info.dlpi_phnum; i++) { auto& header = info.dlpi_phdr[i]; - if (header.p_type != PT_LOAD) { + if (header.p_type != PT_LOAD && header.p_type != PT_GNU_RELRO) { continue; } max = std::max(max, detail::fast_align_up(header.p_vaddr + header.p_memsz, @@ -60,7 +60,7 @@ namespace hat::process { for (size_t i = 0; i < info.dlpi_phnum; i++) { auto& header = info.dlpi_phdr[i]; - if (header.p_type != PT_LOAD) { + if (header.p_type != PT_LOAD && header.p_type != PT_GNU_RELRO) { continue; } @@ -104,6 +104,7 @@ namespace hat::process { std::optional module{}; auto callback = [&](const dl_phdr_info& info) { + if (!info.dlpi_addr) return 0; const Handle h{dlopen(info.dlpi_name, RTLD_LAZY | RTLD_NOLOAD)}; if (h == handle) { module = hat::process::module{std::bit_cast(info.dlpi_addr)}; diff --git a/src/os/mac/Process.cpp b/src/os/mac/Process.cpp new file mode 100644 index 0000000..63ac723 --- /dev/null +++ b/src/os/mac/Process.cpp @@ -0,0 +1,126 @@ +#include +#ifdef LIBHAT_MAC + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +namespace hat::process { + +#ifdef LIBHAT_LP64 + using mach_header_t = mach_header_64; + using segment_command_t = segment_command_64; + static constexpr uint32_t mh_magic = MH_MAGIC_64; + static constexpr uint32_t mh_cigam = MH_CIGAM_64; + static constexpr uint32_t lc_segment = LC_SEGMENT_64; +#else + using mach_header_t = mach_header; + using segment_command_t = segment_command; + static constexpr uint32_t mh_magic = MH_MAGIC; + static constexpr uint32_t mh_cigam = MH_CIGAM; + static constexpr uint32_t lc_segment = LC_SEGMENT; +#endif + + static bool is_valid_header(const mach_header_t* header) { + return header && (header->magic == mh_magic || header->magic == mh_cigam); + } + + hat::process::module get_process_module() { + const uint32_t count = _dyld_image_count(); + for (uint32_t i = 0; i != count; i++) { + const auto* header = reinterpret_cast(_dyld_get_image_header(i)); + if (!is_valid_header(header)) { + continue; + } + + if (header->filetype == MH_EXECUTE) { + return hat::process::module{std::bit_cast(header)}; + } + } + std::abort(); + } + + void module::for_each_segment(const std::function, hat::protection)>& callback) const { + const uint32_t imageCount = _dyld_image_count(); + for (uint32_t i = 0; i < imageCount; i++) { + const auto* header = reinterpret_cast(_dyld_get_image_header(i)); + if (!is_valid_header(header)) { + continue; + } + if (std::bit_cast(header) != this->address()) { + continue; + } + + const auto slide = static_cast(_dyld_get_image_vmaddr_slide(i)); + const auto* cmd = reinterpret_cast( + reinterpret_cast(header) + sizeof(mach_header_t)); + + for (uint32_t j = 0; j < header->ncmds; j++) { + if (cmd->cmd == lc_segment) { + const auto* seg = reinterpret_cast(cmd); + + // skip __PAGEZERO and any unmapped segment + if (seg->vmsize != 0 && seg->initprot != 0) { + const std::span data{ + reinterpret_cast(seg->vmaddr + slide), + seg->vmsize + }; + + hat::protection prot{}; + if (seg->initprot & VM_PROT_READ) prot |= hat::protection::Read; + if (seg->initprot & VM_PROT_WRITE) prot |= hat::protection::Write; + if (seg->initprot & VM_PROT_EXECUTE) prot |= hat::protection::Execute; + + if (!callback(data, prot)) { + return; + } + } + } + cmd = reinterpret_cast( + reinterpret_cast(cmd) + cmd->cmdsize); + } + return; + } + } + + std::optional get_module(const std::string_view name) { + if (name.empty()) { + return get_process_module(); + } + + using Handle = std::unique_ptr; + + const std::string buffer{name}; + const Handle handle{dlopen(buffer.c_str(), RTLD_LAZY | RTLD_NOLOAD)}; + if (!handle) { + return {}; + } + + const uint32_t count = _dyld_image_count(); + for (uint32_t i = 0; i < count; i++) { + const auto* header = reinterpret_cast(_dyld_get_image_header(i)); + if (!is_valid_header(header)) { + continue; + } + + const Handle h{dlopen(_dyld_get_image_name(i), RTLD_LAZY | RTLD_NOLOAD)}; + if (h == handle) { + return hat::process::module{std::bit_cast(header)}; + } + } + + return {}; + } +} + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fda2f3a..9ef35d7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -59,6 +59,7 @@ function(register_test NAME SOURCE) endfunction() register_test(libhat_benchmark_compare benchmark/Compare.cpp) +register_test(libhat_benchmark_compare_impl benchmark/CompareImpl.cpp) register_test(libhat_test_scanner tests/Scanner.cpp) register_test(libhat_test_process tests/Process.cpp) diff --git a/test/benchmark/CompareImpl.cpp b/test/benchmark/CompareImpl.cpp new file mode 100644 index 0000000..a7d1250 --- /dev/null +++ b/test/benchmark/CompareImpl.cpp @@ -0,0 +1,49 @@ +#include + +#include +#include + +static constexpr std::string_view test_pattern = "01 02 03 04 05 06 07 08 09"; + +static auto gen_random_buffer(const size_t size) { + std::vector buffer(size); + std::default_random_engine generator(123); + std::uniform_int_distribution distribution(0, 0xFFFFFFFFFFFFFFFF); + for (size_t i = 0; i < buffer.size(); i += 8) { + uint64_t value = distribution(generator); + std::memcpy(&buffer[i], &value, sizeof(value)); + } + return buffer; +} + +template +static void BM_Throughput(benchmark::State& state) { + const size_t size = state.range(0); + const auto buf = gen_random_buffer(size); + const auto begin = std::to_address(buf.begin()); + const auto end = std::to_address(buf.end()); + + const auto sig = hat::parse_signature(test_pattern).value(); + const auto ctx = hat::detail::scan_context::create(sig, hat::scan_alignment::X1, hat::scan_hint::none); + for (auto _ : state) { + benchmark::DoNotOptimize(ctx.scan(begin, end)); + } + state.SetBytesProcessed(static_cast(state.iterations() * size)); +} + +static constexpr int64_t rangeStart = 1 << 22; // 4 MiB +static constexpr int64_t rangeLimit = 1 << 28; // 256 MiB + +#define LIBHAT_BENCHMARK(...) BENCHMARK(__VA_ARGS__) \ + ->Threads(1) \ + ->MinWarmUpTime(1) \ + ->MinTime(2) \ + ->Range(rangeStart, rangeLimit) \ + ->UseRealTime(); + +LIBHAT_BENCHMARK(BM_Throughput); +#if defined(LIBHAT_AARCH64) || defined(LIBHAT_ARM) +LIBHAT_BENCHMARK(BM_Throughput); +#endif + +BENCHMARK_MAIN(); diff --git a/test/tests/Process.cpp b/test/tests/Process.cpp index 76fd19a..e6f6fec 100644 --- a/test/tests/Process.cpp +++ b/test/tests/Process.cpp @@ -4,3 +4,22 @@ TEST(ProcessTest, ProcessModuleMatchesEmptyStr) { EXPECT_EQ(hat::process::get_process_module(), hat::process::get_module({})); } + +TEST(ProcessTest, ProcessModuleHasDefaultSegments) { + bool rx = false; // .text + bool r = false; // .rdata + bool rw = false; // .data + hat::process::get_process_module().for_each_segment([&](auto, auto prot) { + if (prot == (hat::protection::Read | hat::protection::Execute)) { + rx = true; + } else if (prot == (hat::protection::Read)) { + r = true; + } else if (prot == (hat::protection::Read | hat::protection::Write)) { + rw = true; + } + return !rx || !r || !rw; + }); + EXPECT_TRUE(rx); + EXPECT_TRUE(r); + EXPECT_TRUE(rw); +} diff --git a/test/tests/Scanner.cpp b/test/tests/Scanner.cpp index 80f26e1..e226564 100644 --- a/test/tests/Scanner.cpp +++ b/test/tests/Scanner.cpp @@ -83,6 +83,14 @@ using FindPatternTestTypes = ::testing::Types< FindPatternParameters, FindPatternParameters, FindPatternParameters, +#endif +#if defined(LIBHAT_ARM) || defined(LIBHAT_AARCH64) + FindPatternParameters, + FindPatternParameters, + FindPatternParameters, + FindPatternParameters, + FindPatternParameters, + FindPatternParameters, #endif FindPatternParameters, FindPatternParameters, @@ -106,6 +114,7 @@ class FindPatternTestNameGenerator { else if constexpr (Mode == hat::detail::scan_mode::SSE) return "SSE"; else if constexpr (Mode == hat::detail::scan_mode::AVX2) return "AVX2"; else if constexpr (Mode == hat::detail::scan_mode::AVX512) return "AVX512"; + else if constexpr (Mode == hat::detail::scan_mode::Neon) return "Neon"; else static_assert(sizeof(Mode) == 0); } }; @@ -119,6 +128,17 @@ TYPED_TEST(FindPatternTest, ScanX1) { }); } +TYPED_TEST(FindPatternTest, ScanX4) { + this->run_cases(hat::scan_alignment::X4, [](auto result, auto expected) { + if (std::bit_cast(expected) % 4 == 0) { + ASSERT_TRUE(result.has_result()); + ASSERT_EQ(result.get(), expected); + } else { + ASSERT_FALSE(result.has_result()); + } + }); +} + TYPED_TEST(FindPatternTest, ScanX16) { this->run_cases(hat::scan_alignment::X16, [](auto result, auto expected) { if (std::bit_cast(expected) % 16 == 0) {