#pragma once #include #include #include "emulator.hpp" #include "x86_register.hpp" namespace segment_utils { enum class segment_bitness { bit16 = 16, bit32 = 32, bit64 = 64, }; #pragma pack(push, 1) struct raw_segment_descriptor { uint16_t limit_low; uint16_t base_low; uint8_t base_mid; uint8_t access; uint8_t limit_high_flags; uint8_t base_high; }; #pragma pack(pop) struct descriptor { uint64_t base{}; uint32_t limit{}; bool present{}; bool system{}; uint8_t type{}; bool long_mode{}; bool default_op_size{}; }; inline std::optional read_descriptor_table(emulator& cpu, const x86_register reg) { cpu_interface::descriptor_table_register table{}; if (!cpu.read_descriptor_table(static_cast(reg), table)) { return std::nullopt; } return table; } inline std::optional read_selector(emulator& cpu, const x86_register reg) { uint16_t selector{}; const auto bytes_read = cpu.read_raw_register(static_cast(reg), &selector, sizeof(selector)); if (bytes_read < sizeof(selector)) { return std::nullopt; } return selector; } inline std::optional read_descriptor(emulator& cpu, const cpu_interface::descriptor_table_register& table, const uint16_t selector) { const auto index = selector >> 3; const auto byte_offset = static_cast(index) * sizeof(raw_segment_descriptor); const auto table_size = static_cast(table.limit) + 1; if (byte_offset + sizeof(raw_segment_descriptor) > table_size) { return std::nullopt; } raw_segment_descriptor raw{}; cpu.read_memory(table.base + byte_offset, &raw, sizeof(raw)); descriptor desc{}; uint64_t base = raw.base_low; base |= static_cast(raw.base_mid) << 16; base |= static_cast(raw.base_high) << 24; desc.base = base; const auto limit_high = static_cast(raw.limit_high_flags & 0x0F); uint32_t limit = raw.limit_low | (limit_high << 16); const bool granularity = (raw.limit_high_flags & 0x80) != 0; if (granularity) { limit = (limit << 12) | 0xFFF; } desc.limit = limit; desc.present = (raw.access & 0x80) != 0; desc.system = (raw.access & 0x10) == 0; desc.type = static_cast(raw.access & 0x0F); desc.long_mode = (raw.limit_high_flags & 0x20) != 0; desc.default_op_size = (raw.limit_high_flags & 0x40) != 0; if (desc.system) { const bool needs_high_base = desc.type == 0x2 || desc.type == 0x9 || desc.type == 0xB; if (needs_high_base) { if (byte_offset + (2 * sizeof(raw_segment_descriptor)) > table_size) { return std::nullopt; } uint32_t base_high{}; cpu.read_memory(table.base + byte_offset + sizeof(raw_segment_descriptor), &base_high, sizeof(base_high)); desc.base |= static_cast(base_high) << 32; } } return desc; } inline std::optional resolve_table(emulator& cpu, const uint16_t selector) { auto gdt = read_descriptor_table(cpu, x86_register::gdtr); if (!gdt) { return std::nullopt; } const bool table_indicator = (selector & 0x4) != 0; if (!table_indicator) { return gdt; } auto ldtr_selector = read_selector(cpu, x86_register::ldtr); if (!ldtr_selector || ((*ldtr_selector) & ~0x3u) == 0) { return std::nullopt; } auto ldt_descriptor = read_descriptor(cpu, *gdt, *ldtr_selector); if (!ldt_descriptor || !ldt_descriptor->present || !ldt_descriptor->system || ldt_descriptor->type != 0x2) { return std::nullopt; } cpu_interface::descriptor_table_register ldt{}; ldt.base = ldt_descriptor->base; ldt.limit = ldt_descriptor->limit; return ldt; } inline std::optional get_segment_bitness(emulator& cpu, const uint16_t selector) { if ((selector & ~0x3u) == 0) { return std::nullopt; } auto table = resolve_table(cpu, selector); if (!table) { return std::nullopt; } auto desc = read_descriptor(cpu, *table, selector); if (!desc || !desc->present || desc->system || (desc->type & 0x8) == 0) { return std::nullopt; } if (desc->long_mode) { return segment_bitness::bit64; } if (desc->default_op_size) { return segment_bitness::bit32; } return segment_bitness::bit16; } }