Skip to content

HoShiMin/Arch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🧩 Arch

It's a bit-perfect (and maybe the most complete in some cases) bunch of architectural headers for i386/AMD64 platform written in C++17.

📜 Supported structures and layouts:

  • Memory paging for all possible regimes and page sizes:
    • Regimes:
      • Legacy Non-PAE with and without CR4.PSE (4Kb and 4Mb pages).
      • Legacy PAE (4Kb and 2Mb pages).
      • Long-mode 4-Level (4Kb, 2Mb and 1Gb pages).
      • Long-mode 5-Level (4Kb, 2Mb and 1Gb pages).
    • With definitions for all possible page tables and entries:
      • PML5 table with PML5E entries introduced with Intel 10th Gen (Ice Lake) and AMD Epyc 7xxx "Genoa" (Zen4).
      • PML4 table with PML4E entries.
      • PDP table with PDPE entries (also known as PDPT table and PDPTE entries in Intel terms) including PageSize forms.
      • PD table with PDE entries including PageSize forms.
      • PT table with PTE entries.
  • Segmentation:
    • GDT - Global Descriptor Table.
    • IDT - Interrupt Descriptor Table.
    • LDT - Local Descriptor Table.
    • TSS - Task State Segment.
    • Segment selector (format of CS, DS, GS, FS, ES, SS and TR registers).
    • Descriptor table register (IDTR, GDTR and LDTR registers).
    • Descriptors (system- and user-segments and gates for all possible interpretations).
  • System registers:
    • EFlags.
    • Control registers: CR0, CR, CR3, CR4, CR8.
    • Debug registers: DR0..DR7.
  • Interrupt vectors.
  • CPUID with some leaves specific for Intel and AMD.
  • MSRs (Model Specific Registers) for Intel and AMD:
    • All possible MSR addresses for Intel and AMD.
    • Layouts for some of them (including Memory Type Range Registers (MTRRs) for Intel).
  • Architectural intrinsics for MSVC and CLang.
  • Virtualization:
    • Intel VT-x (also known as Intel VMX):
      • VMCS - Virtual Machine Control Structure.
      • Exit reasons.
      • EPT tables.
      • MSR permission map.
      • VMENTRY configuration.
      • VMEXIT qualification.
    • AMD-V (also known as AMD SVM):
      • VMCB - Virtual Machine Control Block.
      • Exit reasons.
      • NPT - Nested Page Tables (also known as AMD-RVI) which are the same as normal page tables.
      • MSR permission map.
      • Event injection layout.
    • A bit of Hyper-V:
      • Hypercall layout.
      • Hypercall result values.
      • Hypercall codes.
      • Hypervisor status.
      • Synthetic MSRs.

🏗️ Requirements:

  • MSVC or Clang (GCC has not tested).
  • C++17 or above.
  • Both usermode and kernelmode are supported.

✍️ How to?

Just add the folder Arch/include to additional headers path and you're ready to go!
Let's start with CPUID example:

#include <Arch/x86/Cpuid.h>

int main()
{
    const auto basicInfo = Cpuid::Cpuid::query<Cpuid::Generic::MaximumFunctionNumberAndVendorId>();
    if (basicInfo->isIntel())
    {
        const auto features = Cpuid::Cpuid::query<Cpuid::Intel::FeatureInformation>();
        printf("SSE 4.2 is supported: %u\n", features->SSE42);
    }
    else if (basicInfo->isAmd())
    {
        const auto features = Cpuid::Cpuid::query<Cpuid::Amd::FeatureInformation>();
        printf("SSE 4.2 is supported: %u\n", features->SSE42);
    }
    else
    {
        /* Unknown CPU */
    }
}

All right, let's go to kernel and try MSRs and control registers:

#include <Arch/x86/Registers.h>

void test()
{
    const auto efer = Msr::Msr::read<Msr::Intel::Ia32Efer>();

    if (efer->LME && efer->LMA) // Is long-mode enabled and active?
    {
        const auto cr3 = Regs::Native::Cr3::query();
        const auto cr4 = Regs::Native::Cr4::query();
        if (cr4->layout.LA57)
        {
            // 5-Level paging:
            const auto pml5pfn = cr3->paging5Level.PML5;
            ...
        }
        else
        {
            // 4-Level paging:
            const auto pml4pfn = cr3->paging4Level.PML4;
            ...
        }
    }
}

Well done! Let's try page traversal through a page table.
Suppose we're in long 4-Level paging mode and the page is presented - checks for presense of a page were omitted for the simplicity:

#include <ntifs.h>
#include <Arch/x86/Pte.h>

template <typename Type>
typename Type::Layout* phys2virt(const Type phys)
{
    return MmGetVirtualForPhysical(PHYSICAL_ADDRESS{ .QuadPart = static_cast<long long>(phys); });
}

unsigned long long getPhysicalAddress(void* virtualAddress)
{
    constexpr auto k_mode = Pte::Mode::longMode4Level;

    using LinearAddress = Pte::LinearAddress<k_mode>;
    using Tables = Pte::Tables<k_mode>;

    const LinearAddress addr{ .raw = reinterpret_cast<size_t>(virtualAddress) };
    const auto cr3Pfn = Regs::Native::Cr3::query()->paging4Level.PML4;

    const auto* const pml4e = phys2virt(Tables::pml4e(cr3Pfn, addr));
    const auto* const pdpe = phys2virt(pml4e->pdpe(addr));
    switch (pdpe->pageSize())
    {
    case Pte::PageSize::nonPse:
    {
        const auto* const pde = phys2virt(pdpe->nonPse.pde(addr));
        switch (pde->pageSize())
        {
        case Pte::PageSize::nonPse:
        {
            // 4Kb:
            const auto* const pte = phys2virt(pde->nonPse.pte(addr));
            const auto phys = pte->physicalAddress(addr);
            return phys.physicalAddress;
        }
        case Pte::PageSize::pse:
        {
            // 2Mb:
            const auto phys = pde->pse.physicalAddress(addr);
            return phys.physicalAddress;
        }
        }
        break;
    }
    case Pte::PageSize::pse:
    {
        // 1Gb:
        const auto phys = pdpe->pse.physicalAddress(addr);
        return phys.physicalAddress;
    }
    }

    return 0; // Invalid translation
}