Compare commits

..

1 Commits

Author SHA1 Message Date
orange 73725b8365 mkdocs improvement 2026-06-08 19:49:29 +03:00
18 changed files with 160 additions and 903 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
python-version: '3.x'
- name: Install mkdocs and dependencies
run: pip install mkdocs mkdocs-bootswatch
run: pip install mkdocs mkdocs-material
- name: Build documentation
run: mkdocs build --strict
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
python-version: '3.x'
- name: Install mkdocs and dependencies
run: pip install mkdocs mkdocs-bootswatch
run: pip install mkdocs mkdocs-material
- name: Build documentation
run: mkdocs build --strict
+1 -1
View File
@@ -1 +1 @@
5.3.0
5.2.0
+15 -8
View File
@@ -1,11 +1,18 @@
/* Widen the navbar container */
.navbar .container {
max-width: 100%; /* adjust to your target width */
width: 95%;
/* Material header tweaks */
.md-header,
.md-tabs {
background-color: #101014;
}
/* Tighter spacing between navbar items */
.navbar-nav > li > a {
padding-left: 8px;
padding-right: 8px;
.md-header {
border-bottom: 1px solid rgba(255, 152, 0, 0.28);
}
.md-tabs {
border-bottom: 1px solid rgba(255, 152, 0, 0.16);
}
.md-tabs__link--active,
.md-tabs__link:hover {
color: #ffb74d;
}
+33 -54
View File
@@ -1,65 +1,44 @@
/* Normal links */
a {
color: orange;
/* Material dark palette overrides */
[data-md-color-scheme="slate"] {
--md-default-bg-color: #0f1117;
--md-default-fg-color: #e8eaed;
--md-primary-fg-color: #101014;
--md-primary-fg-color--light: #1a1a21;
--md-primary-fg-color--dark: #09090d;
--md-accent-fg-color: #ff9800;
--md-accent-fg-color--transparent: rgba(255, 152, 0, 0.14);
--md-typeset-a-color: #ffb74d;
--md-code-bg-color: #171a22;
--md-code-fg-color: #f5f7fa;
}
/* On hover/focus */
a:hover,
a:focus {
color: #ff9900; /* a slightly different orange, optional */
}
/* Navbar background */
.navbar,
.navbar-default,
.navbar-inverse {
background-color: #a26228 !important; /* your orange */
border-color: #ff6600 !important;
.md-typeset a {
color: var(--md-typeset-a-color);
}
/* Navbar brand + links */
.navbar .navbar-brand,
.navbar .navbar-nav > li > a {
color: #ffffff !important;
.md-typeset a:hover,
.md-typeset a:focus {
color: #ffd180;
}
/* Active and hover states */
.navbar .navbar-nav > .active > a,
.navbar .navbar-nav > .active > a:focus,
.navbar .navbar-nav > .active > a:hover,
.navbar .navbar-nav > li > a:hover,
.navbar .navbar-nav > li > a:focus {
color: #ffffff !important;
}
/* === DROPDOWN MENU BACKGROUND === */
.navbar .dropdown-menu {
border-color: #ff6600 !important;
.md-tabs__item--active > .md-tabs__link,
.md-tabs__link:hover,
.md-nav__link:hover,
.md-nav__link:focus,
.md-nav__link--active,
.md-nav__item .md-nav__link--active {
color: #ffb74d;
}
/* Caret icon (the little triangle) */
.navbar .dropdown-toggle .caret {
border-top-color: #ffffff !important;
border-bottom-color: #ffffff !important;
.md-typeset .md-button {
border-color: #ff9800;
color: #ffb74d;
}
/* === BOOTSTRAP 3 STYLE ITEMS (mkdocs + bootswatch darkly often use this) === */
.navbar .dropdown-menu > li > a {
color: #ffffff !important;
}
.navbar .dropdown-menu > li > a:hover,
.navbar .dropdown-menu > li > a:focus {
background-color: #e65c00 !important; /* darker orange on hover */
color: #ffffff !important;
}
/* === BOOTSTRAP 4+ STYLE ITEMS (if your theme uses .dropdown-item) === */
.navbar .dropdown-item {
color: #ffffff !important;
}
.navbar .dropdown-item:hover,
.navbar .dropdown-item:focus {
background-color: #e65c00 !important;
color: #ffffff !important;
.md-typeset .md-button:hover,
.md-typeset .md-button:focus,
.md-typeset .md-button--primary {
background-color: #ff9800;
border-color: #ff9800;
color: #111111;
}
+2 -63
View File
@@ -2,13 +2,12 @@
// Created by Vladislav on 30.12.2025.
//
#pragma once
#include "pattern_scan.hpp"
#include "section_scan_result.hpp"
#include <cstdint>
#include <filesystem>
#include <optional>
#include <span>
#include <string_view>
#include "section_scan_result.hpp"
namespace omath
{
class ElfPatternScanner final
@@ -19,74 +18,14 @@ namespace omath
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_loaded_module(module_base_address, target_section_name,
&ElfPatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_file(path_to_file, target_section_name,
&ElfPatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_memory_file(file_data, target_section_name,
&ElfPatternScanner::scan_section_for_pattern<Pattern>);
}
private:
using SectionScanFunction = std::optional<std::ptrdiff_t> (*)(std::span<const std::byte>);
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<std::ptrdiff_t> scan_section_for_pattern(const std::span<const std::byte> section_data)
{
const auto result = PatternScanner::scan_for_pattern<Pattern>(section_data.begin(), section_data.end());
if (result == section_data.end())
return std::nullopt;
return result - section_data.begin();
}
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult> scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name, SectionScanFunction scan_pattern);
};
} // namespace omath
} // namespace omath
+1 -62
View File
@@ -2,13 +2,12 @@
// Created by Copilot on 04.02.2026.
//
#pragma once
#include "pattern_scan.hpp"
#include "section_scan_result.hpp"
#include <cstdint>
#include <filesystem>
#include <optional>
#include <span>
#include <string_view>
#include "section_scan_result.hpp"
namespace omath
{
class MachOPatternScanner final
@@ -19,74 +18,14 @@ namespace omath
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = "__text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name = "__text")
{
return scan_for_pattern_in_loaded_module(module_base_address, target_section_name,
&MachOPatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = "__text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name = "__text")
{
return scan_for_pattern_in_file(path_to_file, target_section_name,
&MachOPatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
const std::string_view& target_section_name = "__text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name = "__text")
{
return scan_for_pattern_in_memory_file(file_data, target_section_name,
&MachOPatternScanner::scan_section_for_pattern<Pattern>);
}
private:
using SectionScanFunction = std::optional<std::ptrdiff_t> (*)(std::span<const std::byte>);
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<std::ptrdiff_t> scan_section_for_pattern(const std::span<const std::byte> section_data)
{
const auto result = PatternScanner::scan_for_pattern<Pattern>(section_data.begin(), section_data.end());
if (result == section_data.end())
return std::nullopt;
return result - section_data.begin();
}
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult> scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name, SectionScanFunction scan_pattern);
};
} // namespace omath
+6 -129
View File
@@ -3,12 +3,9 @@
//
#pragma once
#include <algorithm>
#include <array>
#include <expected>
#include <optional>
#include <span>
#include <stdexcept>
#include <string_view>
#include <vector>
@@ -18,8 +15,6 @@ class unit_test_pattern_scan_corner_case_1_Test;
class unit_test_pattern_scan_corner_case_2_Test;
class unit_test_pattern_scan_corner_case_3_Test;
class unit_test_pattern_scan_corner_case_4_Test;
class unit_test_pattern_scan_consteval_read_test_Test;
class unit_test_pattern_scan_consteval_spacing_and_case_Test;
// ReSharper restore CppInconsistentNaming
namespace omath
{
@@ -34,22 +29,8 @@ namespace omath
friend unit_test_pattern_scan_corner_case_2_Test;
friend unit_test_pattern_scan_corner_case_3_Test;
friend unit_test_pattern_scan_corner_case_4_Test;
friend unit_test_pattern_scan_consteval_read_test_Test;
friend unit_test_pattern_scan_consteval_spacing_and_case_Test;
public:
template<std::size_t N>
struct ConstevalPattern final
{
char value[N]{};
// ReSharper disable once CppNonExplicitConvertingConstructor
constexpr ConstevalPattern(const char (&text)[N]) // NOLINT(*-explicit-constructor)
{
std::ranges::copy(text, value);
}
};
[[nodiscard]]
static std::span<std::byte>::iterator scan_for_pattern(const std::span<std::byte>& range,
const std::string_view& pattern);
@@ -68,26 +49,9 @@ namespace omath
if (!parsed_pattern) [[unlikely]]
return end;
return scan_for_parsed_pattern(begin, end, parsed_pattern.value());
}
template<ConstevalPattern Pattern, class IteratorType>
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
static IteratorType scan_for_pattern(const IteratorType& begin, const IteratorType& end)
{
constexpr auto parsed_pattern = parse_pattern<Pattern>();
return scan_for_parsed_pattern(begin, end, parsed_pattern);
}
private:
template<class IteratorType, class ParsedPattern>
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
static IteratorType scan_for_parsed_pattern(const IteratorType& begin, const IteratorType& end,
const ParsedPattern& parsed_pattern)
{
const auto whole_range_size = static_cast<std::ptrdiff_t>(std::distance(begin, end));
const auto pattern_size = static_cast<std::ptrdiff_t>(parsed_pattern.size());
const auto pattern_size = static_cast<std::ptrdiff_t>(parsed_pattern->size());
const std::ptrdiff_t scan_size = whole_range_size - pattern_size;
if (scan_size < 0)
@@ -97,9 +61,9 @@ namespace omath
{
bool found = true;
for (std::ptrdiff_t j = 0; j < static_cast<std::ptrdiff_t>(parsed_pattern.size()); j++)
for (std::ptrdiff_t j = 0; j < static_cast<std::ptrdiff_t>(parsed_pattern->size()); j++)
{
found = parsed_pattern.at(j) == std::nullopt || parsed_pattern.at(j) == *(begin + i + j);
found = parsed_pattern->at(j) == std::nullopt || parsed_pattern->at(j) == *(begin + i + j);
if (!found)
break;
@@ -109,97 +73,10 @@ namespace omath
}
return end;
}
private:
[[nodiscard]]
static std::expected<std::vector<std::optional<std::byte>>, PatternScanError>
parse_pattern(const std::string_view& pattern_string);
[[nodiscard]]
constexpr static bool is_space(const char c)
{
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
[[nodiscard]]
constexpr static int hex_value(const char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return -1;
}
template<ConstevalPattern Pattern>
[[nodiscard]]
static consteval std::size_t signature_size()
{
std::size_t count = 0;
bool in_token = false;
for (std::size_t i = 0; i + 1 < sizeof(Pattern.value); ++i)
{
if (is_space(Pattern.value[i]))
{
in_token = false;
}
else if (!in_token)
{
++count;
in_token = true;
}
}
return count;
}
template<ConstevalPattern Pattern>
static consteval std::array<std::optional<std::byte>, signature_size<Pattern>()> parse_pattern()
{
std::array<std::optional<std::byte>, signature_size<Pattern>()> result{};
std::size_t out = 0;
std::size_t i = 0;
while (i + 1 < sizeof(Pattern.value))
{
while (i + 1 < sizeof(Pattern.value) && is_space(Pattern.value[i]))
++i;
const std::size_t token_start = i;
while (i + 1 < sizeof(Pattern.value) && !is_space(Pattern.value[i]))
++i;
const std::size_t token_size = i - token_start;
if (token_size == 0)
continue;
// ReSharper disable once CppTooWideScope
const bool is_wildcard = (token_size == 1 || token_size == 2) && Pattern.value[token_start] == '?';
if (is_wildcard)
{
if (token_size == 2 && Pattern.value[token_start + 1] != '?')
throw std::logic_error("invalid wildcard token");
result[out++] = std::nullopt;
continue;
}
if (token_size != 2)
throw std::logic_error("invalid byte token");
const int high = hex_value(Pattern.value[token_start]);
const int low = hex_value(Pattern.value[token_start + 1]);
if (high < 0 || low < 0)
throw std::logic_error("invalid hex byte");
result[out++] = static_cast<std::byte>((high << 4) | low);
}
return result;
}
};
} // namespace omath
} // namespace omath
+2 -63
View File
@@ -3,13 +3,12 @@
//
#pragma once
#include "pattern_scan.hpp"
#include "section_scan_result.hpp"
#include <cstdint>
#include <filesystem>
#include <optional>
#include <span>
#include <string_view>
#include "section_scan_result.hpp"
namespace omath
{
@@ -21,74 +20,14 @@ namespace omath
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_loaded_module(module_base_address, target_section_name,
&PePatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_file(path_to_file, target_section_name,
&PePatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_memory_file(file_data, target_section_name,
&PePatternScanner::scan_section_for_pattern<Pattern>);
}
private:
using SectionScanFunction = std::optional<std::ptrdiff_t> (*)(std::span<const std::byte>);
template<PatternScanner::ConstevalPattern Pattern>
[[nodiscard]]
static std::optional<std::ptrdiff_t> scan_section_for_pattern(const std::span<const std::byte> section_data)
{
const auto result = PatternScanner::scan_for_pattern<Pattern>(section_data.begin(), section_data.end());
if (result == section_data.end())
return std::nullopt;
return result - section_data.begin();
}
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult> scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name, SectionScanFunction scan_pattern);
};
} // namespace omath
} // namespace omath
+23 -2
View File
@@ -1,7 +1,28 @@
site_name: OMATH Docs
theme:
name: darkly
name: material
font:
text: Roboto
code: Roboto Mono
palette:
scheme: slate
primary: black
accent: orange
features:
- navigation.instant
- navigation.tracking
- navigation.sections
- navigation.top
- search.highlight
- content.code.copy
markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.inlinehilite
- pymdownx.superfences
extra_css:
- styles/center.css
- styles/custom-header.css
- styles/links.css
- styles/links.css
+9 -93
View File
@@ -304,8 +304,8 @@ namespace
elf_headers);
}
template<class FileHeader, class SectionHeader, class ScanPattern>
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const ScanPattern scan_pattern,
template<class FileHeader, class SectionHeader>
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const std::string_view pattern,
const std::string_view target_section_name)
{
const auto* file_header = reinterpret_cast<const FileHeader*>(base);
@@ -336,14 +336,13 @@ namespace
continue;
const auto* section_begin = base + static_cast<std::size_t>(section->sh_addr);
const auto scan_range =
std::span<const std::byte>{section_begin, static_cast<std::size_t>(section->sh_size)};
const auto* section_end = section_begin + static_cast<std::size_t>(section->sh_size);
const auto target_offset = scan_pattern(scan_range);
if (!target_offset.has_value())
const auto scan_result = omath::PatternScanner::scan_for_pattern(section_begin, section_end, pattern);
if (scan_result == section_end)
return std::nullopt;
return reinterpret_cast<std::uintptr_t>(section_begin + target_offset.value());
return reinterpret_cast<std::uintptr_t>(scan_result);
}
return std::nullopt;
@@ -375,58 +374,15 @@ namespace omath
if (!arch.has_value()) [[unlikely]]
return std::nullopt;
const auto scan_pattern =
[&pattern](const std::span<const std::byte> section_data) -> std::optional<std::ptrdiff_t>
{
const auto scan_result =
PatternScanner::scan_for_pattern(section_data.begin(), section_data.end(), pattern);
if (scan_result == section_data.end())
return std::nullopt;
return scan_result - section_data.begin();
};
if (arch == FileArch::x64)
return scan_in_module_impl<Elf64Ehdr, Elf64Shdr>(static_cast<const std::byte*>(module_base_address),
scan_pattern, target_section_name);
pattern, target_section_name);
if (arch == FileArch::x32)
return scan_in_module_impl<Elf32Ehdr, Elf32Shdr>(static_cast<const std::byte*>(module_base_address),
scan_pattern, target_section_name);
pattern, target_section_name);
std::unreachable();
}
std::optional<std::uintptr_t>
ElfPatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
if (module_base_address == nullptr) [[unlikely]]
return std::nullopt;
const auto* base = static_cast<const std::byte*>(module_base_address);
constexpr std::string_view valid_elf_signature = "\x7F"
"ELF";
if (std::string_view{reinterpret_cast<const char*>(base), valid_elf_signature.size()} != valid_elf_signature)
return std::nullopt;
const auto ei_class_value = static_cast<uint8_t>(base[ei_class]);
const auto arch = ei_class_value == elfclass64 ? FileArch::x64
: ei_class_value == elfclass32 ? FileArch::x32
: std::optional<FileArch>{};
if (!arch.has_value()) [[unlikely]]
return std::nullopt;
if (arch == FileArch::x64)
return scan_in_module_impl<Elf64Ehdr, Elf64Shdr>(base, scan_pattern, target_section_name);
if (arch == FileArch::x32)
return scan_in_module_impl<Elf32Ehdr, Elf32Shdr>(base, scan_pattern, target_section_name);
std::unreachable();
}
std::optional<SectionScanResult>
ElfPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& pattern,
@@ -449,26 +405,6 @@ namespace omath
.target_offset = offset};
}
std::optional<SectionScanResult>
ElfPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto section = get_elf_section_by_name(path_to_file, target_section_name);
if (!section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset = scan_pattern(std::span<const std::byte>{section->data.data(), section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = section->virtual_base_addr,
.raw_base_addr = section->raw_base_addr,
.target_offset = target_offset.value()};
}
std::optional<SectionScanResult>
ElfPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& pattern,
@@ -491,24 +427,4 @@ namespace omath
.raw_base_addr = section->raw_base_addr,
.target_offset = offset};
}
std::optional<SectionScanResult>
ElfPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto section = get_elf_section_by_name_from_memory(file_data, target_section_name);
if (!section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset = scan_pattern(std::span<const std::byte>{section->data.data(), section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = section->virtual_base_addr,
.raw_base_addr = section->raw_base_addr,
.target_offset = target_offset.value()};
}
} // namespace omath
} // namespace omath
+10 -89
View File
@@ -316,8 +316,8 @@ namespace
data, section_name);
if (magic == mh_magic_32 || magic == mh_cigam_32)
return extract_section_from_memory_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(
data, section_name);
return extract_section_from_memory_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(data,
section_name);
return std::nullopt;
}
@@ -344,9 +344,8 @@ namespace
return extract_section_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(file, section_name);
}
template<typename HeaderType, typename SegmentType, typename SectionType, std::uint32_t segment_cmd,
class ScanPattern>
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const ScanPattern scan_pattern,
template<typename HeaderType, typename SegmentType, typename SectionType, std::uint32_t segment_cmd>
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const std::string_view pattern,
const std::string_view target_section_name)
{
const auto* header = reinterpret_cast<const HeaderType*>(base);
@@ -375,13 +374,12 @@ namespace
continue;
}
const auto* section_begin = base + static_cast<std::size_t>(section->addr);
const auto scan_range =
std::span<const std::byte>{section_begin, static_cast<std::size_t>(section->size)};
const auto* section_end = section_begin + static_cast<std::size_t>(section->size);
const auto target_offset = scan_pattern(scan_range);
const auto scan_result = omath::PatternScanner::scan_for_pattern(section_begin, section_end, pattern);
if (target_offset.has_value())
return reinterpret_cast<std::uintptr_t>(section_begin + target_offset.value());
if (scan_result != section_end)
return reinterpret_cast<std::uintptr_t>(scan_result);
}
}
@@ -406,48 +404,12 @@ namespace omath
std::uint32_t magic{};
std::memcpy(&magic, base, sizeof(magic));
const auto scan_pattern =
[&pattern](const std::span<const std::byte> section_data) -> std::optional<std::ptrdiff_t>
{
const auto scan_result =
PatternScanner::scan_for_pattern(section_data.begin(), section_data.end(), pattern);
if (scan_result == section_data.end())
return std::nullopt;
return scan_result - section_data.begin();
};
if (magic == mh_magic_64 || magic == mh_cigam_64)
return scan_in_module_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(base, scan_pattern,
return scan_in_module_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(base, pattern,
target_section_name);
if (magic == mh_magic_32 || magic == mh_cigam_32)
return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, scan_pattern,
target_section_name);
return std::nullopt;
}
std::optional<std::uintptr_t>
MachOPatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
if (module_base_address == nullptr) [[unlikely]]
return std::nullopt;
const auto* base = static_cast<const std::byte*>(module_base_address);
std::uint32_t magic{};
std::memcpy(&magic, base, sizeof(magic));
if (magic == mh_magic_64 || magic == mh_cigam_64)
return scan_in_module_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(base, scan_pattern,
target_section_name);
if (magic == mh_magic_32 || magic == mh_cigam_32)
return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, scan_pattern,
return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, pattern,
target_section_name);
return std::nullopt;
@@ -476,27 +438,6 @@ namespace omath
.target_offset = offset};
}
std::optional<SectionScanResult>
MachOPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto macho_section = get_macho_section_by_name(path_to_file, target_section_name);
if (!macho_section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset =
scan_pattern(std::span<const std::byte>{macho_section->data.data(), macho_section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = macho_section->virtual_base_addr,
.raw_base_addr = macho_section->raw_base_addr,
.target_offset = target_offset.value()};
}
std::optional<SectionScanResult>
MachOPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& pattern,
@@ -519,24 +460,4 @@ namespace omath
.raw_base_addr = section->raw_base_addr,
.target_offset = offset};
}
std::optional<SectionScanResult>
MachOPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto section = get_macho_section_by_name_from_memory(file_data, target_section_name);
if (!section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset = scan_pattern(std::span<const std::byte>{section->data.data(), section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = section->virtual_base_addr,
.raw_base_addr = section->raw_base_addr,
.target_offset = target_offset.value()};
}
} // namespace omath
+7 -111
View File
@@ -234,12 +234,8 @@ namespace
constexpr bool invalid_nt_header_file(const NtHeaderVariant& variant)
{
constexpr std::uint32_t nt_hdr_magic = 0x4550;
return std::visit(
[&nt_hdr_magic](const auto& header) -> bool
{
return header.signature != nt_hdr_magic;
},
variant);
return std::visit([&nt_hdr_magic](const auto& header) -> bool { return header.signature != nt_hdr_magic; },
variant);
}
struct ExtractedSection final
@@ -266,7 +262,8 @@ namespace
if (nt_off + sizeof(ImageNtHeaders<NtArchitecture::x32_bit>) > data.size())
return std::nullopt;
const auto* x86_hdrs = reinterpret_cast<const ImageNtHeaders<NtArchitecture::x32_bit>*>(data.data() + nt_off);
const auto* x86_hdrs =
reinterpret_cast<const ImageNtHeaders<NtArchitecture::x32_bit>*>(data.data() + nt_off);
NtHeaderVariant nt_headers;
if (x86_hdrs->optional_header.magic == opt_hdr32_magic)
@@ -287,8 +284,8 @@ namespace
[&data, &section_name, nt_off](const auto& concrete_headers) -> std::optional<ExtractedSection>
{
constexpr std::size_t sig_size = sizeof(concrete_headers.signature);
const auto section_table_off =
nt_off + sig_size + sizeof(FileHeader) + concrete_headers.file_header.size_optional_header;
const auto section_table_off = nt_off + sig_size + sizeof(FileHeader)
+ concrete_headers.file_header.size_optional_header;
for (std::size_t i = 0; i < concrete_headers.file_header.num_sections; ++i)
{
@@ -438,65 +435,6 @@ namespace omath
},
nt_header_variant.value());
}
std::optional<std::uintptr_t>
PePatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto base_address = reinterpret_cast<std::uintptr_t>(module_base_address);
const auto* base_bytes = static_cast<const std::byte*>(module_base_address);
if (!base_address)
return std::nullopt;
const auto* dos_header = static_cast<const DosHeader*>(module_base_address);
if (invalid_dos_header_file(*dos_header)) [[unlikely]]
return std::nullopt;
const auto nt_header_variant = get_nt_header_from_loaded_module(module_base_address);
if (!nt_header_variant) [[unlikely]]
return std::nullopt;
return std::visit(
[base_bytes, base_address, lfanew = dos_header->e_lfanew, &target_section_name,
scan_pattern](const auto& nt_header) -> std::optional<std::uintptr_t>
{
constexpr std::size_t signature_size = sizeof(nt_header.signature);
const auto section_table_off = static_cast<std::size_t>(lfanew) + signature_size
+ sizeof(FileHeader) + nt_header.file_header.size_optional_header;
const auto* section_table = reinterpret_cast<const SectionHeader*>(base_bytes + section_table_off);
for (std::size_t i = 0; i < nt_header.file_header.num_sections; ++i)
{
const auto* section = section_table + i;
if (std::string_view{section->name, sizeof(section->name)} != target_section_name
|| section->size_raw_data == 0)
continue;
const auto section_size = section->virtual_size != 0
? static_cast<std::size_t>(section->virtual_size)
: static_cast<std::size_t>(section->size_raw_data);
const auto* section_begin =
reinterpret_cast<const std::byte*>(base_address + section->virtual_address);
const auto scan_range = std::span<const std::byte>{section_begin, section_size};
const auto target_offset = scan_pattern(scan_range);
if (target_offset.has_value())
return reinterpret_cast<std::uintptr_t>(section_begin + target_offset.value());
}
return std::nullopt;
},
nt_header_variant.value());
}
std::optional<SectionScanResult>
PePatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& pattern,
@@ -519,27 +457,6 @@ namespace omath
.target_offset = offset};
}
std::optional<SectionScanResult>
PePatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto pe_section = extract_section_from_pe_file(path_to_file, target_section_name);
if (!pe_section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset =
scan_pattern(std::span<const std::byte>{pe_section->data.data(), pe_section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
.raw_base_addr = pe_section->raw_base_addr,
.target_offset = target_offset.value()};
}
std::optional<SectionScanResult>
PePatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& pattern,
@@ -562,25 +479,4 @@ namespace omath
.raw_base_addr = pe_section->raw_base_addr,
.target_offset = offset};
}
std::optional<SectionScanResult>
PePatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto pe_section = extract_section_from_pe_memory(file_data, target_section_name);
if (!pe_section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset =
scan_pattern(std::span<const std::byte>{pe_section->data.data(), pe_section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
.raw_base_addr = pe_section->raw_base_addr,
.target_offset = target_offset.value()};
}
} // namespace omath
} // namespace omath
+20 -65
View File
@@ -35,22 +35,13 @@ static std::vector<std::byte> make_elf64_with_text_section(const std::vector<std
std::vector<std::byte> buf(total_size, std::byte{0});
auto w8 = [&](std::size_t off, std::uint8_t v)
{
buf[off] = std::byte{v};
};
auto w8 = [&](std::size_t off, std::uint8_t v) { buf[off] = std::byte{v}; };
auto w16 = [&](std::size_t off, std::uint16_t v)
{
std::memcpy(buf.data() + off, &v, 2);
};
{ std::memcpy(buf.data() + off, &v, 2); };
auto w32 = [&](std::size_t off, std::uint32_t v)
{
std::memcpy(buf.data() + off, &v, 4);
};
{ std::memcpy(buf.data() + off, &v, 4); };
auto w64 = [&](std::size_t off, std::uint64_t v)
{
std::memcpy(buf.data() + off, &v, 8);
};
{ std::memcpy(buf.data() + off, &v, 8); };
// --- ELF64 file header ---
// e_ident
@@ -62,19 +53,19 @@ static std::vector<std::byte> make_elf64_with_text_section(const std::vector<std
w8(5, 1); // ELFDATA2LSB
w8(6, 1); // EV_CURRENT
// rest of e_ident is 0
w16(16, 2); // e_type = ET_EXEC
w16(16, 2); // e_type = ET_EXEC
w16(18, 62); // e_machine = EM_X86_64
w32(20, 1); // e_version
w64(24, 0); // e_entry
w64(32, 0); // e_phoff
w32(20, 1); // e_version
w64(24, 0); // e_entry
w64(32, 0); // e_phoff
w64(40, static_cast<std::uint64_t>(shdr_table_off)); // e_shoff
w32(48, 0); // e_flags
w32(48, 0); // e_flags
w16(52, 64); // e_ehsize
w16(54, 56); // e_phentsize
w16(56, 0); // e_phnum
w16(56, 0); // e_phnum
w16(58, static_cast<std::uint16_t>(shdr_size)); // e_shentsize
w16(60, static_cast<std::uint16_t>(num_sections)); // e_shnum
w16(62, 2); // e_shstrndx = 2 (.shstrtab is section index 2)
w16(62, 2); // e_shstrndx = 2 (.shstrtab is section index 2)
// --- section data (.text) ---
const std::size_t copy_len = std::min(code_bytes.size(), text_size);
@@ -113,9 +104,9 @@ static std::vector<std::byte> make_elf64_with_text_section(const std::vector<std
// Section 1: .text
{
const std::size_t base = shdr_table_off + 1 * shdr_size;
w32(base + 0, 1); // sh_name → index 1 in shstrtab → ".text"
w32(base + 4, 1); // sh_type = SHT_PROGBITS
w64(base + 8, 6); // sh_flags = SHF_ALLOC|SHF_EXECINSTR
w32(base + 0, 1); // sh_name → index 1 in shstrtab → ".text"
w32(base + 4, 1); // sh_type = SHT_PROGBITS
w64(base + 8, 6); // sh_flags = SHF_ALLOC|SHF_EXECINSTR
w64(base + 16, static_cast<std::uint64_t>(text_off)); // sh_addr (same as offset in test)
w64(base + 24, static_cast<std::uint64_t>(text_off)); // sh_offset
w64(base + 32, static_cast<std::uint64_t>(text_size)); // sh_size
@@ -125,8 +116,8 @@ static std::vector<std::byte> make_elf64_with_text_section(const std::vector<std
// Section 2: .shstrtab
{
const std::size_t base = shdr_table_off + 2 * shdr_size;
w32(base + 0, 0); // sh_name → index 0 → "" (good enough for scanner)
w32(base + 4, 3); // sh_type = SHT_STRTAB
w32(base + 0, 0); // sh_name → index 0 → "" (good enough for scanner)
w32(base + 4, 3); // sh_type = SHT_STRTAB
w64(base + 24, static_cast<std::uint64_t>(shstrtab_off)); // sh_offset
w64(base + 32, static_cast<std::uint64_t>(shstrtab_size)); // sh_size
}
@@ -160,18 +151,6 @@ TEST(unit_test_elf_pattern_scan_memory, finds_pattern_with_wildcard)
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_elf_pattern_scan_memory, consteval_finds_pattern_with_wildcard)
{
const std::vector<std::uint8_t> code = {0x00, 0xDE, 0xAD, 0xBE, 0xEF};
const auto buf = make_elf64_with_text_section(code);
const auto result =
ElfPatternScanner::scan_for_pattern_in_memory_file<"DE ?? BE EF">(std::span<const std::byte>{buf}, ".text");
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 1);
}
TEST(unit_test_elf_pattern_scan_memory, pattern_not_found_returns_nullopt)
{
const std::vector<std::uint8_t> code = {0x01, 0x02, 0x03, 0x04};
@@ -203,8 +182,8 @@ TEST(unit_test_elf_pattern_scan_memory, missing_section_returns_nullopt)
const std::vector<std::uint8_t> code = {0x90, 0x90};
const auto buf = make_elf64_with_text_section(code);
const auto result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf}, "90 90",
".nonexistent");
const auto result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf},
"90 90", ".nonexistent");
EXPECT_FALSE(result.has_value());
}
@@ -222,8 +201,8 @@ TEST(unit_test_elf_pattern_scan_memory, matches_file_scan)
}
const auto file_result = ElfPatternScanner::scan_for_pattern_in_file(tmp_path, "48 89 E5 DE AD", ".text");
const auto mem_result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf},
"48 89 E5 DE AD", ".text");
const auto mem_result =
ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf}, "48 89 E5 DE AD", ".text");
std::filesystem::remove(tmp_path);
@@ -233,27 +212,3 @@ TEST(unit_test_elf_pattern_scan_memory, matches_file_scan)
EXPECT_EQ(file_result->raw_base_addr, mem_result->raw_base_addr);
EXPECT_EQ(file_result->target_offset, mem_result->target_offset);
}
TEST(unit_test_elf_pattern_scan_memory, consteval_file_scan_finds_pattern)
{
const std::vector<std::uint8_t> code = {0x48, 0x89, 0xE5, 0xDE, 0xAD};
const auto buf = make_elf64_with_text_section(code);
const auto tmp_path = std::filesystem::temp_directory_path() / "omath_elf_consteval_test.elf";
{
std::ofstream out(tmp_path, std::ios::binary);
out.write(reinterpret_cast<const char*>(buf.data()), static_cast<std::streamsize>(buf.size()));
}
const auto result = ElfPatternScanner::scan_for_pattern_in_file<"48 ?? E5">(tmp_path, ".text");
std::filesystem::remove(tmp_path);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_elf_pattern_scan_memory, consteval_loaded_module_null_returns_nullopt)
{
const auto result = ElfPatternScanner::scan_for_pattern_in_loaded_module<"DE AD">(nullptr);
EXPECT_FALSE(result.has_value());
}
@@ -1,7 +1,5 @@
// Tests for MachOPatternScanner::scan_for_pattern_in_memory_file
#include <cstring>
#include <filesystem>
#include <fstream>
#include <gtest/gtest.h>
#include <omath/utility/macho_pattern_scan.hpp>
#include <span>
@@ -39,43 +37,38 @@ static std::vector<std::byte> make_macho64_with_text_section(const std::vector<s
constexpr std::size_t total_size = text_raw_off + text_raw_size;
constexpr std::uint64_t text_vmaddr = 0x1000ULL;
constexpr std::uint32_t cmd_size = static_cast<std::uint32_t>(seg_size + sect_hdr_size); // segment + 1 section
constexpr std::uint32_t cmd_size =
static_cast<std::uint32_t>(seg_size + sect_hdr_size); // segment + 1 section
std::vector<std::byte> buf(total_size, std::byte{0});
auto w32 = [&](std::size_t off, std::uint32_t v)
{
std::memcpy(buf.data() + off, &v, 4);
};
auto w64 = [&](std::size_t off, std::uint64_t v)
{
std::memcpy(buf.data() + off, &v, 8);
};
auto w32 = [&](std::size_t off, std::uint32_t v) { std::memcpy(buf.data() + off, &v, 4); };
auto w64 = [&](std::size_t off, std::uint64_t v) { std::memcpy(buf.data() + off, &v, 8); };
// MachHeader64
w32(0, mh_magic_64);
w32(4, 0x0100000C); // cputype = CPU_TYPE_ARM64 (doesn't matter for scan)
w32(12, 2); // filetype = MH_EXECUTE
w32(16, 1); // ncmds = 1
w32(20, cmd_size); // sizeofcmds
w32(4, 0x0100000C); // cputype = CPU_TYPE_ARM64 (doesn't matter for scan)
w32(12, 2); // filetype = MH_EXECUTE
w32(16, 1); // ncmds = 1
w32(20, cmd_size); // sizeofcmds
// SegmentCommand64 at 0x20
constexpr std::size_t seg_off = hdr_size;
w32(seg_off + 0, lc_segment_64);
w32(seg_off + 4, cmd_size);
std::memcpy(buf.data() + seg_off + 8, "__TEXT", 6); // segname
w64(seg_off + 24, text_vmaddr); // vmaddr
w64(seg_off + 32, text_raw_size); // vmsize
w64(seg_off + 40, text_raw_off); // fileoff
w64(seg_off + 48, text_raw_size); // filesize
w32(seg_off + 64, 1); // nsects
w64(seg_off + 24, text_vmaddr); // vmaddr
w64(seg_off + 32, text_raw_size); // vmsize
w64(seg_off + 40, text_raw_off); // fileoff
w64(seg_off + 48, text_raw_size); // filesize
w32(seg_off + 64, 1); // nsects
// Section64 at 0x68
constexpr std::size_t sect_off = seg_off + seg_size;
std::memcpy(buf.data() + sect_off + 0, "__text", 6); // sectname
std::memcpy(buf.data() + sect_off + 0, "__text", 6); // sectname
std::memcpy(buf.data() + sect_off + 16, "__TEXT", 6); // segname
w64(sect_off + 32, text_vmaddr); // addr
w64(sect_off + 40, text_raw_size); // size
w64(sect_off + 32, text_vmaddr); // addr
w64(sect_off + 40, text_raw_size); // size
w32(sect_off + 48, static_cast<std::uint32_t>(text_raw_off)); // offset (file offset)
// Section data
@@ -112,18 +105,6 @@ TEST(unit_test_macho_memory_file_scan, finds_pattern_with_wildcard)
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_macho_memory_file_scan, consteval_finds_pattern_with_wildcard)
{
const std::vector<std::uint8_t> code = {0x00, 0xDE, 0xAD, 0xBE, 0xEF};
const auto buf = make_macho64_with_text_section(code);
const auto result =
MachOPatternScanner::scan_for_pattern_in_memory_file<"DE ?? BE EF">(std::span<const std::byte>{buf});
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 1);
}
TEST(unit_test_macho_memory_file_scan, pattern_not_found_returns_nullopt)
{
const std::vector<std::uint8_t> code = {0x01, 0x02, 0x03};
@@ -162,27 +143,3 @@ TEST(unit_test_macho_memory_file_scan, raw_addr_and_virtual_addr_correct)
EXPECT_EQ(result->raw_base_addr, expected_raw_off);
EXPECT_EQ(result->virtual_base_addr, 0x1000u);
}
TEST(unit_test_macho_memory_file_scan, consteval_file_scan_finds_pattern)
{
const std::vector<std::uint8_t> code = {0x48, 0x89, 0xE5, 0xDE, 0xAD};
const auto buf = make_macho64_with_text_section(code);
const auto tmp_path = std::filesystem::temp_directory_path() / "omath_macho_consteval_test.bin";
{
std::ofstream out(tmp_path, std::ios::binary);
out.write(reinterpret_cast<const char*>(buf.data()), static_cast<std::streamsize>(buf.size()));
}
const auto result = MachOPatternScanner::scan_for_pattern_in_file<"48 ?? E5">(tmp_path);
std::filesystem::remove(tmp_path);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_macho_memory_file_scan, consteval_loaded_module_null_returns_nullopt)
{
const auto result = MachOPatternScanner::scan_for_pattern_in_loaded_module<"DE AD">(nullptr);
EXPECT_FALSE(result.has_value());
}
+2 -36
View File
@@ -4,8 +4,8 @@
#include "omath/utility/pe_pattern_scan.hpp"
#include "gtest/gtest.h"
#include <omath/utility/pattern_scan.hpp>
#include <print>
#include <source_location>
#include <print>
TEST(unit_test_pattern_scan, read_test)
{
const auto result = omath::PatternScanner::parse_pattern("FF ? ?? E9");
@@ -50,38 +50,4 @@ TEST(unit_test_pattern_scan, corner_case_4)
{
const auto result = omath::PatternScanner::parse_pattern("X ? ?? E9 ");
EXPECT_FALSE(result.has_value());
}
TEST(unit_test_pattern_scan, consteval_read_test)
{
constexpr auto result = omath::PatternScanner::parse_pattern<"FF ? ?? E9">();
static_assert(result.size() == 4);
static_assert(result[0] == static_cast<std::byte>(0xFF));
static_assert(result[1] == std::nullopt);
static_assert(result[2] == std::nullopt);
static_assert(result[3] == static_cast<std::byte>(0xE9));
EXPECT_EQ(result[0], static_cast<std::byte>(0xFF));
EXPECT_EQ(result[1], std::nullopt);
EXPECT_EQ(result[2], std::nullopt);
EXPECT_EQ(result[3], static_cast<std::byte>(0xE9));
}
TEST(unit_test_pattern_scan, consteval_spacing_and_case)
{
constexpr auto result = omath::PatternScanner::parse_pattern<" \tde\nAD\r?? ? ef ">();
static_assert(result.size() == 5);
static_assert(result[0] == static_cast<std::byte>(0xDE));
static_assert(result[1] == static_cast<std::byte>(0xAD));
static_assert(result[2] == std::nullopt);
static_assert(result[3] == std::nullopt);
static_assert(result[4] == static_cast<std::byte>(0xEF));
EXPECT_EQ(result[0], static_cast<std::byte>(0xDE));
EXPECT_EQ(result[1], static_cast<std::byte>(0xAD));
EXPECT_EQ(result[2], std::nullopt);
EXPECT_EQ(result[3], std::nullopt);
EXPECT_EQ(result[4], static_cast<std::byte>(0xEF));
}
}
@@ -14,16 +14,6 @@ TEST(unit_test_pattern_scan_extra, IteratorScanFound)
EXPECT_EQ(std::distance(buf.begin(), it), 0);
}
TEST(unit_test_pattern_scan_extra, ConstevalIteratorScan)
{
std::vector<std::byte> buf = {static_cast<std::byte>(0x00), static_cast<std::byte>(0xDE),
static_cast<std::byte>(0xAD), static_cast<std::byte>(0xBE),
static_cast<std::byte>(0xEF), static_cast<std::byte>(0x11)};
const auto it = PatternScanner::scan_for_pattern<"DE ?? BE EF">(buf.begin(), buf.end());
EXPECT_NE(it, buf.end());
EXPECT_EQ(std::distance(buf.begin(), it), 1);
}
TEST(unit_test_pattern_scan_extra, IteratorScanNotFound)
{
std::vector<std::byte> buf = {static_cast<std::byte>(0x00), static_cast<std::byte>(0x11),
+11 -56
View File
@@ -1,7 +1,5 @@
// Tests for PePatternScanner::scan_for_pattern_in_memory_file
#include <cstring>
#include <filesystem>
#include <fstream>
#include <gtest/gtest.h>
#include <omath/utility/pe_pattern_scan.hpp>
#include <span>
@@ -12,7 +10,8 @@ using namespace omath;
// Reuse the fake-module builder from unit_test_pe_pattern_scan_loaded.cpp but
// lay out the buffer as a raw PE *file* (ptr_raw_data != virtual_address).
static std::vector<std::byte> make_fake_pe_file(std::uint32_t virtual_address, std::uint32_t ptr_raw_data,
std::uint32_t section_size, const std::vector<std::uint8_t>& code_bytes)
std::uint32_t section_size,
const std::vector<std::uint8_t>& code_bytes)
{
constexpr std::uint32_t e_lfanew = 0x80;
constexpr std::uint32_t nt_sig = 0x4550;
@@ -25,18 +24,9 @@ static std::vector<std::byte> make_fake_pe_file(std::uint32_t virtual_address, s
const std::uint32_t total_size = ptr_raw_data + section_size + 0x100;
std::vector<std::byte> buf(total_size, std::byte{0});
auto w16 = [&](std::size_t off, std::uint16_t v)
{
std::memcpy(buf.data() + off, &v, 2);
};
auto w32 = [&](std::size_t off, std::uint32_t v)
{
std::memcpy(buf.data() + off, &v, 4);
};
auto w64 = [&](std::size_t off, std::uint64_t v)
{
std::memcpy(buf.data() + off, &v, 8);
};
auto w16 = [&](std::size_t off, std::uint16_t v) { std::memcpy(buf.data() + off, &v, 2); };
auto w32 = [&](std::size_t off, std::uint32_t v) { std::memcpy(buf.data() + off, &v, 4); };
auto w64 = [&](std::size_t off, std::uint64_t v) { std::memcpy(buf.data() + off, &v, 8); };
// DOS header
w16(0x00, 0x5A4D);
@@ -58,10 +48,10 @@ static std::vector<std::byte> make_fake_pe_file(std::uint32_t virtual_address, s
// Section header (.text)
const std::size_t sh_off = section_table_off;
std::memcpy(buf.data() + sh_off, ".text", 5);
w32(sh_off + 8, section_size); // VirtualSize
w32(sh_off + 12, virtual_address); // VirtualAddress
w32(sh_off + 16, section_size); // SizeOfRawData
w32(sh_off + 20, ptr_raw_data); // PointerToRawData
w32(sh_off + 8, section_size); // VirtualSize
w32(sh_off + 12, virtual_address); // VirtualAddress
w32(sh_off + 16, section_size); // SizeOfRawData
w32(sh_off + 20, ptr_raw_data); // PointerToRawData
// Place code at raw file offset
const std::size_t copy_len = std::min(code_bytes.size(), static_cast<std::size_t>(section_size));
@@ -97,24 +87,13 @@ TEST(unit_test_pe_memory_file_scan, finds_pattern_with_wildcard)
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_pe_memory_file_scan, consteval_finds_pattern_with_wildcard)
{
const std::vector<std::uint8_t> code = {0x00, 0xDE, 0xAD, 0xBE, 0xEF};
const auto buf = make_fake_pe_file(0x2000, 0x600, static_cast<std::uint32_t>(code.size()), code);
const auto result =
PePatternScanner::scan_for_pattern_in_memory_file<"DE ?? BE EF">(std::span<const std::byte>{buf});
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 1);
}
TEST(unit_test_pe_memory_file_scan, pattern_not_found_returns_nullopt)
{
const std::vector<std::uint8_t> code = {0x01, 0x02, 0x03};
const auto buf = make_fake_pe_file(0x1000, 0x400, static_cast<std::uint32_t>(code.size()), code);
const auto result = PePatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf}, "AA BB CC");
const auto result =
PePatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf}, "AA BB CC");
EXPECT_FALSE(result.has_value());
}
@@ -147,27 +126,3 @@ TEST(unit_test_pe_memory_file_scan, raw_addr_differs_from_virtual_address)
// virtual_base_addr = virtual_address + image_base (image_base = 0)
EXPECT_EQ(result->virtual_base_addr, 0x3000u);
}
TEST(unit_test_pe_memory_file_scan, consteval_file_scan_finds_pattern)
{
const std::vector<std::uint8_t> code = {0x48, 0x89, 0xE5, 0xDE, 0xAD};
const auto buf = make_fake_pe_file(0x2000, 0x600, static_cast<std::uint32_t>(code.size()), code);
const auto tmp_path = std::filesystem::temp_directory_path() / "omath_pe_consteval_test.exe";
{
std::ofstream out(tmp_path, std::ios::binary);
out.write(reinterpret_cast<const char*>(buf.data()), static_cast<std::streamsize>(buf.size()));
}
const auto result = PePatternScanner::scan_for_pattern_in_file<"48 ?? E5">(tmp_path);
std::filesystem::remove(tmp_path);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_pe_memory_file_scan, consteval_loaded_module_null_returns_nullopt)
{
const auto result = PePatternScanner::scan_for_pattern_in_loaded_module<"DE AD">(nullptr);
EXPECT_FALSE(result.has_value());
}