├── README.md ├── img └── unifix.gif ├── qiling ├── gdb.py ├── unicorn │ ├── 2.7z │ ├── unicorn.py │ └── unicorn_const.py └── utils.py └── unicorn-whpx-core ├── softmmuwhpx ├── cpus.c ├── exec-vary.c ├── exec.c ├── ioport.c ├── memory.c ├── memory_mapping.c └── vl.c ├── target ├── DoubleLinkedList.cpp ├── DoubleLinkedList.hpp ├── WinHvAllocationTracker.cpp ├── WinHvAllocationTracker.hpp ├── WinHvDefs.hpp ├── WinHvHelpers.cpp ├── WinHvHelpers.hpp ├── WinHvMemory.cpp ├── WinHvMemory.hpp ├── WinHvMemoryInternal.cpp ├── WinHvMemoryInternal.hpp ├── WinHvMemoryPrivate.cpp ├── WinHvMemoryPrivate.hpp ├── unicorn-whpx.c ├── unicorn-whpx.h └── whpx-internal.h └── x86_64_whpx.h /README.md: -------------------------------------------------------------------------------- 1 | ## 引用 ## 2 | 3 | >这篇文章的目的是介绍基于Windows Hyper-V虚拟机平台Hypervisor Platform API实现的魔改版Unicorn Engine模拟器和调试引擎开发心得 4 | 5 | [toc] 6 | 7 | ## 简介 ## 8 | 9 | 跨平台模拟器unicorn框架基于Qemu的TCG模式(Tiny Code Generator),以无硬件虚拟化支持方式实现全系统的虚拟化,支持跨平台和架构的CPU指令模拟,本文讨论是一款笔者的实验性项目采用Windows Hypervisor Platform虚拟机模式提供了另一种CPU指令的模拟方式,在保持原有unicorn导出接口不变的情况下,采用Hyper-V支持带硬件虚拟化支持的Windows Hypervisor Platform API接口扩展了底层CPU模拟环境实现,支持X86指令集的二进制程序模拟平台和调试器. 10 | 11 | ## unicorn框架qemu之Hyper-v模式比较 ## 12 | 13 | Windows Hypervisor Platform是微软在Hyper-V平台提供一种新的API库,用于允许第三方开发者创建和管理EXO分区.EXO分区是一种与Hyper-V兼容并可以同时运行的虚拟机分区,用于支持第三方开发者的虚拟化解决方案,如VirtualBox、Qemu、VMware等,采用虚拟机容器的方式模拟客户机整个操作系统虚拟环境. 14 | 跨平台模拟执行unicorn框架和上层qiling框架都是基于qemu的TCG模式(Tiny Code Generator),支持无硬件虚拟化支持方式在源ISA(处理器架构)和目标ISA不同的情况下CPU指令模拟,类似一个jit解释器,一个循环中不断的读入源ISA程序指令,QEMU先转换成源ISA的IR,反汇编并用代码在目标ISA编译后的IR在模拟TranslationBlock指令中执行,当然这些指令也是转换后的汇编模式比起直接调用c函数模拟可以优化效率,qemu对TranslationBlock在分支执行返回后切换到Qemu上下文保存虚拟环境状态继续下个分支执行,转换过程采用内联汇编的方式支持hook断点与内存监视trace等功能.如果切换成Windows Hypervisor Platform(以下简称Hyper-v虚拟机)模式就省去了模拟cpu指令的环节,真实的物理cpu被虚拟化成vcpu,这个逻辑封装成由Hypervisor API创建虚拟机的调度者调度和物理机共享cpu资源,API底层实现又由Hyper-v自己的调度器(Hypervisor)的Hvix64.exe模块实现,源ISA的指令运行在vcpu上,看起来就像在物理cpu一样.每个hyper-v分区实例是一个相对于其他分区隔离的虚拟环境,由WHvCreatePartition函数创建分区,这个分区通过VID.sys(Virtualization Infrastructure Driver)驱动向管理者也就是是被创建分区的父分区hv(Hypervisor)模块通信抽象成上层api交给调用者调度,hv模块同样也有自己的分区与其他分区隔离,如果要调试hv模块可以通过bcdedit /set hypervisordebug on方式(具体见引用节)启用2个windbg实例调试内核和hv.如果是在物理机上,物理机的操作系统运行在由根分区hv管理创建的虚拟机容器中,嵌套的子分区由它的父分区hv模块管理,所有的虚拟机陷入陷出都首先交给根分区的hv处理,再分发给父分区hv处理完成后回到子分区继续执行,即使被调度的是一段shellcode,整个虚拟环境也具备一个完整的操作系统拥有x86体系虚拟化资源.一个分区允许创建多个可以同时执行调度的vcpu通过WHvCreateVirtualProcessor,每个vcpu都可以设置自己的寄存器上下文,而内存对象被整个分区共享,进入WHvRunVirtualProcessor开始调度,整个调度过程中对外面的Hypervisor是不透明的,直到遇到一个退出条件比如说断点,内存违规访问,vmcall指令等函数会返回,可以从vmexit的上下文中获取退出原因,Hypervisor可以执行对应的操作继续vcpu运行.qemu无硬件虚拟化采用纯模拟的方式实现缺点是速度较慢.Hyper-v模式主要是陷入陷出调度器需要处理时间,源ISA指令执行速度与真实cpu相当,这种方式速度较快. 15 | 16 | ## 内存管理分析 ## 17 | 18 | qemu采用MemoryRegion结构体管理所有分配的gva(客户机虚拟内存地址)到hva(宿主机虚拟内存地址)的映射,内部是一个双向链表结构包含了起始,结束gva和映射hva地址,支持先指定gva再分配hva模式,查询链表通过二叉树方式实现,如果新分配的地址位于已分配区域返回UC_ERR_MAP错误需要重新指定gva,对于读取和写入内存则是先通过gva找到hva,直接操作hva相对偏移量数据,这种方式一般仅限于模拟应用层程序的内存管理,对于所有内存操作只是处理所有已经映射的gva,遇到了未被映射的内存直接抛出UC_ERR_WRITE_UNMAPPED错误结束程序.由于对于内核态程序存在虚拟机地址和物理地址映射关系,这种直接的转换映射处理并不适用于这种情况.而Hyper-v模式多出了一个gpa(客户机物理内存地址)的概念,映射宿主机虚拟内存并不能直接通过hva -> gva的方式映射,而是通过WHvMapGpaRange函数先映射gpa再根据当前vcpu的cr3寄存器pde,pte转换到gva,这种模式也就是我们真实x86体系操作系统的内存映射模式,同时适用于用户态和内核态程序.至于cr3寄存器如映何射gva虚拟内存可以参考看雪其他相关文章这里不在赘述,笔者项目沿用了qemu内存管理框架结构体,实现参考WinHvShellcodeEmulator项目,下面这段代码展示了在虚拟机映射gva和方式. 19 | ``` 20 | HRESULT WhSeMapHostToGuestVirtualMemory(whpx_state *Partition, uintptr_t HostVa, 21 | uintptr_t *GuestVa, size_t Size, 22 | WHSE_MEMORY_ACCESS_FLAGS Flags) 23 | { 24 | auto size = ALIGN_UP(Size); 25 | PWHSE_ALLOCATION_NODE existingNode = nullptr; 26 | auto hresult = 27 | WhSeFindAllocationNodeByGva(Partition, *GuestVa, &existingNode); 28 | uintptr_t suggestedGva = 0; 29 | if (*GuestVa == 0 || existingNode != nullptr) { 30 | auto hresult = WhSiSuggestVirtualAddress( 31 | Partition, size, &suggestedGva, Partition->VirtualProcessor.Mode); 32 | } else 33 | suggestedGva = ALIGN(*GuestVa); 34 | existingNode = nullptr; 35 | hresult = WhSeFindAllocationNodeByGva(Partition, suggestedGva, &existingNode); 36 | auto startingGva = ALIGN(suggestedGva); 37 | auto endingGva = ALIGN_UP(startingGva + size); 38 | uintptr_t suggestedGpa = 0; 39 | hresult = WhSiSuggestPhysicalAddress(Partition, size, &suggestedGpa); 40 | WHSE_ALLOCATION_NODE node{.BlockType = 41 | MEMORY_BLOCK_TYPE::MemoryBlockVirtual, 42 | .HostVirtualAddress = HostVa, 43 | .GuestPhysicalAddress = suggestedGpa, 44 | .GuestVirtualAddress = startingGva, 45 | .Size = size}; 46 | hresult = WhSeInsertAllocationTrackingNode(Partition, node); 47 | // Setup matching PTEs 48 | for (auto gva = startingGva, page = suggestedGpa; gva < endingGva; 49 | gva += PAGE_SIZE, page += PAGE_SIZE) { 50 | hresult = WhSiInsertPageTableEntry(Partition, gva, page); 51 | hresult = ::WHvMapGpaRange( 52 | Partition->partition, reinterpret_cast(HostVa), 53 | static_cast(suggestedGpa), size, Flags); 54 | *GuestVa = startingGva; 55 | return hresult; 56 | } 57 | HRESULT WhSiInsertPageTableEntry(whpx_state *Partition, 58 | uintptr_t VirtualAddress, 59 | uintptr_t PhysicalAddress) 60 | { 61 | // "Explode" the VA into translation indexes 62 | uint16_t pml4Idx; 63 | uint16_t pdpIdx; 64 | uint16_t pdIdx; 65 | uint16_t ptIdx; 66 | uint16_t phyOffset; 67 | auto hresult = WhSiDecomposeVirtualAddress( 68 | VirtualAddress, &pml4Idx, &pdpIdx, &pdIdx, &ptIdx, &phyOffset); 69 | // Search entry in PML4 70 | auto pml4e = reinterpret_cast( 71 | Partition->MemoryLayout.Pml4HostVa)[pml4Idx]; 72 | if (pml4e.Valid == FALSE) { 73 | // Shouldn't happen as we initialized all PLM4 entries upfront 74 | return HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR); 75 | } 76 | // Search entry in Page Directory Pointers 77 | uintptr_t pdpHva = 0; 78 | hresult = WhSpLookupHVAFromPFN(Partition, pml4e.PageFrameNumber, &pdpHva); 79 | auto pdp = reinterpret_cast(pdpHva); 80 | auto pdpe = pdp[pdpIdx]; 81 | if (pdpe.Valid == FALSE) { 82 | // Allocate a Page Directory page 83 | // 84 | hresult = WhSpInsertPageTableEntry(Partition, pdp, pdpIdx); 85 | pdpe = pdp[pdpIdx]; 86 | } 87 | // Search entry in Page Directories 88 | uintptr_t pdHva = 0; 89 | hresult = WhSpLookupHVAFromPFN(Partition, pdpe.PageFrameNumber, &pdHva); 90 | if (FAILED(hresult)) 91 | return hresult; 92 | auto pd = reinterpret_cast(pdHva); 93 | auto pde = pd[pdIdx]; 94 | if (pde.Valid == FALSE) { 95 | // Allocate a Page Table page 96 | hresult = WhSpInsertPageTableEntry(Partition, pd, pdIdx); 97 | pde = pd[pdIdx]; 98 | } 99 | // Add entry in Page Tables 100 | uintptr_t ptHva = 0; 101 | hresult = WhSpLookupHVAFromPFN(Partition, pde.PageFrameNumber, &ptHva); 102 | if (FAILED(hresult)) 103 | return hresult; 104 | auto pt = reinterpret_cast(ptHva); 105 | auto ppte = &pt[ptIdx]; 106 | if (ppte->Valid == FALSE) { 107 | /*PWHSE_ALLOCATION_NODE found = nullptr; 108 | hresult = WhSeFindAllocationNodeByGpa( Partition, PhysicalAddress, 109 | &found ); if ( hresult != HRESULT_FROM_WIN32( ERROR_NOT_FOUND ) && 110 | FAILED( hresult ) ) return hresult; 111 | // Create a valid PTE 112 | MMPTE_HARDWARE pte{}; 113 | pte.AsUlonglong = 0; // Ensure zeroed 114 | pte.Valid = 1; // Intel's Present bit 115 | pte.Write = 1; // Intel's Read/Write bit 116 | pte.Owner = 1; // Intel's User/Supervisor bit, let's say it is a user 117 | // accessible frame 118 | pte.PageFrameNumber = 119 | (PhysicalAddress / PAGE_SIZE); // Physical address of PDP page 120 | *ppte = pte; 121 | WHSE_ALLOCATION_NODE node{.BlockType = 122 | MEMORY_BLOCK_TYPE::MemoryBlockPte, 123 | .HostVirtualAddress = 0, 124 | .GuestPhysicalAddress = PhysicalAddress, 125 | .GuestVirtualAddress = 0, 126 | .Size = PAGE_SIZE}; 127 | hresult = WhSeInsertAllocationTrackingNode(Partition, node); 128 | } 129 | return S_OK; 130 | } 131 | 132 | ``` 133 | 由于Hyper-v模式模拟的是整个虚拟机环境,在初始化分区时构建512个pde项,对需要映射的gva需要先对齐到一个页大小,对每个要申请的gva,使用其中一个可用的pde,计算出索引PageFrameNumber分对应的pte项,插入这些页的gpa,最后把vcpu初始化的cr3基址指向pde的物理地址.除了初始化内存分配,还要加载进内存要执行之前都会初始化段寄存器的值,包括全局描述符表(GDT),局部描述符表(LDT)和中断描述符表(IDT),这里只需要根据当前模拟的是用户态还是内核态选择对应的段选择子和DescriptorPrivilegeLevel,还有根据否是64位和32位模式设置段寄存器cs的Long位就可以了.虚拟机本身对32位和64位模式没有限制,这些都通过x86平台自身的段属性配置,这里还存在一种特殊情况,类似在模拟器中执行VirtualAlloc由于实际申请到的gva是未知的,如果传入一个随机的内存地址,传给模拟器可能返回一个已映射地址错误,导致分配失败,解决方法是先申请宿主机hva,然后找到一个已释放的页或者计算一个新的页索引PageFrameNumber分配页得到一个可以用的gva和gpa地址映射hva的pte项,把计算出的gva返回给调用者.这种方式可以模拟任何虚拟内存申请函数. 134 | 原WinHvShellcodeEmulator项目默认配置不支持xmm寄存器指令,解决方法是需要开启cr4的OSXSAVE位和xcr的XSTATE相关位,开启后就可以正常执行sse指令集了. 135 | ``` 136 | 先设置cr4的这些位 137 | #define CR4_OSXSAVE_MASK (1U << 18) 138 | #define CR4_OSFXSR_SHIFT 9 139 | #define CR4_OSFXSR_MASK (1U << CR4_OSFXSR_SHIFT) 140 | #define CR4_OSXMMEXCPT_MASK (1U << 10) 141 | RegisterName = WHvX64RegisterCr4; 142 | uint64_t cr4val = 0; 143 | whpx_get_reg(RegisterName, &cr4val); 144 | cr4val = (cr4val | (1ULL << 5)) & ~(1 << 24); 145 | cr4val |= CR4_OSXSAVE_MASK; 146 | cr4val |= CR4_OSFXSR_MASK; 147 | cr4val |= CR4_OSXMMEXCPT_MASK; 148 | whpx_set_reg(RegisterName, cr4val); 149 | //再设置WHvX64RegisterXCr0的这些位 150 | #define XSTATE_FP_BIT 0 151 | #define XSTATE_SSE_BIT 1 152 | #define XSTATE_FP_MASK (1ULL << XSTATE_FP_BIT) 153 | #define XSTATE_SSE_MASK (1ULL << XSTATE_SSE_BIT) 154 | WHV_REGISTER_VALUE xcr0; 155 | WHV_REGISTER_NAME xcr0_name = WHvX64RegisterXCr0; 156 | if (!whpx_has_xsave()) { 157 | return; 158 | } 159 | env->xcr0 |= XSTATE_FP_MASK; 160 | env->xcr0 |= XSTATE_SSE_MASK; 161 | /* Only xcr0 is supported by the hypervisor currently */ 162 | xcr0.Reg64 = env->xcr0; 163 | hr = WHvSetVirtualProcessorRegisters(whpx->partition, whpx->cpu_index, 164 | &xcr0_name, 1, &xcr0); 165 | ``` 166 | 笔者为项目添加了一个支持导入windbg的dump文件模拟应用程序的功能,支持在加载dump文件后自动映射入口点所有寄存器的值,对已经dump的相关内存自动映射相关gva,包括所有已加载模块镜像的内存,并且设置退出条件ExceptionExitBitmap包含WHvX64ExceptionTypePageFault位,这样模拟shellcode时即使未完成全部内存映射,设置为内核模式,如果模拟运行遇到了未映射的内存Hypervisor会去idt中查找缺页异常的handler,实际上的这个异常所在的handler的内存是个已释放的页面,导致最终产生了一个WHvX64ExceptionTypePageFault类型的退出错误在,在vcpu->exit_ctx.VpException.ExceptionParameter这个字段中包含的就是未映射的内存地址,这样只要从dump文件中把那片内存读出来,恢复模拟器运行就能修复常见的违规内存访问错误.Windows Hypervisor Platform 还提供了一种机制用于修复WHvRunVpExitReasonMemoryAccess错误,称为WHvEmulatorTryMmioEmulation函数,会模拟当前指令的汇编代码在传给WHvEmulatorCreateEmulator回调函数中返回的Emulator句柄,如果通过模拟汇编代码找到一个映射关系在WHvEmulatorTranslateGvaPage回调函数中得到得到解析出来的gva和WHvTranslateGva的gpa,这种方式也提供了类似的逻辑修复违规内存访问错误.其他类型退出异常比如说cpuid,apic等可以参考qemu的Windows Hypervisor Platform实现具体见引用节. 167 | 168 | ## 调试器功能开发 ## 169 | 170 | qiling框架实现了一套基于gdb远程调试协议的ida调试插件支持,gdb远程调试协议文档详见引用节,调试插件在一个循环中读取ida发过来是请求包,初始化调试环境在入口处停下来,ida读取当前状态的寄存器和内存数据,用户可以在这个时候设置断点,直到用户执行continue,把所有的断点请求包发送调试器完成后.到了continue发送handle_c包调用uc_emu_start,这个时候模拟器开始执行并设置当前启用的断点,直到遇到一个退出条件,模拟器遍历符合条件的导致退出执行的断点,上报至调试插件,调试插件再根据不同的断点类型确定是要跳过的中断还是暂停调试中断到调试器,如果要中断到调试器,在断点回调中调用uc_emu_stop终止模拟循环,这里需要注意的一点是uc_emu_start是主线程,断点回调只是在线程的执行过程中向上层回调,回调完成后handle_c函数才会返回{SIGTRAP:02x},在ida中看到的现象是调试运行位置切换到断点位置中断,用户可以选择读取数据,设置断点或者继续运行.对于普通断点的实现采用的方法是把断点位置的第一个字节替换成INT1=0xf1,这样运行得到断点处就会抛出一个WHvX64ExceptionTypeDebugTrapOrFault,如果vcpu->exit_ctx.VpException.InstructionBytes[0]=0xf1就可以确定是触发INT1断点中断到调试器,但是如果直接继续运行会发现这个断点会无限触发导致死循环,解决方法是先恢复断点处指令为原始数据字节,然后设置单步执行修复方法解决.,等单步指令执行完触发单步异常时,再来重启断点,这个步骤在内部执行对上层调试器没有影响,再根据当前调试器是继续执行还是单步模式继续处理,笔者参考了其它调试器的文章也是这样实现的.笔者还为调试器新加入了硬件断点的功能,在gdb远程调试协议中如果收到一个Z1-Z3的包,表示是一个硬件断点,可以采用x86架构的DR0-7调试寄存器的方式实现.启用断点,调试寄存器DR7的0~7位的L位和G位分别表示对应的断点是否启用局部还是全局,第8位和第9位是L位和G位的大开关,16~31位表示断点类型和长度.DR0~3寄存器保存的是断点的地址,断点触发后DR6寄存器的B0~3置位表示断点的索引.硬件断点同样也存在死循环问题可以单步执行修复方法解决,具体方法如下:. 171 | ``` 172 | #define RT_BIT_64(bit) (UINT64_C(1) << (bit)) 173 | #define RT_BIT_64_FIND(val, bit) (val & (UINT64_C(1) << (bit))) 174 | #define RT_BIT_64_SLOT(bit) (UINT64_C(1) << (bit << 1)) 175 | #define RT_BIT_64_FIND_SLOT(val, bit) (val & (UINT64_C(1) << (bit << 1))) 176 | static void 177 | whpx_apply_hardware_breakpoint(struct whpx_breakpoint_collection *breakpoints, 178 | CPUState *cpu, uintptr_t addrskip) 179 | { 180 | uint8_t hwbpslot = 0; 181 | uint64_t dr7val=0; 182 | uint64_t dr7valrw = 0; 183 | for (int i = 0; i < breakpoints->used; i++) { 184 | struct whpx_breakpoint *breakpoint = &breakpoints->data[i]; 185 | WhpxBreakpointState state = breakpoint->state; 186 | if (breakpoint->bptype & 0xff0000) { 187 | if (state == WHPX_BP_SET_PENDING) { 188 | for (uint8_t j = 0; j < 4; j++) { 189 | //如果有使用槽置位详见源码 190 | if (!RT_BIT_64_FIND_SLOT(dr7val, j)) { 191 | breakpoint->original_instruction = j; 192 | hwbpslot |= RT_BIT_64(breakpoint->original_instruction); 193 | whpx_set_reg(WHvX64RegisterDr0+j, breakpoint->address); 194 | 195 | } 196 | } 197 | if (breakpoint->bptype == UC_HOOK_HARDWARE_READ) { 198 | dr7valrw |= 199 | RT_BIT_64(breakpoint->original_instruction << 2); 200 | dr7valrw |= 201 | RT_BIT_64((breakpoint->original_instruction << 2) + 1); 202 | } 203 | if (breakpoint->bptype == UC_HOOK_HARDWARE_WRITE) { 204 | dr7valrw |= 205 | RT_BIT_64(breakpoint->original_instruction << 2); 206 | } 207 | breakpoint->state = WHPX_BP_SET; 208 | 209 | } 210 | } 211 | } 212 | dr7val = 0; 213 | if (hwbpslot) { 214 | for (uint8_t j = 0; j < 4; j++) { 215 | if (hwbpslot & RT_BIT_64(j)) { 216 | dr7val |= (RT_BIT_64_SLOT(j)); 217 | } 218 | } 219 | dr7val |= dr7valrw << 16; 220 | //启用大标志 221 | dr7val |= RT_BIT_64(8); 222 | } 223 | whpx_set_reg(WHvX64RegisterDr7, dr7val); 224 | } 225 | ``` 226 | 笔者项目目前只支持单线程(1个vcpu)模拟,有兴趣的读者可以自行开发多线程功能实现.原qiling框架有自己的pe加载器设置的gdtr和idt寄存器和笔者项目有冲突暂时未使用,模拟了常用的winapi函数,这种模拟方式同样在笔者项目使用用于api模拟,留给读者自行尝试. 227 | 228 | 229 | ## 编译方式 ## 230 | 231 | 添加工程文件至Unicorn Engine在修改CMakeLists.txt新建unicorn-whpx静态库,添加"Winhvplatform.lib"和 "WinHvEmulation.lib"库依赖,使用如下方式创建模拟器实例,导出api形式和原工程相同: 232 | ``` 233 | uc_err err = uc_open(UC_ARCH_X86_WHPX, UC_MODE_64, &uc); 234 | ``` 235 | ## 运行效果 ## 236 | 237 | 以下是笔者模拟器运行的效果,如图: 238 | 239 | ![查看大图](img/unifix.gif) 240 | 241 | ## 相关引用 ## 242 | 243 | [Unicorn Engine](https://github.com/unicorn-engine/unicorn) 244 | 245 | [Windows Hypervisor Platform API](https://learn.microsoft.com/en-us/virtualization/api/) 246 | 247 | [hypervisor implementation for Bochs](https://github.com/gamozolabs/applepie) 248 | 249 | [参考qemu实现](https://github.com/kata-containers/qemu/blob/cdcb7dcb401757b5853ca99c1967a6d66e1deea5/target/i386/whpx/whpx-all.c) 250 | 251 | [WinHvShellcodeEmulator](https://github.com/Midi12/whse/tree/master) 252 | 253 | [看雪Qemu的tcg分析](https://bbs.kanxue.com/thread-277163.htm) 254 | 255 | [看雪hyper-v分析](https://bbs.kanxue.com/thread-278784.htm) 256 | 257 | [gdt](https://bbs.kanxue.com/thread-270476.htm) 258 | 259 | [段模式](https://bbs.kanxue.com/thread-279127.htm) 260 | 261 | [看雪调试器](https://bbs.kanxue.com/thread-276162.htm) 262 | 263 | [gdb远程调试协议](https://sourceware.org/gdb/current/onlinedocs/gdb.html/index.html) 264 | 265 | [硬件断点文档](https://en.wikipedia.org/wiki/X86_debug_register) 266 | 267 | [hv模块调试](https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/bcdedit--hypervisorsettings) 268 | 269 | [笔者模拟器项目](https://github.com/cbwang505/unicorn-whpx) 270 | 271 | 272 | ## 参与贡献 ## 273 | 274 | 275 | 作者来自ZheJiang Guoli Security Technology,邮箱cbwang505@hotmail.com -------------------------------------------------------------------------------- /img/unifix.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbwang505/unicorn-whpx/c1cc4de7eb5f2424195ba885ab4778c3dd865bed/img/unifix.gif -------------------------------------------------------------------------------- /qiling/gdb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Cross Platform and Multi Architecture Advanced Binary Emulation Framework 4 | # 5 | 6 | # for watching actual protocol messages: 7 | # server: gdbserver --remote-debug 127.0.0.1:9999 /path/to/exec 8 | # client: gdb -q -ex "target remote 127.0.0.1:9999" 9 | # 10 | # also, run this command on the gdb client: 11 | # (gdb) set debug remote 1 12 | # 13 | # gdb remote protocol: 14 | # https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html 15 | 16 | import os 17 | import socket 18 | import re 19 | import tempfile 20 | from functools import partial 21 | from logging import Logger 22 | from typing import IO, Iterator, MutableMapping, Optional, Union 23 | 24 | from unicorn import UcError 25 | from unicorn.unicorn_const import ( 26 | UC_ERR_READ_UNMAPPED, UC_ERR_WRITE_UNMAPPED, UC_ERR_FETCH_UNMAPPED, 27 | UC_ERR_READ_PROT, UC_ERR_WRITE_PROT, UC_ERR_FETCH_PROT, 28 | UC_ERR_READ_UNALIGNED, UC_ERR_WRITE_UNALIGNED, UC_ERR_FETCH_UNALIGNED, 29 | UC_ERR_INSN_INVALID 30 | ) 31 | 32 | from qiling import Qiling 33 | from qiling.const import QL_ARCH, QL_ENDIAN, QL_OS, QL_STATE 34 | from qiling.debugger import QlDebugger 35 | from qiling.debugger.gdb.xmlregs import QlGdbFeatures 36 | from qiling.debugger.gdb.utils import QlGdbUtils 37 | from qiling.os.linux.procfs import QlProcFS 38 | 39 | # gdb logging prompt 40 | PROMPT = r'gdb>' 41 | 42 | # default string encoding 43 | ENCODING = 'latin' 44 | 45 | # define a few handy linux signals 46 | SIGINT = 2 47 | SIGILL = 4 48 | SIGTRAP = 5 49 | SIGABRT = 6 50 | SIGBUS = 7 51 | SIGKILL = 9 52 | SIGSEGV = 11 53 | SIGALRM = 14 54 | SIGTERM = 15 55 | SIGCHLD = 16 56 | SIGCONT = 17 57 | SIGSTOP = 18 58 | 59 | # common replies 60 | REPLY_ACK = b'+' 61 | REPLY_EMPTY = b'' 62 | REPLY_OK = b'OK' 63 | 64 | # reply type 65 | Reply = Union[bytes, str] 66 | 67 | 68 | class QlGdb(QlDebugger): 69 | """A simple gdbserver implementation. 70 | """ 71 | 72 | def __init__(self, ql: Qiling, ip: str = '127.0.0.1', port: int = 9999): 73 | super().__init__(ql) 74 | 75 | if type(port) is str: 76 | port = int(port, 0) 77 | 78 | self.ip = ip 79 | self.port = port 80 | 81 | def __get_attach_addr() -> int: 82 | if ql.baremetal: 83 | entry_point = ql.loader.entry_point 84 | 85 | elif ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD) and not ql.code: 86 | entry_point = ql.os.elf_entry 87 | 88 | else: 89 | entry_point = ql.os.entry_point 90 | 91 | # though linkers set the entry point LSB to indicate arm thumb mode, the 92 | # effective entry point address is aligned. make sure we have it aligned 93 | if hasattr(ql.arch, 'is_thumb'): 94 | entry_point &= ~0b1 95 | 96 | return entry_point 97 | 98 | def __get_detach_addr() -> int: 99 | if ql.baremetal: 100 | base = ql.loader.load_address 101 | size = os.path.getsize(ql.path) 102 | 103 | elif ql.code: 104 | base = ql.os.entry_point 105 | size = len(ql.code) 106 | 107 | else: 108 | #base = ql.loader.load_address 109 | base = ql.entry_point 110 | size = os.path.getsize(ql.path) 111 | 112 | return base + size 113 | 114 | attach_addr = __get_attach_addr() if ql.entry_point is None else ql.entry_point 115 | detach_addr = __get_detach_addr() if ql.exit_point is None else ql.exit_point 116 | 117 | self.gdb = QlGdbUtils(ql, attach_addr, detach_addr) 118 | 119 | self.features = QlGdbFeatures(self.ql.arch.type, self.ql.os.type) 120 | self.regsmap = self.features.regsmap 121 | 122 | self.fake_procfs: MutableMapping[int, IO] = {} 123 | self.filearr=[] 124 | 125 | def run(self): 126 | server = GdbSerialConn(self.ip, self.port, self.ql.log) 127 | killed = False 128 | 129 | def __hexstr(value: int, nibbles: int = 0) -> str: 130 | """Encode a value into a hex string. 131 | """ 132 | 133 | length = (nibbles or self.ql.arch.bits // 4) // 2 134 | byteorder = 'little' if self.ql.arch.endian == QL_ENDIAN.EL else 'big' 135 | 136 | return value.to_bytes(length, byteorder).hex() 137 | 138 | def __unkown_reg_value(nibbles: int) -> str: 139 | """Encode the hex string for unknown regsiter value. 140 | """ 141 | 142 | return 'x' * nibbles 143 | 144 | def __get_reg_value(reg: Optional[int], pos: int, nibbles: int) -> str: 145 | # reg is either None or uc reg invalid 146 | if reg: 147 | value = self.ql.arch.regs.read(reg) 148 | assert type(value) is int 149 | 150 | hexstr = __hexstr(value, nibbles) 151 | else: 152 | hexstr = __unkown_reg_value(nibbles) 153 | 154 | return hexstr 155 | 156 | def __set_reg_value(reg: Optional[int], pos: int, nibbles: int, hexval: str) -> None: 157 | # reg is neither None nor uc reg invalid 158 | if reg and hexval != __unkown_reg_value(nibbles): 159 | assert len(hexval) == nibbles 160 | 161 | val = int(hexval, 16) 162 | 163 | if self.ql.arch.endian == QL_ENDIAN.EL: 164 | val = __swap_endianess(val) 165 | 166 | self.ql.arch.regs.write(reg, val) 167 | 168 | def __swap_endianess(value: int) -> int: 169 | length = (value.bit_length() + 7) // 8 170 | raw = value.to_bytes(length, 'little') 171 | 172 | return int.from_bytes(raw, 'big') 173 | 174 | def handle_exclaim(subcmd: str) -> Reply: 175 | return REPLY_OK 176 | 177 | def handle_qmark(subcmd: str) -> Reply: 178 | """Request status. 179 | 180 | @see: https://sourceware.org/gdb/current/onlinedocs/gdb/Stop-Reply-Packets.html 181 | """ 182 | 183 | from unicorn.x86_const import UC_X86_REG_EBP 184 | from unicorn.x86_const import UC_X86_REG_RBP 185 | from unicorn.arm_const import UC_ARM_REG_R11 186 | from unicorn.arm64_const import UC_ARM64_REG_X29 187 | from unicorn.mips_const import UC_MIPS_REG_INVALID 188 | 189 | arch_uc_bp = { 190 | QL_ARCH.X86 : UC_X86_REG_EBP, 191 | QL_ARCH.X8664 : UC_X86_REG_RBP, 192 | QL_ARCH.ARM : UC_ARM_REG_R11, 193 | QL_ARCH.ARM64 : UC_ARM64_REG_X29, 194 | QL_ARCH.MIPS : UC_MIPS_REG_INVALID, # skipped 195 | QL_ARCH.A8086 : UC_X86_REG_EBP, 196 | QL_ARCH.CORTEX_M : UC_ARM_REG_R11 197 | }[self.ql.arch.type] 198 | 199 | def __get_reg_idx(ucreg: int) -> int: 200 | """Get the index of a uc reg whithin the regsmap array. 201 | 202 | Returns: array index where this reg's info is stored, or -1 if not found 203 | """ 204 | 205 | return next((i for i, (regnum, _, _) in enumerate(self.regsmap) if regnum == ucreg), -1) 206 | 207 | def __get_reg_info(ucreg: int) -> str: 208 | """Retrieve register info and pack it as a pair. 209 | """ 210 | 211 | regnum = __get_reg_idx(ucreg) 212 | hexval = __get_reg_value(*self.regsmap[regnum]) 213 | 214 | return f'{regnum:02x}:{hexval};' 215 | 216 | # mips targets skip this reg info pair 217 | bp_info = '' if self.ql.arch.type == QL_ARCH.MIPS else __get_reg_info(arch_uc_bp) 218 | 219 | # FIXME: a8086 should use 'esp' and 'eip' here instead of 'sp' and 'ip' set by its arch instance 220 | sp_info = __get_reg_info(self.ql.arch.regs.uc_sp) 221 | pc_info = __get_reg_info(self.ql.arch.regs.uc_pc) 222 | 223 | return f'T{SIGTRAP:02x}{bp_info}{sp_info}{pc_info}' 224 | 225 | def handle_c(subcmd: str) -> Reply: 226 | try: 227 | self.gdb.resume_emu() 228 | except UcError as err: 229 | sigmap = { 230 | UC_ERR_READ_UNMAPPED : SIGSEGV, 231 | UC_ERR_WRITE_UNMAPPED : SIGSEGV, 232 | UC_ERR_FETCH_UNMAPPED : SIGSEGV, 233 | UC_ERR_WRITE_PROT : SIGSEGV, 234 | UC_ERR_READ_PROT : SIGSEGV, 235 | UC_ERR_FETCH_PROT : SIGSEGV, 236 | UC_ERR_READ_UNALIGNED : SIGBUS, 237 | UC_ERR_WRITE_UNALIGNED : SIGBUS, 238 | UC_ERR_FETCH_UNALIGNED : SIGBUS, 239 | UC_ERR_INSN_INVALID : SIGILL 240 | } 241 | 242 | # determine signal from uc error; default to SIGTERM 243 | reply = f'S{sigmap.get(err.errno, SIGTERM):02x}' 244 | 245 | except KeyboardInterrupt: 246 | # emulation was interrupted with ctrl+c 247 | reply = f'S{SIGINT:02x}' 248 | 249 | else: 250 | if getattr(self.ql.arch, 'effective_pc', self.ql.arch.regs.arch_pc) == self.gdb.last_bp: 251 | # emulation stopped because it hit a breakpoint 252 | reply = f'S{SIGTRAP:02x}' 253 | else: 254 | # emulation has completed successfully 255 | reply = f'W{self.ql.os.exit_code:02x}' 256 | 257 | return reply 258 | 259 | def handle_g(subcmd: str) -> Reply: 260 | # NOTE: in the past the 'g' reply packet for arm included the f0-f7 and fps registers between pc 261 | # and cpsr, which placed cpsr at index (regnum) 25. as the f-registers became obsolete the cpsr 262 | # index decreased. in order to maintain backward compatibility with older gdb versions, the gap 263 | # between pc and cpsr that used to represent the f-registers (96 bits each + 32 bits for fps) is 264 | # filled with unknown reg values. 265 | # 266 | # gdb clients that follow the xml definitions no longer need these placeholders, as registers 267 | # indices are flexible and may be defined arbitrarily though xml. 268 | # 269 | # see: ./xml/arm/arm-fpa.xml 270 | 271 | return ''.join(__get_reg_value(*entry) for entry in self.regsmap) 272 | 273 | def handle_G(subcmd: str) -> Reply: 274 | data = subcmd 275 | 276 | for reg, pos, nibbles in self.regsmap: 277 | hexval = data[pos : pos + nibbles] 278 | 279 | __set_reg_value(reg, pos, nibbles, hexval) 280 | 281 | return REPLY_OK 282 | 283 | def handle_H(subcmd: str) -> Reply: 284 | op = subcmd[0] 285 | 286 | if op in ('c', 'g'): 287 | return REPLY_OK 288 | 289 | return REPLY_EMPTY 290 | 291 | def handle_k(subcmd: str) -> Reply: 292 | global killed 293 | 294 | killed = True 295 | return REPLY_OK 296 | 297 | def handle_m(subcmd: str) -> Reply: 298 | """Read target memory. 299 | """ 300 | 301 | addr, size = (int(p, 16) for p in subcmd.split(',')) 302 | 303 | try: 304 | data = self.ql.mem.read(addr, size) 305 | except UcError as ex: 306 | return f'E{ex.errno:02d}' 307 | else: 308 | return data.hex() 309 | 310 | def handle_M(subcmd: str) -> Reply: 311 | """Write target memory. 312 | """ 313 | 314 | addr, data = subcmd.split(',') 315 | size, data = data.split(':') 316 | 317 | addr = int(addr, 16) 318 | data = bytes.fromhex(data) 319 | 320 | assert len(data) == size 321 | 322 | try: 323 | self.ql.mem.write(addr, data) 324 | except UcError as ex: 325 | return f'E{ex.errno:02d}' 326 | else: 327 | return REPLY_OK 328 | 329 | def handle_p(subcmd: str) -> Reply: 330 | """Read register value by index. 331 | """ 332 | 333 | idx = int(subcmd, 16) 334 | 335 | return __get_reg_value(*self.regsmap[idx]) 336 | 337 | def handle_P(subcmd: str) -> Reply: 338 | """Write register value by index. 339 | """ 340 | 341 | idx, data = subcmd.split('=') 342 | idx = int(idx, 16) 343 | 344 | if idx < len(self.regsmap): 345 | __set_reg_value(*self.regsmap[idx], hexval=data) 346 | 347 | return REPLY_OK 348 | 349 | return 'E00' 350 | 351 | def handle_Q(subcmd: str) -> Reply: 352 | """General queries. 353 | 354 | @see: https://sourceware.org/gdb/onlinedocs/gdb/General-Query-Packets.html 355 | """ 356 | 357 | feature, *data = subcmd.split(':', maxsplit=1) 358 | 359 | supported = ( 360 | 'DisableRandomization', 361 | 'NonStop', 362 | 'PassSignals', 363 | 'ProgramSignals', 364 | 'StartNoAckMode' 365 | ) 366 | 367 | if feature == 'StartNoAckMode': 368 | server.ack_mode = False 369 | server.log.debug('[noack mode enabled]') 370 | 371 | return REPLY_OK if feature in supported else REPLY_EMPTY 372 | 373 | def handle_D(subcmd: str) -> Reply: 374 | """Detach. 375 | """ 376 | 377 | return REPLY_OK 378 | 379 | def handle_q(subcmd: str) -> Reply: 380 | query, *data = subcmd.split(':') 381 | 382 | # qSupported command 383 | # 384 | # @see: https://sourceware.org/gdb/onlinedocs/gdb/General-Query-Packets.html#qSupported 385 | 386 | if query == 'Supported': 387 | # list of supported features excluding the multithreading-related ones 388 | features = [ 389 | 'BreakpointCommands+', 390 | 'ConditionalBreakpoints+', 391 | 'ConditionalTracepoints+', 392 | 'DisconnectedTracing+', 393 | 'EnableDisableTracepoints+', 394 | 'InstallInTrace+', 395 | 'QAgent+', 396 | 'QCatchSyscalls+', 397 | 'QDisableRandomization+', 398 | 'QNonStop+', 399 | 'QPassSignals+', 400 | 'QProgramSignals+', 401 | 'QStartNoAckMode+', 402 | 'QStartupWithShell+', 403 | 'QTBuffer:size+', 404 | 'StaticTracepoints+', 405 | 'TraceStateVariables+', 406 | 'TracepointSource+', 407 | 'augmented-libraries-svr4-read+', 408 | 'exec-events+', 409 | 'fork-events+', 410 | 'hwbreak+', 411 | 'multiprocess+', 412 | 'no-resumed+', 413 | 'qXfer:features:read+', 414 | 'qXfer:libraries:read+', 415 | 'qXfer:libraries-svr4:read+', 416 | # 'qXfer:osdata:read+', 417 | 'qXfer:siginfo:read+', 418 | 'qXfer:siginfo:write+', 419 | 'qXfer:statictrace:read+', 420 | 'qXfer:traceframe-info:read+', 421 | 'swbreak+', 422 | 'tracenz+', 423 | 'vfork-events+' 424 | ] 425 | 426 | # might or might not need for multi thread 427 | if self.ql.multithread: 428 | features += [ 429 | 'PacketSize=47ff', 430 | 'FastTracepoints+', 431 | 'QThreadEvents+', 432 | 'Qbtrace-conf:bts:size+', 433 | 'Qbtrace-conf:pt:size+', 434 | 'Qbtrace:bts+', 435 | 'Qbtrace:off+', 436 | 'Qbtrace:pt+', 437 | 'qXfer:btrace-conf:read+', 438 | 'qXfer:btrace:read+', 439 | 'vContSupported+' 440 | ] 441 | 442 | else: 443 | features += [ 444 | 'PacketSize=3fff', 445 | 'qXfer:spu:read+', 446 | 'qXfer:spu:write+' 447 | ] 448 | # filesystem dependent features 449 | if hasattr(self.ql.os, 'path'): 450 | features += [ 451 | 'QSetWorkingDir+', 452 | 'qXfer:auxv:read+', 453 | 'qXfer:exec-file:read+' 454 | ] 455 | # os dependent features 456 | if not self.ql.interpreter: 457 | features += [ 458 | 'QEnvironmentHexEncoded+', 459 | 'QEnvironmentReset+', 460 | 'QEnvironmentUnset+' 461 | ] 462 | 463 | 464 | 465 | # process dependent features 466 | if hasattr(self.ql.os, 'pid'): 467 | features.append('qXfer:threads:read+') 468 | 469 | return ';'.join(features) 470 | 471 | elif query == 'Xfer': 472 | feature, op, annex, params = data 473 | offset, length = (int(p, 16) for p in params.split(',')) 474 | 475 | if feature == 'features' and op == 'read': 476 | if annex == r'target.xml': 477 | content = self.features.tostring()[offset:offset + length] 478 | 479 | else: 480 | self.ql.log.info(f'{PROMPT} did not expect "{annex}" here') 481 | content = '' 482 | 483 | return f'{"l" if len(content) < length else "m"}{content}' 484 | 485 | elif feature == 'threads' and op == 'read': 486 | content = '\r\n'.join(( 487 | '', 488 | f'', 489 | '' 490 | )) 491 | 492 | return f'l{content}' 493 | 494 | elif feature == 'auxv' and op == 'read': 495 | try: 496 | with self.ql.os.fs_mapper.open('/proc/self/auxv', 'rb') as infile: 497 | infile.seek(offset, 0) # SEEK_SET 498 | auxv_data = infile.read(length) 499 | 500 | except FileNotFoundError: 501 | auxv_data = b'' 502 | 503 | return b'l' + auxv_data 504 | 505 | elif feature == 'exec-file' and op == 'read': 506 | #return f'l{self.ql.os.path.host_to_virtual_path(self.ql.path)}' 507 | return f'l{self.ql.os.path.virtual_to_host_path(self.ql.image)}' 508 | 509 | elif feature == 'libraries-svr4' and op == 'read': 510 | # TODO: this one requires information of loaded libraries which currently not provided 511 | # by the ELF loader. until we gather that information, we cannot fulfill this request 512 | # 513 | # see: https://sourceware.org/gdb/current/onlinedocs/gdb/Library-List-Format-for-SVR4-Targets.html 514 | return REPLY_EMPTY 515 | 516 | # if self.ql.os.type in (QL_OS.LINUX, QL_OS.FREEBSD): 517 | # tag = 'library-list-svr4' 518 | # xml_lib_entries = (f'' for lbnd, ubnd, _, _, path in self.ql.mem.get_mapinfo() if path) 519 | # 520 | # xml = '\r\n'.join((f'<{tag} version="1.0">', *xml_lib_entries, f'')) 521 | # 522 | # return f'l{xml}' 523 | # else: 524 | # return f'' 525 | 526 | elif feature == 'btrace-conf' and op == 'read': 527 | return 'E.Btrace not enabled.' 528 | 529 | elif query == 'Attached': 530 | #return REPLY_EMPTY 531 | #return '1' 532 | return '0' 533 | 534 | elif query == 'C': 535 | #return REPLY_EMPTY 536 | return 'QC1' 537 | 538 | elif query == 'L': 539 | return 'M001' 540 | 541 | elif query == 'fThreadInfo': 542 | return 'm1' 543 | 544 | elif query == 'sThreadInfo': 545 | return 'l' 546 | 547 | elif query == 'TStatus': 548 | tsize = __hexstr(0x500000) 549 | 550 | fields = ( 551 | 'T0', 552 | 'tnotrun:0', 553 | 'tframes:0', 554 | 'tcreated:0', 555 | f'tfree:{tsize}', 556 | f'tsize:{tsize}', 557 | 'circular:0', 558 | 'disconn:0', 559 | 'starttime:0', 560 | 'stoptime:0', 561 | 'username:', 562 | 'notes::' 563 | ) 564 | 565 | return ';'.join(fields) 566 | 567 | elif query in ('TfV', 'TsV', 'TfP', 'TsP'): 568 | return 'l' 569 | 570 | elif query == 'Symbol': 571 | return REPLY_OK 572 | 573 | elif query == 'Offsets': 574 | fields = ('Text=0', 'Data=0', 'Bss=0') 575 | 576 | return ';'.join(fields) 577 | 578 | return REPLY_EMPTY 579 | 580 | def handle_v(subcmd: str) -> Reply: 581 | if subcmd == 'MustReplyEmpty': 582 | return REPLY_EMPTY 583 | 584 | elif subcmd.startswith('File'): 585 | _, op, data = subcmd.split(':', maxsplit=2) 586 | params = data.split(',') 587 | 588 | if op == 'open': 589 | fd = -1 590 | 591 | # files can be opened only where there is an os that supports filesystem 592 | if not self.ql.interpreter and hasattr(self.ql.os, 'path'): 593 | path, flags, mode = params 594 | 595 | path = bytes.fromhex(path).decode(encoding='utf-8') 596 | flags = int(flags, 16) 597 | mode = int(mode, 16) 598 | 599 | virtpath = self.ql.os.path.virtual_abspath(path) 600 | 601 | if virtpath.startswith(r'/proc/'): 602 | pid, _, vfname = virtpath[6:].partition(r'/') 603 | 604 | # 42000 is a magic number indicating the remote process' pid 605 | # see: https://sourceware.org/bugzilla/show_bug.cgi?id=17760 606 | if pid == '42000': 607 | vfmap = { 608 | 'maps': lambda: partial(QlProcFS.self_map, self.ql.mem) 609 | } 610 | 611 | if vfname in vfmap and not self.ql.os.fs_mapper.has_mapping(virtpath): 612 | self.ql.add_fs_mapper(virtpath, vfmap[vfname]()) 613 | 614 | if self.ql.os.fs_mapper.has_mapping(virtpath): 615 | # Mapped object by itself is not backed with a host fd and thus a tempfile can 616 | # 1. Make pread easy to implement and avoid duplicate code like seek, fd etc. 617 | # 2. Avoid fd clash if we assign a generated fd. 618 | tfile = tempfile.TemporaryFile("rb+") 619 | tfile.write(self.ql.os.fs_mapper.open(virtpath, "rb+").read()) 620 | tfile.seek(0, os.SEEK_SET) 621 | 622 | fd = tfile.fileno() 623 | self.fake_procfs[fd] = tfile 624 | else: 625 | host_path = self.ql.os.path.PureVirtualPath(path) 626 | if not host_path.is_absolute(): 627 | host_path = self.ql.os.path.virtual_to_host_path(path) 628 | 629 | self.ql.log.debug(f'{PROMPT} target host path: {host_path}') 630 | 631 | if os.path.exists(str(host_path)): 632 | if hasattr(os, 'pread'): 633 | fd = os.open(host_path, flags, mode) 634 | else: 635 | fi = open(host_path, 'rb') 636 | self.filearr.append(fi) 637 | fd=len(self.filearr) 638 | 639 | return f'F{fd:x}' 640 | 641 | elif op == 'pread': 642 | fd, count, offset = (int(p, 16) for p in params) 643 | if hasattr(os, 'pread'): 644 | data = os.pread(fd, count, offset) 645 | else: 646 | fi=self.filearr[fd-1] 647 | fi.seek(offset) 648 | data = fi.read(count) 649 | return f'F{len(data):x};'.encode() + data 650 | 651 | elif op == 'close': 652 | fd, *_ = params 653 | fd = int(fd, 16) 654 | if hasattr(os, 'pread'): 655 | os.close(fd) 656 | else: 657 | fi=self.filearr[fd-1] 658 | fi.close() 659 | self.filearr.remove(fi) 660 | 661 | if fd in self.fake_procfs: 662 | del self.fake_procfs[fd] 663 | 664 | return 'F0' 665 | 666 | return REPLY_EMPTY 667 | 668 | elif subcmd.startswith('Kill'): 669 | return handle_k('') 670 | 671 | elif subcmd.startswith('Cont'): 672 | # remove 'Cont' prefix 673 | data = subcmd[len('Cont'):] 674 | 675 | if data == '?': 676 | # note 't' and 'r' are currently not supported 677 | return ';'.join(('vCont', 'c', 'C', 's', 'S')) 678 | 679 | elif data.startswith(';'): 680 | groups = subcmd.split(';')[1:] 681 | 682 | for grp in groups: 683 | cmd, *tid = grp.split(':', maxsplit=1) 684 | 685 | if cmd in ('c', f'C{SIGTRAP:02x}'): 686 | return handle_c('') 687 | 688 | elif cmd in ('s', f'S{SIGTRAP:02x}'): 689 | return handle_s('') 690 | 691 | # FIXME: not sure how to handle multiple command 692 | # groups, so handling just the first one 693 | break 694 | 695 | return REPLY_EMPTY 696 | 697 | def handle_s(subcmd: str) -> Reply: 698 | """Perform a single step. 699 | """ 700 | 701 | self.gdb.resume_emu(steps=1) 702 | 703 | # if emulation has been stopped, signal program termination 704 | #if self.ql.emu_state is QL_STATE.STOPPED: 705 | # return f'S{SIGTERM:02x}' 706 | 707 | # otherwise, this is just single stepping 708 | return f'S{SIGTRAP:02x}' 709 | 710 | def handle_X(subcmd: str) -> Reply: 711 | """Write data to memory. 712 | """ 713 | 714 | params, data = subcmd.split(':', maxsplit=1) 715 | addr, length = (int(p, 16) for p in params.split(',')) 716 | 717 | if length != len(data): 718 | return 'E00' 719 | 720 | try: 721 | if data: 722 | self.ql.mem.write(addr, data.encode(ENCODING)) 723 | except UcError as ex: 724 | return f'E{ex.errno:02d}' 725 | else: 726 | return REPLY_OK 727 | 728 | def handle_Z(subcmd: str) -> Reply: 729 | """Insert breakpoints or watchpoints. 730 | """ 731 | 732 | params, *conds = subcmd.split(';') 733 | type, addr, kind = (int(p, 16) for p in params.split(',')) 734 | 735 | # type values: 736 | # 0 = sw breakpoint 737 | # 1 = hw breakpoint 738 | # 2 = write watchpoint 739 | # 3 = read watchpoint 740 | # 4 = access watchpoint 741 | 742 | if type == 0: 743 | success = self.gdb.bp_insert(addr, kind) 744 | return REPLY_OK if success else 'E22' 745 | 746 | if type == 1 or type == 2 or type == 3: 747 | success = self.gdb.bp_insert_hw(addr, kind,type) 748 | return REPLY_OK if success else 'E22' 749 | 750 | return REPLY_EMPTY 751 | 752 | def handle_z(subcmd: str) -> Reply: 753 | """Remove breakpoints or watchpoints. 754 | """ 755 | 756 | type, addr, kind = (int(p, 16) for p in subcmd.split(',')) 757 | 758 | if type == 0: 759 | success = self.gdb.bp_remove(addr, kind) 760 | 761 | return REPLY_OK if success else 'E22' 762 | 763 | return REPLY_EMPTY 764 | 765 | handlers = { 766 | '!': handle_exclaim, 767 | '?': handle_qmark, 768 | 'c': handle_c, 769 | 'C': handle_c, # this is intentional; not a typo 770 | 'D': handle_D, 771 | 'g': handle_g, 772 | 'G': handle_G, 773 | 'H': handle_H, 774 | 'k': handle_k, 775 | 'm': handle_m, 776 | 'M': handle_M, 777 | 'p': handle_p, 778 | 'P': handle_P, 779 | 'q': handle_q, 780 | 'Q': handle_Q, 781 | 's': handle_s, 782 | 'v': handle_v, 783 | 'X': handle_X, 784 | 'Z': handle_Z, 785 | 'z': handle_z 786 | } 787 | 788 | # main server loop 789 | for packet in server.readpackets(): 790 | if server.ack_mode: 791 | server.send(REPLY_ACK, raw=True) 792 | server.log.debug('[sent ack]') 793 | 794 | cmd, subcmd = packet[0], packet[1:] 795 | handler = handlers.get(f'{cmd:c}') 796 | 797 | if handler: 798 | reply = handler(subcmd.decode(ENCODING)) 799 | server.send(reply) 800 | 801 | if killed: 802 | break 803 | else: 804 | self.ql.log.info(f'{PROMPT} command not supported') 805 | server.send(REPLY_EMPTY) 806 | 807 | server.close() 808 | 809 | 810 | class GdbSerialConn: 811 | """Serial connection handler. 812 | """ 813 | 814 | # default recieve buffer size 815 | BUFSIZE = 4096 816 | 817 | def __init__(self, ipaddr: str, port: int, logger: Logger) -> None: 818 | """Create a new gdb serial connection handler. 819 | 820 | Args: 821 | ipaddr : ip address to bind the socket to 822 | port : port number to listen on 823 | logger : logger instance to use 824 | """ 825 | 826 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 827 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 828 | sock.bind((ipaddr, port)) 829 | sock.listen() 830 | 831 | self.log = logger 832 | self.log.info(f'{PROMPT} listening on {ipaddr}:{port:d}') 833 | 834 | client, _ = sock.accept() 835 | 836 | self.sock = sock 837 | self.client = client 838 | 839 | # ack mode should be turend on by default 840 | self.ack_mode = True 841 | 842 | def close(self): 843 | """Close the gdb serial connection handler and release its resources. 844 | """ 845 | 846 | self.client.close() 847 | self.sock.close() 848 | 849 | def readpackets(self) -> Iterator[bytes]: 850 | """Iterate through incoming packets in an active connection until 851 | it is terminated. 852 | """ 853 | 854 | pattern = re.compile(br'^\$(?P[^#]*)#(?P[0-9a-fA-F]{2})') 855 | buffer = bytearray() 856 | 857 | while True: 858 | try: 859 | incoming = self.client.recv(self.BUFSIZE) 860 | except ConnectionError: 861 | break 862 | 863 | # remote connection closed 864 | if not incoming: 865 | break 866 | 867 | buffer += incoming 868 | 869 | # discard incoming acks 870 | if buffer.startswith(REPLY_ACK): 871 | del buffer[0] 872 | 873 | packet = pattern.match(buffer) 874 | 875 | # if there is no match, the rest of the packet might be missing 876 | if not packet: 877 | continue 878 | 879 | data = packet['data'] 880 | read_csum = int(packet['checksum'], 16) 881 | calc_csum = GdbSerialConn.checksum(data) 882 | 883 | if read_csum != calc_csum: 884 | raise IOError(f'checksum error: expected {calc_csum:02x} but got {read_csum:02x}') 885 | 886 | # follow gdbserver debug output format 887 | self.log.debug(f'getpkt ("{GdbSerialConn.__printable_prefix(data).decode(ENCODING)}");') 888 | 889 | data = GdbSerialConn.rle_decode(data) 890 | data = GdbSerialConn.unescape(data) 891 | 892 | del buffer[:packet.endpos] 893 | yield data 894 | 895 | def send(self, data: Reply, raw: bool = False) -> None: 896 | """Send out a packet. 897 | 898 | Args: 899 | data : data to send out 900 | raw : whether to encapsulate the data with standard header and 901 | checksum or leave it raw 902 | """ 903 | 904 | if type(data) is str: 905 | data = data.encode(ENCODING) 906 | 907 | assert type(data) is bytes 908 | 909 | if raw: 910 | packet = data 911 | else: 912 | data = GdbSerialConn.escape(data) 913 | data = GdbSerialConn.rle_encode(data) 914 | 915 | packet = b'$' + data + b'#' + f'{GdbSerialConn.checksum(data):02x}'.encode() 916 | 917 | # follow gdbserver debug output format 918 | self.log.debug(f'putpkt ("{GdbSerialConn.__printable_prefix(data).decode(ENCODING)}");') 919 | 920 | self.client.sendall(packet) 921 | 922 | @staticmethod 923 | def __printable_prefix(data: bytes) -> bytes: 924 | """Follow the gnu gdbserver debug message format which emits only the 925 | printable prefix of a packet (either incoming or outgoing). Note that 926 | despite of its name, it includes non-printable characters as well. 927 | 928 | Args: 929 | data : packet data to scan 930 | 931 | Returns: a prefix of the specified data buffer 932 | """ 933 | 934 | def __isascii(ch: int) -> bool: 935 | return 0 < ch < 0x80 936 | 937 | if data.isascii(): 938 | return data 939 | 940 | return data[:next((i for i, ch in enumerate(data) if not __isascii(ch)), len(data))] 941 | 942 | @staticmethod 943 | def escape(data: bytes) -> bytes: 944 | """Escape data according to gdb protocol escaping rules. 945 | """ 946 | 947 | def __repl(m: 're.Match[bytes]') -> bytes: 948 | ch, *_ = m[0] 949 | 950 | return bytes([ord('}'), ch ^ 0x20]) 951 | 952 | return re.sub(br'[*#$}]', __repl, data, flags=re.DOTALL) 953 | 954 | @staticmethod 955 | def unescape(data: bytes) -> bytes: 956 | """Unescape data according to gdb protocol escaping rules. 957 | """ 958 | 959 | def __repl(m: 're.Match[bytes]') -> bytes: 960 | _, ch = m[0] 961 | 962 | return bytes([ch ^ 0x20]) 963 | 964 | return re.sub(br'}.', __repl, data, flags=re.DOTALL) 965 | 966 | @staticmethod 967 | def rle_encode(data: bytes) -> bytes: 968 | """Compact data using run-length encoding. 969 | """ 970 | 971 | def __simple_rep(b: bytes, times: int) -> bytes: 972 | return b * times 973 | 974 | def __runlen_rep(b: bytes, times: int) -> bytes: 975 | return b + b'*' + bytes([times - 1 + 29]) 976 | 977 | def __encode_rep(b: bytes, times: int) -> bytes: 978 | assert times > 0, 'time should be a positive value' 979 | 980 | if 0 < times < 4: 981 | return __simple_rep(b, times) 982 | 983 | elif times == 6+1 or times == 7+1: 984 | return __runlen_rep(b, 6) + __encode_rep(b, times - 6) 985 | 986 | else: 987 | return __runlen_rep(b, times) 988 | 989 | def __repl(m: 're.Match[bytes]') -> bytes: 990 | repetition = m[0] 991 | 992 | ch = repetition[0:1] 993 | times = len(repetition) 994 | 995 | return __encode_rep(ch, times) 996 | 997 | return re.sub(br'(.)\1{3,96}', __repl, data, flags=re.DOTALL) 998 | 999 | @staticmethod 1000 | def rle_decode(data: bytes) -> bytes: 1001 | """Expand run-length encoded data. 1002 | """ 1003 | 1004 | def __repl(m: 're.Match[bytes]') -> bytes: 1005 | ch, _, times = m[0] 1006 | 1007 | return bytes([ch] * (1 + times - 29)) 1008 | 1009 | return re.sub(br'.\*.', __repl, data, flags=re.DOTALL) 1010 | 1011 | @staticmethod 1012 | def checksum(data: bytes) -> int: 1013 | return sum(data) & 0xff 1014 | -------------------------------------------------------------------------------- /qiling/unicorn/2.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbwang505/unicorn-whpx/c1cc4de7eb5f2424195ba885ab4778c3dd865bed/qiling/unicorn/2.7z -------------------------------------------------------------------------------- /qiling/unicorn/unicorn_const.py: -------------------------------------------------------------------------------- 1 | # For Unicorn Engine. AUTO-GENERATED FILE, DO NOT EDIT [unicorn_const.py] 2 | UC_API_MAJOR = 2 3 | 4 | UC_API_MINOR = 0 5 | UC_API_PATCH = 1 6 | UC_API_EXTRA = 255 7 | UC_VERSION_MAJOR = 2 8 | 9 | UC_VERSION_MINOR = 0 10 | UC_VERSION_PATCH = 1 11 | UC_VERSION_EXTRA = 255 12 | UC_SECOND_SCALE = 1000000 13 | UC_MILISECOND_SCALE = 1000 14 | UC_ARCH_ARM = 1 15 | UC_ARCH_ARM64 = 2 16 | UC_ARCH_MIPS = 3 17 | UC_ARCH_X86 = 4 18 | UC_ARCH_PPC = 5 19 | UC_ARCH_SPARC = 6 20 | UC_ARCH_M68K = 7 21 | UC_ARCH_RISCV = 8 22 | UC_ARCH_S390X = 9 23 | UC_ARCH_TRICORE = 10 24 | UC_ARCH_X86_WHPX = 11 25 | UC_ARCH_MAX = 11 26 | 27 | UC_MODE_LITTLE_ENDIAN = 0 28 | UC_MODE_BIG_ENDIAN = 1073741824 29 | 30 | UC_MODE_ARM = 0 31 | UC_MODE_THUMB = 16 32 | UC_MODE_MCLASS = 32 33 | UC_MODE_V8 = 64 34 | UC_MODE_ARMBE8 = 1024 35 | UC_MODE_ARM926 = 128 36 | UC_MODE_ARM946 = 256 37 | UC_MODE_ARM1176 = 512 38 | UC_MODE_MICRO = 16 39 | UC_MODE_MIPS3 = 32 40 | UC_MODE_MIPS32R6 = 64 41 | UC_MODE_MIPS32 = 4 42 | UC_MODE_MIPS64 = 8 43 | UC_MODE_16 = 2 44 | UC_MODE_32 = 4 45 | UC_MODE_64 = 8 46 | UC_MODE_PPC32 = 4 47 | UC_MODE_PPC64 = 8 48 | UC_MODE_QPX = 16 49 | UC_MODE_SPARC32 = 4 50 | UC_MODE_SPARC64 = 8 51 | UC_MODE_V9 = 16 52 | UC_MODE_RISCV32 = 4 53 | UC_MODE_RISCV64 = 8 54 | 55 | UC_ERR_OK = 0 56 | UC_ERR_NOMEM = 1 57 | UC_ERR_ARCH = 2 58 | UC_ERR_HANDLE = 3 59 | UC_ERR_MODE = 4 60 | UC_ERR_VERSION = 5 61 | UC_ERR_READ_UNMAPPED = 6 62 | UC_ERR_WRITE_UNMAPPED = 7 63 | UC_ERR_FETCH_UNMAPPED = 8 64 | UC_ERR_HOOK = 9 65 | UC_ERR_INSN_INVALID = 10 66 | UC_ERR_MAP = 11 67 | UC_ERR_WRITE_PROT = 12 68 | UC_ERR_READ_PROT = 13 69 | UC_ERR_FETCH_PROT = 14 70 | UC_ERR_ARG = 15 71 | UC_ERR_READ_UNALIGNED = 16 72 | UC_ERR_WRITE_UNALIGNED = 17 73 | UC_ERR_FETCH_UNALIGNED = 18 74 | UC_ERR_HOOK_EXIST = 19 75 | UC_ERR_RESOURCE = 20 76 | UC_ERR_EXCEPTION = 21 77 | UC_MEM_READ = 16 78 | UC_MEM_WRITE = 17 79 | UC_MEM_FETCH = 18 80 | UC_MEM_READ_UNMAPPED = 19 81 | UC_MEM_WRITE_UNMAPPED = 20 82 | UC_MEM_FETCH_UNMAPPED = 21 83 | UC_MEM_WRITE_PROT = 22 84 | UC_MEM_READ_PROT = 23 85 | UC_MEM_FETCH_PROT = 24 86 | UC_MEM_READ_AFTER = 25 87 | 88 | UC_TCG_OP_SUB = 0 89 | UC_TCG_OP_FLAG_CMP = 1 90 | UC_TCG_OP_FLAG_DIRECT = 2 91 | UC_HOOK_INTR = 1 92 | UC_HOOK_INSN = 2 93 | UC_HOOK_CODE = 4 94 | UC_HOOK_BLOCK = 8 95 | UC_HOOK_MEM_READ_UNMAPPED = 16 96 | UC_HOOK_MEM_WRITE_UNMAPPED = 32 97 | UC_HOOK_MEM_FETCH_UNMAPPED = 64 98 | UC_HOOK_MEM_READ_PROT = 128 99 | UC_HOOK_MEM_WRITE_PROT = 256 100 | UC_HOOK_MEM_FETCH_PROT = 512 101 | UC_HOOK_MEM_READ = 1024 102 | UC_HOOK_MEM_WRITE = 2048 103 | UC_HOOK_MEM_FETCH = 4096 104 | UC_HOOK_MEM_READ_AFTER = 8192 105 | UC_HOOK_INSN_INVALID = 16384 106 | UC_HOOK_EDGE_GENERATED = 32768 107 | UC_HOOK_TCG_OPCODE = 65536 108 | UC_HOOK_HARDWARE_EXECUTE = 131072 109 | UC_HOOK_HARDWARE_READ = 262144 110 | UC_HOOK_HARDWARE_WRITE = 524288 111 | UC_HOOK_HARDWARE_READWRITE = 1048576 112 | 113 | UC_HOOK_MEM_UNMAPPED = 112 114 | UC_HOOK_MEM_PROT = 896 115 | UC_HOOK_MEM_READ_INVALID = 144 116 | UC_HOOK_MEM_WRITE_INVALID = 288 117 | UC_HOOK_MEM_FETCH_INVALID = 576 118 | UC_HOOK_MEM_INVALID = 1008 119 | UC_HOOK_MEM_VALID = 7168 120 | UC_QUERY_MODE = 1 121 | UC_QUERY_PAGE_SIZE = 2 122 | UC_QUERY_ARCH = 3 123 | UC_QUERY_TIMEOUT = 4 124 | 125 | UC_CTL_IO_NONE = 0 126 | UC_CTL_IO_WRITE = 1 127 | UC_CTL_IO_READ = 2 128 | UC_CTL_IO_READ_WRITE = 3 129 | 130 | UC_CTL_UC_MODE = 0 131 | UC_CTL_UC_PAGE_SIZE = 1 132 | UC_CTL_UC_ARCH = 2 133 | UC_CTL_UC_TIMEOUT = 3 134 | UC_CTL_UC_USE_EXITS = 4 135 | UC_CTL_UC_EXITS_CNT = 5 136 | UC_CTL_UC_EXITS = 6 137 | UC_CTL_CPU_MODEL = 7 138 | UC_CTL_TB_REQUEST_CACHE = 8 139 | UC_CTL_TB_REMOVE_CACHE = 9 140 | UC_CTL_TB_FLUSH = 10 141 | 142 | UC_PROT_NONE = 0 143 | UC_PROT_READ = 1 144 | UC_PROT_WRITE = 2 145 | UC_PROT_EXEC = 4 146 | UC_PROT_ALL = 7 147 | -------------------------------------------------------------------------------- /qiling/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Cross Platform and Multi Architecture Advanced Binary Emulation Framework 4 | # 5 | 6 | from typing import Optional 7 | 8 | from qiling import Qiling 9 | 10 | # this code is partially based on uDbg 11 | # @see: https://github.com/iGio90/uDdbg 12 | 13 | PROMPT = r'gdb>' 14 | 15 | 16 | class QlGdbUtils: 17 | def __init__(self, ql: Qiling, entry_point: int, exit_point: int): 18 | self.ql = ql 19 | 20 | self.exit_point = exit_point 21 | self.swbp = set() 22 | self.last_bp = None 23 | self.bpdic={} 24 | def __entry_point_hook(ql: Qiling): 25 | ql.hook_del(ep_hret) 26 | ql.hook_code(self.dbg_hook) 27 | 28 | ql.log.info(f'{PROMPT} stopped at entry point: {ql.arch.regs.arch_pc:#x}') 29 | ql.stop() 30 | 31 | # set a one-time hook to be dispatched upon reaching program entry point. 32 | # that hook will be used to set up the breakpoint handling hook 33 | ep_hret = ql.hook_address(__entry_point_hook, entry_point) 34 | 35 | def dbg_hook(self, ql: Qiling, address: int, size: int): 36 | if getattr(ql.arch, 'is_thumb', False): 37 | address |= 1 38 | if self.last_bp is None: 39 | # resuming emulation after hitting a breakpoint will re-enter this hook. 40 | # avoid an endless hooking loop by detecting and skipping this case 41 | if address == self.last_bp: 42 | self.last_bp = None 43 | 44 | elif address in self.swbp: 45 | self.last_bp = address 46 | 47 | ql.log.info(f'{PROMPT} breakpoint hit, stopped at {address:#x}') 48 | ql.stop() 49 | self.last_bp = address 50 | 51 | def dbg_hook_safe(self, ql: Qiling): 52 | ql.stop() 53 | self.last_bp = ql.arch.regs.arch_pc 54 | self.ql.log.info(f'{PROMPT} dbg_hook_safe ql stop at {self.last_bp:#x}') 55 | 56 | def bp_insert(self, addr: int, size: int): 57 | targets = set(addr + i for i in range(size or 1)) 58 | 59 | if targets.intersection(self.swbp): 60 | return False 61 | 62 | for bp in targets: 63 | self.swbp.add(bp) 64 | 65 | self.ql.log.info(f'{PROMPT} breakpoint added at {addr:#x}') 66 | 67 | self.bpdic[addr]=self.ql.hook_address(self.dbg_hook_safe,addr ) 68 | 69 | return True 70 | 71 | def bp_insert_hw(self, addr: int, size: int,type: int): 72 | targets = set(addr + i for i in range(size or 1)) 73 | 74 | if targets.intersection(self.swbp): 75 | return False 76 | 77 | for bp in targets: 78 | self.swbp.add(bp) 79 | 80 | self.ql.log.info(f'{PROMPT} breakpoint added at {addr:#x}') 81 | 82 | self.bpdic[addr]=self.ql.hook_address_hw(self.dbg_hook_safe,addr,type ) 83 | 84 | return True 85 | 86 | def bp_remove(self, addr: int, size: int) -> bool: 87 | targets = set(addr + i for i in range(size or 1)) 88 | 89 | if not targets.issubset(self.swbp): 90 | return False 91 | 92 | for bp in targets: 93 | self.swbp.remove(bp) 94 | 95 | self.ql.log.info(f'{PROMPT} breakpoint removed from {addr:#x}') 96 | self.ql.hook_del(self.bpdic[addr]) 97 | self.bpdic.pop(addr) 98 | return True 99 | 100 | def resume_emu(self, address: Optional[int] = None, steps: int = 0): 101 | if address is None: 102 | address = self.ql.arch.regs.arch_pc 103 | 104 | if getattr(self.ql.arch, 'is_thumb', False): 105 | address |= 0b1 106 | 107 | op = f'stepping {steps} instructions' if steps else 'resuming' 108 | self.ql.log.info(f'{PROMPT} {op} from {address:#x}') 109 | 110 | self.ql.emu_start(address, self.exit_point, count=steps) 111 | -------------------------------------------------------------------------------- /unicorn-whpx-core/softmmuwhpx/cpus.c: -------------------------------------------------------------------------------- 1 | /* 2 | * QEMU System Emulator 3 | * 4 | * Copyright (c) 2003-2008 Fabrice Bellard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | #include "sysemu/tcg.h" 26 | #include "sysemu/cpus.h" 27 | #include "qemu/bitmap.h" 28 | #include "tcg/tcg.h" 29 | #include "exec/tb-hash.h" 30 | #include "accel/tcg/translate-all.h" 31 | 32 | #include "uc_priv.h" 33 | #include "WinHvDefs.hpp" 34 | 35 | #define DumpExitReason(name) \ 36 | printf("ExitReason:=> " #name ",Run from rip=%016llx to %016llx\r\n", \ 37 | ripvalold, ripval) 38 | #define DumpExceptionType(name) \ 39 | if (vcpu->exit_ctx.VpException.ExceptionType == name) { \ 40 | printf("VpException.ExceptionType:=> " #name \ 41 | " ,ExceptionTypeCode = %08x ,ExceptionParameter = " \ 42 | "%016llx,retry\r\n", \ 43 | vcpu->exit_ctx.VpException.ExceptionType, \ 44 | vcpu->exit_ctx.VpException.ExceptionParameter); \ 45 | } 46 | 47 | extern const uint8_t whpx_breakpoint_instruction; 48 | uint64_t whpx_hardware_breakpoint_config_single_step(CPUState *cpu, 49 | uintptr_t addr); 50 | uint64_t whpx_check_hardware_breakpoint(); 51 | int whpx_handle_mmio(CPUState *cpu, WHV_MEMORY_ACCESS_CONTEXT *ctx); 52 | int whpx_handle_portio(CPUState *cpu, WHV_X64_IO_PORT_ACCESS_CONTEXT *ctx); 53 | int whpx_first_vcpu_starting(CPUState *cpu); 54 | int whpx_config_nested_breakpoint_restore(CPUState *cpu, uintptr_t restorerip); 55 | void dump_stack_ret_addr(uint8_t *rspaddr, uintptr_t rspval, ULONG difflen); 56 | int64_t cpu_icount_to_ns(int64_t icount) 57 | { 58 | // return icount << atomic_read(&timers_state.icount_time_shift); 59 | // from configure_icount(QemuOpts *opts, Error **errp) 60 | /* 125MIPS seems a reasonable initial guess at the guest speed. 61 | It will be corrected fairly quickly anyway. */ 62 | // timers_state.icount_time_shift = 3; 63 | 64 | return icount << 3; 65 | } 66 | 67 | bool cpu_is_stopped(CPUState *cpu) 68 | { 69 | return cpu->stopped; 70 | } 71 | 72 | /* return the time elapsed in VM between vm_start and vm_stop. Unless 73 | * icount is active, cpu_get_ticks() uses units of the host CPU cycle 74 | * counter. 75 | */ 76 | int64_t cpu_get_ticks(void) 77 | { 78 | return cpu_get_host_ticks(); 79 | } 80 | 81 | /* Return the monotonic time elapsed in VM, i.e., 82 | * the time between vm_start and vm_stop 83 | */ 84 | int64_t cpu_get_clock(void) 85 | { 86 | return get_clock(); 87 | } 88 | 89 | static bool cpu_can_run(CPUState *cpu) 90 | { 91 | if (cpu->stop) { 92 | return false; 93 | } 94 | if (cpu_is_stopped(cpu)) { 95 | return false; 96 | } 97 | return cpu->uc->stop_request == false; 98 | ; 99 | } 100 | 101 | static void cpu_handle_guest_debug(CPUState *cpu) 102 | { 103 | cpu->stopped = true; 104 | } 105 | void whpx_vcpu_pre_run(CPUState *cpu); 106 | void whpx_vcpu_post_run(CPUState *cpu); 107 | 108 | static int whpx_dump_stack(struct uc_struct *uc) 109 | { 110 | char buf[0x1000]; 111 | size_t len = 0x1000; 112 | CPUState *cpu = uc->cpu; 113 | X86CPU *x86_cpu = X86_CPU(cpu); 114 | uint64_t ripval = 0; 115 | whpx_get_reg(WHvX64RegisterRsp, &ripval); 116 | // len = 0x100; 117 | cpu_memory_rw_whpx(cpu, ripval, buf, len, false); 118 | dumpbuf(buf, 0x100); 119 | dump_stack_ret_addr(buf, ripval, len); 120 | } 121 | 122 | static int whpx_cpu_exec(struct uc_struct *uc) 123 | { 124 | uc_tb cur_tb, prev_tb; 125 | struct hook *hook; 126 | HOOK_FOREACH_VAR_DECLARE; 127 | char buf[0x1000]; 128 | int r; 129 | size_t len = 0x1000; 130 | bool finish = false; 131 | whpx_state *whpx = &whpx_global; 132 | CPUState *cpu = uc->cpu; 133 | X86CPU *x86_cpu = X86_CPU(cpu); 134 | whpx_vcpu *vcpu = get_whpx_vcpu(x86_cpu); 135 | while (!uc->exit_request) { 136 | 137 | // qemu_clock_enable(QEMU_CLOCK_VIRTUAL, 138 | // (cpu->singlestep_enabled & SSTEP_NOTIMER) == 0); 139 | if (cpu_can_run(cpu)) { 140 | 141 | whpx_vcpu_pre_run(cpu); 142 | uc->quit_request = false; 143 | uint64_t ripvalold = 0; 144 | uint64_t ripval = 0; 145 | whpx_get_reg(WHvX64RegisterRip, &ripvalold); 146 | cpu_memory_rw_whpx(cpu, ripvalold, buf, 0x1000, false); 147 | printf("WHPX:whpx_vcpu_pre_run from rip=%016llx\r\n", ripvalold); 148 | r = WHvRunVirtualProcessor(whpx->partition, whpx->cpu_index, 149 | &vcpu->exit_ctx, sizeof(vcpu->exit_ctx)); 150 | if (FAILED(r)) { 151 | printf("WHPX: Failed to WHvRunVirtualProcessor," 152 | " hr=%08lx\r\n", 153 | r); 154 | uc->exit_request = true; 155 | break; 156 | } 157 | whpx_get_reg(WHvX64RegisterRip, &ripval); 158 | whpx_vcpu_post_run(cpu); 159 | printf("WHPX:whpx_vcpu_post_run from rip=%016llx\r\n", ripval); 160 | /*printf( 161 | "WHPX:WHvRunVirtualProcessor from rip=%016llx to 162 | rip=%016llx\r\n", ripvalold, ripval);*/ 163 | 164 | // quit current TB but continue emulating? 165 | if (uc->quit_request) { 166 | // reset stop_request 167 | uc->stop_request = false; 168 | 169 | // resume cpu 170 | // cpu->halted = 0; 171 | cpu->exit_request = 0; 172 | cpu->exception_index = -1; 173 | cpu_resume(cpu); 174 | } else if (uc->stop_request) { 175 | // printf(">>> got STOP request!!!\n"); 176 | finish = true; 177 | break; 178 | } 179 | 180 | // save invalid memory access error & quit 181 | if (uc->invalid_error) { 182 | // printf(">>> invalid memory accessed, STOP = %u!!!\n", 183 | // env->invalid_error); 184 | finish = true; 185 | break; 186 | } 187 | if (cpu->uc->size_recur_mem > 1) { 188 | printf("WHPX:whpx_vcpu_post_run size_recur_mem reach max " 189 | "count=%016llx fatal error tear down\r\n", 190 | cpu->uc->size_recur_mem); 191 | finish = true; 192 | break; 193 | } 194 | 195 | // printf(">>> stop with r = %x, HLT=%x\n", r, EXCP_HLT); 196 | if (r == EXCP_DEBUG) { 197 | cpu_handle_guest_debug(cpu); 198 | break; 199 | } 200 | if (r == EXCP_HLT) { 201 | // printf(">>> got HLT!!!\n"); 202 | finish = true; 203 | break; 204 | } 205 | 206 | switch (vcpu->exit_ctx.ExitReason) { 207 | case WHvRunVpExitReasonMemoryAccess: 208 | DumpExitReason(WHvRunVpExitReasonMemoryAccess); 209 | 210 | uint64_t MemoryAccessGva = vcpu->exit_ctx.MemoryAccess.Gva; 211 | if (cpu->uc->size_recur_mem == 0) { 212 | whpx_dump_stack(uc); 213 | } 214 | WhSeFindAllocationGvaByGpa( 215 | whpx, vcpu->exit_ctx.MemoryAccess.Gpa, &ripval); 216 | printf("try to fetch MemoryAccess times %d, Gpa :=> " 217 | "%016llx,Gva :=> %016llx,Solved Gva :=> %016llx\r\n", 218 | cpu->uc->size_recur_mem, vcpu->exit_ctx.MemoryAccess.Gpa, 219 | vcpu->exit_ctx.MemoryAccess.Gva, ripval); 220 | cpu->uc->size_recur_mem++; 221 | if (whpx_handle_mmio(cpu, &vcpu->exit_ctx.MemoryAccess) != 0) { 222 | printf("whpx_handle_mmio failed from " 223 | "gpa=%016llx,gva=%016llx\r\n", 224 | vcpu->exit_ctx.MemoryAccess.Gpa, 225 | vcpu->exit_ctx.MemoryAccess.Gva); 226 | //这个把他置位如果还有错就终止 227 | uc->invalid_error = true; 228 | } else { 229 | printf("whpx_handle_mmio EmulationSuccessful from " 230 | "gpa=%016llx,gva=%016llx\r\n", 231 | vcpu->exit_ctx.MemoryAccess.Gpa, 232 | vcpu->exit_ctx.MemoryAccess.Gva); 233 | if (uc_mem_read_hook(cpu->uc, MemoryAccessGva, len) == 234 | UC_ERR_OK) { 235 | 236 | printf("fix:MemoryAccess from buf=%016llx\r\n", 237 | vcpu->exit_ctx.MemoryAccess.Gva); 238 | break; 239 | 240 | } else { 241 | 242 | printf("ERROR:MemoryAccess from buf=%016llx\r\n", 243 | vcpu->exit_ctx.MemoryAccess.Gva); 244 | 245 | break; 246 | } 247 | } 248 | finish = false; 249 | break; 250 | 251 | case WHvRunVpExitReasonX64IoPortAccess: 252 | DumpExitReason(WHvRunVpExitReasonX64IoPortAccess); 253 | 254 | if (whpx_handle_portio(cpu, &vcpu->exit_ctx.IoPortAccess)) { 255 | finish = true; 256 | } 257 | 258 | break; 259 | ; 260 | case WHvRunVpExitReasonException: { 261 | DumpExitReason(WHvRunVpExitReasonException); 262 | if ((vcpu->exit_ctx.VpException.ExceptionType == 263 | WHvX64ExceptionTypeDebugTrapOrFault) && 264 | (vcpu->exit_ctx.VpException.InstructionByteCount >= 1) && 265 | (vcpu->exit_ctx.VpException.InstructionBytes[0] == 266 | whpx_breakpoint_instruction)) { 267 | /* Stopped at a software breakpoint. */ 268 | cpu->exception_index = EXCP_DEBUG; 269 | printf("whpx_breakpoint_instruction=%016llx\r\n", ripval); 270 | } else if ((vcpu->exit_ctx.VpException.ExceptionType == 271 | WHvX64ExceptionTypeDebugTrapOrFault) && 272 | cpu->singlestep_enabled) { 273 | /* 274 | * Just finished stepping over a breakpoint, but the 275 | * gdb does not expect us to do single-stepping. 276 | * Don't do anything special. 277 | */ 278 | cpu->exception_index = EXCP_INTERRUPT; 279 | if (cpu->mem_io_pc) { 280 | printf( 281 | "whpx_config_nested_breakpoint_restore=%016llx\r\n", 282 | cpu->mem_io_pc); 283 | whpx_config_nested_breakpoint_restore(cpu, 284 | cpu->mem_io_pc); 285 | 286 | //虚拟的恢复断点模式,如果之前是虚拟单步执行模式,如果之前是真实单步执行模式还是走原来逻辑 287 | if (!cpu->singlestep_enabled) { 288 | 289 | printf("whpx_config_nested_breakpoint_restore " 290 | "resume normal debug=%" 291 | "016llx\r\n", 292 | cpu->mem_io_pc); 293 | finish == false; 294 | break; 295 | } else { 296 | //如果之前还是trap模式中断 297 | printf("WHvX64ExceptionTypeDebugTrapOrFault org " 298 | "step mode=%" 299 | "016llx\r\n", 300 | ripval); 301 | finish = true; 302 | } 303 | //如果之前是单步模式分发到callback 304 | } else { 305 | printf( 306 | "WHvX64ExceptionTypeDebugTrapOrFault=%016llx\r\n", 307 | ripval); 308 | finish = true; 309 | } 310 | } else if (vcpu->exit_ctx.VpException.ExceptionType == 311 | WHvX64ExceptionTypeDebugTrapOrFault) { 312 | 313 | uint64_t addrhw = whpx_check_hardware_breakpoint(); 314 | printf("whpx_check_hardware_breakpoint " 315 | "rip=%016llx,addr=%016llx \r\n", 316 | ripval, addrhw); 317 | if (addrhw) { 318 | cpu->mem_io_pc = addrhw; 319 | /*whpx_hardware_breakpoint_config_single_step(cpu, 320 | addrhw);*/ 321 | HOOK_FOREACH(uc, hook, UC_HOOK_HARDWARE_EXECUTE) 322 | { 323 | 324 | if (hook->to_delete) { 325 | continue; 326 | } 327 | 328 | if (HOOK_BOUND_CHECK(hook, (uint64_t)addrhw)) { 329 | ((uc_cb_hookcode_t)hook->callback)( 330 | uc, addrhw, 1, hook->user_data); 331 | } 332 | } 333 | 334 | HOOK_FOREACH(uc, hook, UC_HOOK_HARDWARE_READ) 335 | { 336 | 337 | if (hook->to_delete) { 338 | continue; 339 | } 340 | 341 | if (HOOK_BOUND_CHECK(hook, (uint64_t)addrhw)) { 342 | ((uc_cb_hookcode_t)hook->callback)( 343 | uc, addrhw, 1, hook->user_data); 344 | } 345 | } 346 | 347 | HOOK_FOREACH(uc, hook, UC_HOOK_HARDWARE_WRITE) 348 | { 349 | 350 | if (hook->to_delete) { 351 | continue; 352 | } 353 | 354 | if (HOOK_BOUND_CHECK(hook, (uint64_t)addrhw)) { 355 | ((uc_cb_hookcode_t)hook->callback)( 356 | uc, addrhw, 1, hook->user_data); 357 | } 358 | } 359 | 360 | HOOK_FOREACH(uc, hook, UC_HOOK_HARDWARE_READWRITE) 361 | { 362 | 363 | if (hook->to_delete) { 364 | continue; 365 | } 366 | 367 | if (HOOK_BOUND_CHECK(hook, (uint64_t)addrhw)) { 368 | ((uc_cb_hookcode_t)hook->callback)( 369 | uc, addrhw, 1, hook->user_data); 370 | } 371 | } 372 | 373 | if (uc->stop_request == true) { 374 | 375 | printf("stop_request =%016llx\r\n", ripval); 376 | finish = true; 377 | } 378 | break; 379 | } 380 | 381 | } else { 382 | /* Another exception or debug event. Report it to GDB. 383 | */ 384 | uintptr_t ripvalsave = ripval; 385 | if (cpu->uc->size_recur_mem == 0) { 386 | whpx_dump_stack(uc); 387 | } 388 | cpu->exception_index = EXCP_DEBUG; 389 | if (ripvalold == ripvalsave) { 390 | printf("WHvX64ExceptionFatalError=%016llx\r\n", ripval); 391 | if (cpu->uc->size_recur_mem > 0) { 392 | whpx_dump_stack(uc); 393 | } 394 | cpu->uc->size_recur_mem++; 395 | } 396 | DumpExceptionType(WHvX64ExceptionTypeDivideErrorFault); 397 | DumpExceptionType(WHvX64ExceptionTypeDebugTrapOrFault); 398 | DumpExceptionType(WHvX64ExceptionTypeBreakpointTrap); 399 | DumpExceptionType(WHvX64ExceptionTypeOverflowTrap); 400 | DumpExceptionType(WHvX64ExceptionTypeBoundRangeFault); 401 | DumpExceptionType(WHvX64ExceptionTypeInvalidOpcodeFault); 402 | DumpExceptionType( 403 | WHvX64ExceptionTypeDeviceNotAvailableFault); 404 | DumpExceptionType(WHvX64ExceptionTypeDoubleFaultAbort); 405 | DumpExceptionType( 406 | WHvX64ExceptionTypeInvalidTaskStateSegmentFault); 407 | DumpExceptionType( 408 | WHvX64ExceptionTypeSegmentNotPresentFault); 409 | DumpExceptionType(WHvX64ExceptionTypeStackFault); 410 | DumpExceptionType( 411 | WHvX64ExceptionTypeGeneralProtectionFault); 412 | DumpExceptionType(WHvX64ExceptionTypePageFault); 413 | DumpExceptionType( 414 | WHvX64ExceptionTypeFloatingPointErrorFault); 415 | DumpExceptionType(WHvX64ExceptionTypeAlignmentCheckFault); 416 | DumpExceptionType(WHvX64ExceptionTypeMachineCheckAbort); 417 | DumpExceptionType( 418 | WHvX64ExceptionTypeSimdFloatingPointFault); 419 | 420 | if (vcpu->exit_ctx.VpException.ExceptionType == 421 | WHvX64ExceptionTypePageFault && 422 | vcpu->exit_ctx.VpException.ExceptionParameter > 423 | 0x100000) { 424 | // len=0x10000 425 | if (uc_mem_read_hook( 426 | cpu->uc, 427 | vcpu->exit_ctx.VpException.ExceptionParameter, 428 | 0x1000) == UC_ERR_OK) { 429 | // cpu->uc->size_recur_mem--; 430 | printf( 431 | "fix:MemoryAccess from " 432 | "buf=%016llx,retry\r\n", 433 | vcpu->exit_ctx.VpException.ExceptionParameter); 434 | break; 435 | } else { 436 | printf( 437 | "WHvX64ExceptionFatalError " 438 | "uc_mem_read_hook unreachable memory " 439 | "address=%016llx\r\n", 440 | vcpu->exit_ctx.VpException.ExceptionParameter); 441 | exit(0); 442 | } 443 | } else { 444 | printf("WHvX64ExceptionFatalError unreachable memory " 445 | "address=%016llx\r\n", 446 | vcpu->exit_ctx.VpException.ExceptionParameter); 447 | exit(0); 448 | } 449 | } 450 | 451 | HOOK_FOREACH(uc, hook, UC_HOOK_CODE) 452 | { 453 | 454 | if (hook->to_delete) { 455 | continue; 456 | } 457 | 458 | if (HOOK_BOUND_CHECK(hook, (uint64_t)ripval)) { 459 | ((uc_cb_hookcode_t)hook->callback)(uc, ripval, 1, 460 | hook->user_data); 461 | } 462 | } 463 | 464 | if (uc->stop_request == true) { 465 | 466 | printf("stop_request =%016llx\r\n", ripval); 467 | finish = true; 468 | } 469 | break; 470 | } 471 | case WHvRunVpExitReasonNone: 472 | case WHvRunVpExitReasonUnrecoverableException: 473 | case WHvRunVpExitReasonInvalidVpRegisterValue: 474 | case WHvRunVpExitReasonUnsupportedFeature: 475 | default: { 476 | DumpExitReason(WHvRunVpExitReasonUnrecoverableException); 477 | cpu->uc->size_recur_mem++; 478 | finish = true; 479 | break; 480 | } 481 | } 482 | if (finish == true) { 483 | break; 484 | } 485 | 486 | } else if (cpu->stop || cpu->stopped) { 487 | // printf(">>> got stopped!!!\n"); 488 | break; 489 | } 490 | } 491 | uc->exit_request = 0; 492 | uc->cpu->exit_request = 0; 493 | // uc->cpu->icount_decr_ptr->u16.high = 0; 494 | uc->cpu->tcg_exit_req = 0; 495 | 496 | return finish; 497 | } 498 | 499 | void cpu_resume(CPUState *cpu) 500 | { 501 | cpu->stop = false; 502 | cpu->stopped = false; 503 | } 504 | 505 | static void qemu_tcg_init_vcpu(CPUState *cpu) 506 | { 507 | /* 508 | * Initialize TCG regions--once. Now is a good time, because: 509 | * (1) TCG's init context, prologue and target globals have been set up. 510 | * (2) qemu_tcg_mttcg_enabled() works now (TCG init code runs before the 511 | * -accel flag is processed, so the check doesn't work then). 512 | */ 513 | // tcg_region_init(cpu->uc->tcg_ctx); 514 | 515 | cpu->created = true; 516 | } 517 | 518 | void qemu_init_vcpu(CPUState *cpu) 519 | { 520 | cpu->nr_cores = 1; 521 | cpu->nr_threads = 1; 522 | cpu->stopped = true; 523 | 524 | qemu_tcg_init_vcpu(cpu); 525 | 526 | return; 527 | } 528 | 529 | void cpu_stop_current(struct uc_struct *uc) 530 | { 531 | if (uc->cpu) { 532 | uc->cpu->stop = false; 533 | uc->cpu->stopped = true; 534 | cpu_exit(uc->cpu); 535 | } 536 | } 537 | 538 | static inline gboolean uc_exit_invalidate_iter(gpointer key, gpointer val, 539 | gpointer data) 540 | { 541 | uint64_t exit = *((uint64_t *)key); 542 | uc_engine *uc = (uc_engine *)data; 543 | 544 | if (exit != 0) { 545 | // Unicorn: Why addr - 1? 546 | // 547 | // 0: INC ecx 548 | // 1: DEC edx <--- We put exit here, then the range of TB is [0, 1) 549 | // 550 | // While tb_invalidate_phys_range invalides [start, end) 551 | // 552 | // This function is designed to used with g_tree_foreach 553 | if (uc->uc_invalidate_tb) { 554 | uc->uc_invalidate_tb(uc, exit - 1, 1); 555 | } 556 | } 557 | 558 | return false; 559 | } 560 | 561 | void resume_all_vcpus(struct uc_struct *uc) 562 | { 563 | CPUState *cpu = uc->cpu; 564 | cpu->halted = 0; 565 | cpu->exit_request = 0; 566 | cpu->exception_index = -1; 567 | whpx_first_vcpu_starting(cpu); 568 | cpu_resume(cpu); 569 | /* static void qemu_tcg_cpu_loop(struct uc_struct *uc) */ 570 | cpu->created = true; 571 | while (true) { 572 | if (whpx_cpu_exec(uc)) { 573 | break; 574 | } 575 | } 576 | 577 | // clear the cache of the exits address, since the generated code 578 | // at that address is to exit emulation, but not for the instruction there. 579 | // if we dont do this, next time we cannot emulate at that address 580 | if (uc->use_exits) { 581 | g_tree_foreach(uc->ctl_exits, uc_exit_invalidate_iter, (void *)uc); 582 | } else { 583 | uc_exit_invalidate_iter((gpointer)&uc->exits[uc->nested_level - 1], 584 | NULL, (gpointer)uc); 585 | } 586 | 587 | cpu->created = false; 588 | } 589 | 590 | void vm_start(struct uc_struct *uc) 591 | { 592 | resume_all_vcpus(uc); 593 | } 594 | -------------------------------------------------------------------------------- /unicorn-whpx-core/softmmuwhpx/exec-vary.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Variable page size handling 3 | * 4 | * Copyright (c) 2003 Fabrice Bellard 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, see . 18 | */ 19 | 20 | #include "qemu/osdep.h" 21 | #include "qemu-common.h" 22 | 23 | #define IN_EXEC_VARY 1 24 | 25 | #include "exec/exec-all.h" 26 | 27 | #include 28 | 29 | bool set_preferred_target_page_bits(struct uc_struct *uc, int bits) 30 | { 31 | /* 32 | * The target page size is the lowest common denominator for all 33 | * the CPUs in the system, so we can only make it smaller, never 34 | * larger. And we can't make it smaller once we've committed to 35 | * a particular size. 36 | */ 37 | #ifdef TARGET_PAGE_BITS_VARY 38 | //assert(bits >= TARGET_PAGE_BITS_MIN); 39 | if (uc->init_target_page == NULL) { 40 | uc->init_target_page = calloc(1, sizeof(TargetPageBits)); 41 | } else { 42 | return false; 43 | } 44 | 45 | if (bits < TARGET_PAGE_BITS_MIN) { 46 | return false; 47 | } 48 | 49 | if (uc->init_target_page->bits == 0 || uc->init_target_page->bits > bits) { 50 | if (uc->init_target_page->decided) { 51 | return false; 52 | } 53 | uc->init_target_page->bits = bits; 54 | } 55 | #endif 56 | return true; 57 | } 58 | 59 | void finalize_target_page_bits(struct uc_struct *uc) 60 | { 61 | #ifdef TARGET_PAGE_BITS_VARY 62 | if (uc->init_target_page == NULL) { 63 | uc->init_target_page = calloc(1, sizeof(TargetPageBits)); 64 | } else { 65 | return; 66 | } 67 | 68 | if (uc->target_bits != 0) { 69 | uc->init_target_page->bits = uc->target_bits; 70 | } 71 | 72 | if (uc->init_target_page->bits == 0) { 73 | uc->init_target_page->bits = TARGET_PAGE_BITS_MIN; 74 | } 75 | uc->init_target_page->mask = ((target_ulong)-1) << uc->init_target_page->bits; 76 | uc->init_target_page->decided = true; 77 | 78 | /* 79 | * For the benefit of an -flto build, prevent the compiler from 80 | * hoisting a read from target_page before we finish initializing. 81 | */ 82 | barrier(); 83 | #endif 84 | } 85 | -------------------------------------------------------------------------------- /unicorn-whpx-core/softmmuwhpx/ioport.c: -------------------------------------------------------------------------------- 1 | /* 2 | * QEMU System Emulator 3 | * 4 | * Copyright (c) 2003-2008 Fabrice Bellard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | /* 25 | * splitted out ioport related stuffs from vl.c. 26 | */ 27 | 28 | #include "qemu/osdep.h" 29 | #include "cpu.h" 30 | #include "exec/memory.h" 31 | #include "uc_priv.h" 32 | 33 | 34 | void cpu_outb(struct uc_struct *uc, uint32_t addr, uint8_t val) 35 | { 36 | // address_space_write(&uc->address_space_io, addr, MEMTXATTRS_UNSPECIFIED, 37 | // &val, 1); 38 | 39 | //LOG_IOPORT("outb: %04"FMT_pioaddr" %02"PRIx8"\n", addr, val); 40 | // Unicorn: call registered OUT callbacks 41 | struct hook *hook; 42 | HOOK_FOREACH_VAR_DECLARE; 43 | HOOK_FOREACH(uc, hook, UC_HOOK_INSN) { 44 | if (hook->to_delete) 45 | continue; 46 | if (hook->insn == UC_X86_INS_OUT) 47 | ((uc_cb_insn_out_t)hook->callback)(uc, addr, 1, val, hook->user_data); 48 | } 49 | } 50 | 51 | void cpu_outw(struct uc_struct *uc, uint32_t addr, uint16_t val) 52 | { 53 | // uint8_t buf[2]; 54 | 55 | // stw_p(buf, val); 56 | // address_space_write(&uc->address_space_io, addr, MEMTXATTRS_UNSPECIFIED, 57 | // buf, 2); 58 | 59 | //LOG_IOPORT("outw: %04"FMT_pioaddr" %04"PRIx16"\n", addr, val); 60 | // Unicorn: call registered OUT callbacks 61 | struct hook *hook; 62 | HOOK_FOREACH_VAR_DECLARE; 63 | HOOK_FOREACH(uc, hook, UC_HOOK_INSN) { 64 | if (hook->to_delete) 65 | continue; 66 | if (hook->insn == UC_X86_INS_OUT) 67 | ((uc_cb_insn_out_t)hook->callback)(uc, addr, 2, val, hook->user_data); 68 | } 69 | } 70 | 71 | void cpu_outl(struct uc_struct *uc, uint32_t addr, uint32_t val) 72 | { 73 | // uint8_t buf[4]; 74 | 75 | // stl_p(buf, val); 76 | // address_space_write(&uc->address_space_io, addr, MEMTXATTRS_UNSPECIFIED, 77 | // buf, 4); 78 | 79 | //LOG_IOPORT("outl: %04"FMT_pioaddr" %08"PRIx32"\n", addr, val); 80 | // Unicorn: call registered OUT callbacks 81 | struct hook *hook; 82 | HOOK_FOREACH_VAR_DECLARE; 83 | HOOK_FOREACH(uc, hook, UC_HOOK_INSN) { 84 | if (hook->to_delete) 85 | continue; 86 | if (hook->insn == UC_X86_INS_OUT) 87 | ((uc_cb_insn_out_t)hook->callback)(uc, addr, 4, val, hook->user_data); 88 | } 89 | } 90 | 91 | uint8_t cpu_inb(struct uc_struct *uc, uint32_t addr) 92 | { 93 | // uint8_t val; 94 | 95 | // address_space_read(&uc->address_space_io, addr, MEMTXATTRS_UNSPECIFIED, 96 | // &val, 1); 97 | 98 | //LOG_IOPORT("inb : %04"FMT_pioaddr" %02"PRIx8"\n", addr, val); 99 | // Unicorn: call registered IN callbacks 100 | struct hook *hook; 101 | HOOK_FOREACH_VAR_DECLARE; 102 | HOOK_FOREACH(uc, hook, UC_HOOK_INSN) { 103 | if (hook->to_delete) 104 | continue; 105 | if (hook->insn == UC_X86_INS_IN) 106 | return ((uc_cb_insn_in_t)hook->callback)(uc, addr, 1, hook->user_data); 107 | } 108 | 109 | return 0; 110 | } 111 | 112 | uint16_t cpu_inw(struct uc_struct *uc, uint32_t addr) 113 | { 114 | // uint8_t buf[2]; 115 | // uint16_t val; 116 | 117 | // address_space_read(&uc->address_space_io, addr, MEMTXATTRS_UNSPECIFIED, buf, 2); 118 | // val = lduw_p(buf); 119 | 120 | //LOG_IOPORT("inw : %04"FMT_pioaddr" %04"PRIx16"\n", addr, val); 121 | // Unicorn: call registered IN callbacks 122 | struct hook *hook; 123 | HOOK_FOREACH_VAR_DECLARE; 124 | HOOK_FOREACH(uc, hook, UC_HOOK_INSN) { 125 | if (hook->to_delete) 126 | continue; 127 | if (hook->insn == UC_X86_INS_IN) 128 | return ((uc_cb_insn_in_t)hook->callback)(uc, addr, 2, hook->user_data); 129 | } 130 | 131 | return 0; 132 | } 133 | 134 | uint32_t cpu_inl(struct uc_struct *uc, uint32_t addr) 135 | { 136 | // uint8_t buf[4]; 137 | // uint32_t val; 138 | 139 | // printf("inl_addr=%x\n", addr); 140 | 141 | // address_space_read(&uc->address_space_io, addr, MEMTXATTRS_UNSPECIFIED, buf, 4); 142 | // val = ldl_p(buf); 143 | 144 | //LOG_IOPORT("inl : %04"FMT_pioaddr" %08"PRIx32"\n", addr, val); 145 | // Unicorn: call registered IN callbacks 146 | struct hook *hook; 147 | HOOK_FOREACH_VAR_DECLARE; 148 | HOOK_FOREACH(uc, hook, UC_HOOK_INSN) { 149 | if (hook->to_delete) 150 | continue; 151 | if (hook->insn == UC_X86_INS_IN) 152 | return ((uc_cb_insn_in_t)hook->callback)(uc, addr, 4, hook->user_data); 153 | } 154 | 155 | return 0; 156 | } 157 | -------------------------------------------------------------------------------- /unicorn-whpx-core/softmmuwhpx/memory_mapping.c: -------------------------------------------------------------------------------- 1 | /* 2 | * QEMU memory mapping 3 | * 4 | * Copyright Fujitsu, Corp. 2011, 2012 5 | * 6 | * Authors: 7 | * Wen Congyang 8 | * 9 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 10 | * See the COPYING file in the top-level directory. 11 | * 12 | */ 13 | 14 | #include "qemu/osdep.h" 15 | 16 | #include "cpu.h" 17 | #include "sysemu/memory_mapping.h" 18 | #include "exec/memory.h" 19 | 20 | //#define DEBUG_GUEST_PHYS_REGION_ADD 21 | 22 | static void memory_mapping_list_add_mapping_sorted(MemoryMappingList *list, 23 | MemoryMapping *mapping) 24 | { 25 | MemoryMapping *p; 26 | 27 | QTAILQ_FOREACH(p, &list->head, next) { 28 | if (p->phys_addr >= mapping->phys_addr) { 29 | QTAILQ_INSERT_BEFORE(p, mapping, next); 30 | return; 31 | } 32 | } 33 | QTAILQ_INSERT_TAIL(&list->head, mapping, next); 34 | } 35 | 36 | static void create_new_memory_mapping(MemoryMappingList *list, 37 | hwaddr phys_addr, 38 | hwaddr virt_addr, 39 | ram_addr_t length) 40 | { 41 | MemoryMapping *memory_mapping; 42 | 43 | memory_mapping = g_malloc(sizeof(MemoryMapping)); 44 | memory_mapping->phys_addr = phys_addr; 45 | memory_mapping->virt_addr = virt_addr; 46 | memory_mapping->length = length; 47 | list->last_mapping = memory_mapping; 48 | list->num++; 49 | memory_mapping_list_add_mapping_sorted(list, memory_mapping); 50 | } 51 | 52 | static inline bool mapping_contiguous(MemoryMapping *map, 53 | hwaddr phys_addr, 54 | hwaddr virt_addr) 55 | { 56 | return phys_addr == map->phys_addr + map->length && 57 | virt_addr == map->virt_addr + map->length; 58 | } 59 | 60 | /* 61 | * [map->phys_addr, map->phys_addr + map->length) and 62 | * [phys_addr, phys_addr + length) have intersection? 63 | */ 64 | static inline bool mapping_have_same_region(MemoryMapping *map, 65 | hwaddr phys_addr, 66 | ram_addr_t length) 67 | { 68 | return !(phys_addr + length < map->phys_addr || 69 | phys_addr >= map->phys_addr + map->length); 70 | } 71 | 72 | /* 73 | * [map->phys_addr, map->phys_addr + map->length) and 74 | * [phys_addr, phys_addr + length) have intersection. The virtual address in the 75 | * intersection are the same? 76 | */ 77 | static inline bool mapping_conflict(MemoryMapping *map, 78 | hwaddr phys_addr, 79 | hwaddr virt_addr) 80 | { 81 | return virt_addr - map->virt_addr != phys_addr - map->phys_addr; 82 | } 83 | 84 | /* 85 | * [map->virt_addr, map->virt_addr + map->length) and 86 | * [virt_addr, virt_addr + length) have intersection. And the physical address 87 | * in the intersection are the same. 88 | */ 89 | static inline void mapping_merge(MemoryMapping *map, 90 | hwaddr virt_addr, 91 | ram_addr_t length) 92 | { 93 | if (virt_addr < map->virt_addr) { 94 | map->length += map->virt_addr - virt_addr; 95 | map->virt_addr = virt_addr; 96 | } 97 | 98 | if ((virt_addr + length) > 99 | (map->virt_addr + map->length)) { 100 | map->length = virt_addr + length - map->virt_addr; 101 | } 102 | } 103 | 104 | void memory_mapping_list_add_merge_sorted(MemoryMappingList *list, 105 | hwaddr phys_addr, 106 | hwaddr virt_addr, 107 | ram_addr_t length) 108 | { 109 | MemoryMapping *memory_mapping, *last_mapping; 110 | 111 | if (QTAILQ_EMPTY(&list->head)) { 112 | create_new_memory_mapping(list, phys_addr, virt_addr, length); 113 | return; 114 | } 115 | 116 | last_mapping = list->last_mapping; 117 | if (last_mapping) { 118 | if (mapping_contiguous(last_mapping, phys_addr, virt_addr)) { 119 | last_mapping->length += length; 120 | return; 121 | } 122 | } 123 | 124 | QTAILQ_FOREACH(memory_mapping, &list->head, next) { 125 | if (mapping_contiguous(memory_mapping, phys_addr, virt_addr)) { 126 | memory_mapping->length += length; 127 | list->last_mapping = memory_mapping; 128 | return; 129 | } 130 | 131 | if (phys_addr + length < memory_mapping->phys_addr) { 132 | /* create a new region before memory_mapping */ 133 | break; 134 | } 135 | 136 | if (mapping_have_same_region(memory_mapping, phys_addr, length)) { 137 | if (mapping_conflict(memory_mapping, phys_addr, virt_addr)) { 138 | continue; 139 | } 140 | 141 | /* merge this region into memory_mapping */ 142 | mapping_merge(memory_mapping, virt_addr, length); 143 | list->last_mapping = memory_mapping; 144 | return; 145 | } 146 | } 147 | 148 | /* this region can not be merged into any existed memory mapping. */ 149 | create_new_memory_mapping(list, phys_addr, virt_addr, length); 150 | } 151 | -------------------------------------------------------------------------------- /unicorn-whpx-core/softmmuwhpx/vl.c: -------------------------------------------------------------------------------- 1 | /* 2 | * QEMU System Emulator 3 | * 4 | * Copyright (c) 2003-2008 Fabrice Bellard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | #include "sysemu/sysemu.h" 26 | #include "sysemu/cpus.h" 27 | #include "uc_priv.h" 28 | 29 | void init_real_host_page_size(struct uc_struct *uc); 30 | void init_cache_info(struct uc_struct *uc); 31 | 32 | 33 | DEFAULT_VISIBILITY 34 | int machine_initialize(struct uc_struct *uc) 35 | { 36 | init_get_clock(); 37 | 38 | /* Init uc->qemu_real_host_page_size. */ 39 | init_real_host_page_size(uc); 40 | 41 | /* Init uc->qemu_icache_linesize. */ 42 | init_cache_info(uc); 43 | 44 | // Initialize arch specific. 45 | uc->init_arch(uc); 46 | 47 | /* Init memory. */ 48 | uc->cpu_exec_init_all(uc); 49 | 50 | uc->target_page(uc); 51 | 52 | /* Init tcg. use DEFAULT_CODE_GEN_BUFFER_SIZE. */ 53 | uc->tcg_exec_init(uc, 0); 54 | 55 | /* Init cpu. use default cpu_model. */ 56 | return uc->cpus_init(uc, NULL); 57 | } 58 | 59 | void qemu_system_reset_request(struct uc_struct* uc) 60 | { 61 | cpu_stop(uc); 62 | } 63 | 64 | void qemu_system_shutdown_request(struct uc_struct *uc) 65 | { 66 | /* TODO: shutdown(exit program) immediately? */ 67 | cpu_stop(uc); 68 | } 69 | -------------------------------------------------------------------------------- /unicorn-whpx-core/target/DoubleLinkedList.cpp: -------------------------------------------------------------------------------- 1 | #include "DoubleLinkedList.hpp" 2 | 3 | #include 4 | #include 5 | 6 | // Initialize a Doubly Linked List header 7 | // 8 | void InitializeDListHeader( PDLIST_HEADER Header ) { 9 | if ( Header == NULL ) 10 | return; 11 | 12 | Header->Head = NULL; 13 | Header->Tail = NULL; 14 | Header->Count = 0; 15 | } 16 | 17 | // Flush the content of a double linked list 18 | // 19 | void FlushDList( PDLIST_HEADER Header ) { 20 | if ( Header == NULL ) 21 | return; 22 | 23 | PDLIST_ENTRY head = Header->Head; 24 | if ( head == NULL ) 25 | return; 26 | 27 | PDLIST_ENTRY current = head; 28 | while ( current != NULL ) { 29 | PDLIST_ENTRY next = current->Next; 30 | free( current ); 31 | current = next; 32 | } 33 | 34 | // Reset header 35 | // 36 | InitializeDListHeader( Header ); 37 | } 38 | 39 | // Get the head (first element) of a double linked list 40 | // 41 | PDLIST_ENTRY GetDListHead( PDLIST_HEADER Header ) { 42 | if ( Header == NULL ) 43 | return NULL; 44 | 45 | return Header->Head; 46 | } 47 | 48 | // Get the tail (last element) of a double linked list 49 | // 50 | PDLIST_ENTRY GetDListTail( PDLIST_HEADER Header ) { 51 | if ( Header == NULL ) 52 | return NULL; 53 | 54 | return Header->Tail; 55 | } 56 | 57 | // Get the number of elements 58 | // 59 | size_t GetDListCount( PDLIST_HEADER Header ) { 60 | if ( Header == NULL ) 61 | return 0; 62 | 63 | return Header->Count; 64 | } 65 | 66 | // Push an element in front (start) of the list 67 | // 68 | PDLIST_ENTRY PushFrontDListEntry( PDLIST_HEADER Header, PDLIST_ENTRY Entry ) { 69 | if ( Header == NULL ) 70 | return NULL; 71 | 72 | PDLIST_ENTRY head = GetDListHead( Header ); 73 | if ( head != NULL ) { 74 | head->Prev = Entry; 75 | Entry->Next = head; 76 | } 77 | 78 | Entry->Prev = NULL; 79 | Header->Head = Entry; 80 | 81 | if ( head == NULL ) 82 | Header->Tail = Entry; 83 | 84 | Header->Count++; 85 | 86 | return head; 87 | } 88 | 89 | // Push an element in back (end) of the list 90 | // 91 | PDLIST_ENTRY PushBackDListEntry( PDLIST_HEADER Header, PDLIST_ENTRY Entry ) { 92 | if ( Header == NULL ) 93 | return NULL; 94 | 95 | PDLIST_ENTRY tail = GetDListTail( Header ); 96 | if ( tail == NULL ) 97 | return PushFrontDListEntry( Header, Entry ); 98 | 99 | Entry->Next = NULL; 100 | Entry->Prev = tail; 101 | tail->Next = Entry; 102 | 103 | Header->Tail = Entry; 104 | 105 | Header->Count++; 106 | 107 | return tail; 108 | } 109 | 110 | // Insert an element before the specified item 111 | // 112 | PDLIST_ENTRY InsertBeforeDListEntry( PDLIST_HEADER Header, PDLIST_ENTRY Item, PDLIST_ENTRY Entry ) { 113 | if ( Header == NULL ) 114 | return NULL; 115 | 116 | if ( Item == NULL ) 117 | return NULL; 118 | 119 | if ( Item == Header->Head ) 120 | return PushFrontDListEntry( Header, Entry ); 121 | 122 | PDLIST_ENTRY prev = Item->Prev; 123 | 124 | // prev <-> Entry (new) <-> Item 125 | // 126 | Entry->Prev = prev; 127 | Entry->Next = Item; 128 | 129 | Item->Prev = Entry; 130 | prev->Next = Entry; 131 | 132 | Header->Count++; 133 | 134 | return Item; 135 | } 136 | 137 | // Insert an element after the specified item 138 | // 139 | PDLIST_ENTRY InsertAfterDListEntry( PDLIST_HEADER Header, PDLIST_ENTRY Item, PDLIST_ENTRY Entry ) { 140 | if ( Header == NULL ) 141 | return NULL; 142 | 143 | if ( Item == NULL ) 144 | return NULL; 145 | 146 | if ( Item == Header->Tail ) 147 | return PushBackDListEntry( Header, Entry ); 148 | 149 | PDLIST_ENTRY next = Item->Next; 150 | 151 | // Item <-> Entry (new) <-> next 152 | // 153 | Entry->Prev = Item; 154 | Entry->Next = next; 155 | 156 | Item->Next = Entry; 157 | next->Prev = Entry; 158 | 159 | Header->Count++; 160 | 161 | return Item; 162 | } 163 | 164 | // Remove an element from the head (start) of the list 165 | // 166 | PDLIST_ENTRY PopFrontDListEntry( PDLIST_HEADER Header ) { 167 | if ( Header == NULL ) 168 | return NULL; 169 | 170 | PDLIST_ENTRY head = Header->Head; 171 | if ( head == NULL ) 172 | return NULL; 173 | 174 | PDLIST_ENTRY newHead = head->Next; 175 | newHead->Prev = NULL; 176 | Header->Head = newHead; 177 | 178 | head->Next = NULL; 179 | head->Prev = NULL; 180 | 181 | Header->Count--; 182 | 183 | return head; 184 | } 185 | 186 | // Remove an element from the tail (end) of the list 187 | // 188 | PDLIST_ENTRY PopBackDListEntry( PDLIST_HEADER Header ) { 189 | if ( Header == NULL ) 190 | return NULL; 191 | 192 | PDLIST_ENTRY tail = Header->Tail; 193 | if ( tail == NULL ) 194 | return NULL; 195 | 196 | PDLIST_ENTRY newTail = tail->Prev; 197 | newTail->Next = NULL; 198 | Header->Tail = newTail; 199 | 200 | tail->Next = NULL; 201 | tail->Prev = NULL; 202 | 203 | Header->Count--; 204 | 205 | return tail; 206 | } 207 | 208 | // Remove an element before the specified item 209 | // 210 | PDLIST_ENTRY RemoveBeforeDListEntry( PDLIST_HEADER Header, PDLIST_ENTRY Item ) { 211 | if ( Header == NULL ) 212 | return NULL; 213 | 214 | if ( Item == NULL ) 215 | return NULL; 216 | 217 | if ( Header->Head == Item ) 218 | return NULL; 219 | 220 | PDLIST_ENTRY node = Item->Prev; 221 | if ( node == NULL ) 222 | return NULL; 223 | 224 | if ( node == Header->Head ) 225 | return PopFrontDListEntry( Header ); 226 | 227 | PDLIST_ENTRY before = node->Prev; 228 | 229 | Item->Prev = before; 230 | if ( before != NULL ) 231 | before->Next = Item; 232 | 233 | node->Prev = NULL; 234 | node->Next = NULL; 235 | 236 | Header->Count--; 237 | 238 | return node; 239 | } 240 | 241 | // Remove an element after the specified item 242 | // 243 | PDLIST_ENTRY RemoveAfterDListEntry( PDLIST_HEADER Header, PDLIST_ENTRY Item ) { 244 | if ( Header == NULL ) 245 | return NULL; 246 | 247 | if ( Item == NULL ) 248 | return NULL; 249 | 250 | if ( Header->Tail == Item ) 251 | return PopBackDListEntry( Header ); 252 | 253 | PDLIST_ENTRY node = Item->Next; 254 | if ( node == NULL ) 255 | return NULL; 256 | 257 | if ( node == Header->Tail ) 258 | return PopBackDListEntry( Header ); 259 | 260 | PDLIST_ENTRY after = node->Next; 261 | 262 | Item->Next = after; 263 | if ( after != NULL ) 264 | after->Prev = Item; 265 | 266 | node->Prev = NULL; 267 | node->Next = NULL; 268 | 269 | Header->Count--; 270 | 271 | return node; 272 | } 273 | 274 | 275 | // Find the entry matching the predicate 276 | // 277 | PDLIST_ENTRY FindDListEntry( PDLIST_HEADER Header, PDLIST_FINDPREDICATE Predicate ) { 278 | if ( Header == NULL ) 279 | return NULL; 280 | 281 | if ( Predicate == NULL ) 282 | return NULL; 283 | 284 | PDLIST_ENTRY head = GetDListHead( Header ); 285 | if ( head == NULL ) 286 | return NULL; 287 | 288 | PDLIST_ENTRY found = NULL; 289 | for ( PDLIST_ENTRY current = head; current != NULL; current = current->Next ) { 290 | if ( Predicate( current ) ) { 291 | found = current; 292 | break; 293 | } 294 | } 295 | 296 | return found; 297 | } 298 | 299 | // Sort the list following the compartor policy 300 | // 301 | void SortDListBy( PDLIST_HEADER Header, PDLIST_COMPARATOR Comparator ) { 302 | if ( Header == NULL ) 303 | return; 304 | 305 | if ( Comparator == NULL ) 306 | return; 307 | 308 | if ( GetDListCount( Header ) == 0 ) 309 | return; 310 | 311 | size_t count = GetDListCount( Header ); 312 | size_t size = sizeof( PDLIST_ENTRY ) * ( count + 1 ); 313 | PDLIST_ENTRY* ptrs = ( PDLIST_ENTRY* ) malloc( size ); 314 | if ( ptrs == NULL ) 315 | return; 316 | 317 | int i = 0; 318 | for ( PDLIST_ENTRY current = GetDListHead( Header ); current != NULL; current = current->Next ) { 319 | ptrs[ i++ ] = current; 320 | } 321 | 322 | qsort( ptrs, count, sizeof( PDLIST_ENTRY ), ( int( * )( const void*, const void* ) )Comparator ); 323 | 324 | InitializeDListHeader( Header ); 325 | 326 | for ( int j = 0; j < count; j++ ) { 327 | PushBackDListEntry( Header, ptrs[ j ] ); 328 | } 329 | 330 | free( ptrs ); 331 | } -------------------------------------------------------------------------------- /unicorn-whpx-core/target/DoubleLinkedList.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DOUBLELINKEDLIST_HPP 2 | #define DOUBLELINKEDLIST_HPP 3 | 4 | struct _DLIST_ENTRY { 5 | struct _DLIST_ENTRY* Prev; 6 | struct _DLIST_ENTRY* Next; 7 | }; 8 | 9 | typedef struct _DLIST_ENTRY DLIST_ENTRY; 10 | typedef struct _DLIST_ENTRY* PDLIST_ENTRY; 11 | 12 | struct _DLIST_HEADER { 13 | struct _DLIST_ENTRY* Head; 14 | struct _DLIST_ENTRY* Tail; 15 | size_t Count; 16 | }; 17 | 18 | typedef struct _DLIST_HEADER DLIST_HEADER; 19 | typedef struct _DLIST_HEADER* PDLIST_HEADER; 20 | 21 | // Initialize a Doubly Linked List header 22 | // 23 | void InitializeDListHeader( PDLIST_HEADER Header ); 24 | 25 | // Flush the content of a double linked list 26 | // 27 | void FlushDList( PDLIST_HEADER Header ); 28 | 29 | // Get the head (first element) of a double linked list 30 | // 31 | PDLIST_ENTRY GetDListHead( PDLIST_HEADER Header ); 32 | 33 | // Get the tail (last element) of a double linked list 34 | // 35 | PDLIST_ENTRY GetDListTail( PDLIST_HEADER Header ); 36 | 37 | // Get the number of elements 38 | // 39 | size_t GetDListCount( PDLIST_HEADER Header ); 40 | 41 | // Push an element in front (start) of the list 42 | // 43 | PDLIST_ENTRY PushFrontDListEntry( PDLIST_HEADER Header, PDLIST_ENTRY Entry ); 44 | 45 | // Push an element in back (end) of the list 46 | // 47 | PDLIST_ENTRY PushBackDListEntry( PDLIST_HEADER Header, PDLIST_ENTRY Entry ); 48 | 49 | // Insert an element before the specified item 50 | // 51 | PDLIST_ENTRY InsertBeforeDListEntry( PDLIST_HEADER Header, PDLIST_ENTRY Item, PDLIST_ENTRY Entry ); 52 | 53 | // Insert an element after the specified item 54 | // 55 | PDLIST_ENTRY InsertAfterDListEntry( PDLIST_HEADER Header, PDLIST_ENTRY Item, PDLIST_ENTRY Entry ); 56 | 57 | // Remove an element from the head (start) of the list 58 | // 59 | PDLIST_ENTRY PopFrontDListEntry( PDLIST_HEADER Header ); 60 | 61 | // Remove an element from the tail (end) of the list 62 | // 63 | PDLIST_ENTRY PopBackDListEntry( PDLIST_HEADER Header ); 64 | 65 | // Remove an element before the specified item 66 | // 67 | PDLIST_ENTRY RemoveBeforeDListEntry( PDLIST_HEADER Header, PDLIST_ENTRY Item ); 68 | 69 | // Remove an element after the specified item 70 | // 71 | PDLIST_ENTRY RemoveAfterDListEntry( PDLIST_HEADER Header, PDLIST_ENTRY Item ); 72 | 73 | // Signature of a "find" predicate 74 | // 75 | typedef bool ( *PDLIST_FINDPREDICATE )( const PDLIST_ENTRY Entry ); 76 | 77 | // Find the entry matching the predicate 78 | // 79 | PDLIST_ENTRY FindDListEntry( PDLIST_HEADER Header, PDLIST_FINDPREDICATE Predicate ); 80 | 81 | // Signature of the list entry comparator 82 | // 83 | typedef int ( *PDLIST_COMPARATOR )( const PDLIST_ENTRY* a, const PDLIST_ENTRY* b ); 84 | 85 | // Sort the list following the compartor policy 86 | // 87 | void SortDListBy( PDLIST_HEADER Header, PDLIST_COMPARATOR Comparator ); 88 | 89 | #endif // !DOUBLELINKEDLIST_HPP -------------------------------------------------------------------------------- /unicorn-whpx-core/target/WinHvAllocationTracker.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "DoubleLinkedList.hpp" 4 | #include "WinHvAllocationTracker.hpp" 5 | #include "WinHvMemory.hpp" 6 | 7 | /** 8 | * @brief Initialize allocation tracker structures 9 | * 10 | * @param Partition The VM partition 11 | * @return A result code 12 | */ 13 | HRESULT WhSeInitializeAllocationTracker(whpx_state *Partition) 14 | { 15 | if (Partition == nullptr) 16 | return HRESULT_FROM_WIN32(PEERDIST_ERROR_NOT_INITIALIZED); 17 | 18 | // Initialize address space allocations tracking 19 | // 20 | auto tracker = &(Partition->MemoryLayout.MemoryArena.AllocatedMemoryBlocks); 21 | 22 | InitializeDListHeader(tracker); 23 | 24 | return S_OK; 25 | } 26 | 27 | /** 28 | * @brief Free allocation tracker structures 29 | * 30 | * @param Partition The VM partition 31 | * @return A result code 32 | */ 33 | HRESULT WhSeFreeAllocationTracker(whpx_state *Partition) 34 | { 35 | if (Partition == nullptr) 36 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 37 | 38 | auto tracker = &(Partition->MemoryLayout.MemoryArena.AllocatedMemoryBlocks); 39 | 40 | auto first = 41 | reinterpret_cast(GetDListHead(tracker)); 42 | if (first == nullptr) 43 | return HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS); 44 | 45 | // Release all allocation on backing host memory 46 | // 47 | auto current = first; 48 | while (current != nullptr) { 49 | if (current->GuestPhysicalAddress != 0 && 50 | current->GuestVirtualAddress == 0) 51 | WhSeFreeGuestPhysicalMemory(Partition, current->HostVirtualAddress, 52 | current->GuestPhysicalAddress, 53 | current->Size); 54 | else if (current->GuestPhysicalAddress != 0 && 55 | current->GuestVirtualAddress != 0) 56 | WhSeFreeGuestVirtualMemory(Partition, current->HostVirtualAddress, 57 | current->GuestVirtualAddress, 58 | current->Size); 59 | else 60 | DebugBreak(); 61 | 62 | current = reinterpret_cast(current->Next); 63 | } 64 | 65 | FlushDList(tracker); 66 | 67 | return S_OK; 68 | } 69 | 70 | /** 71 | * @brief Find the first allocation node matching the predicate 72 | * 73 | * @param Partition The VM partition 74 | * @param Predicate The predicate to match 75 | * @param Node The node to be returned 76 | * @return A result code 77 | */ 78 | HRESULT WhSeFindAllocationNode(whpx_state *Partition, 79 | WHSE_ALLOCATION_TRACKER_PREDICATE Predicate, 80 | WHSE_ALLOCATION_NODE **Node) 81 | { 82 | if (Partition == nullptr) 83 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 84 | 85 | if (Node == nullptr) 86 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 87 | 88 | if (*Node != nullptr) 89 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 90 | 91 | auto tracker = &(Partition->MemoryLayout.MemoryArena.AllocatedMemoryBlocks); 92 | 93 | auto first = 94 | reinterpret_cast(GetDListHead(tracker)); 95 | if (first == nullptr) 96 | return HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS); 97 | 98 | // Iterate over the list 99 | // 100 | auto current = first; 101 | while (current != nullptr) { 102 | if (Predicate(current)) { 103 | *Node = current; 104 | break; 105 | } 106 | 107 | current = reinterpret_cast(current->Next); 108 | } 109 | 110 | return *Node != nullptr ? S_OK : HRESULT_FROM_WIN32(ERROR_NOT_FOUND); 111 | } 112 | 113 | /** 114 | * @brief Find the first allocation node matching the guest virtual address 115 | * 116 | * @param Partition The VM partition 117 | * @param GuestVa The guest virtual address to match 118 | * @param Node The node to be returned 119 | * @return A result code 120 | */ 121 | HRESULT WhSeFindAllocationNodeByGva(whpx_state *Partition, uintptr_t GuestVa, 122 | WHSE_ALLOCATION_NODE **Node) 123 | { 124 | if (Partition == nullptr) 125 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 126 | 127 | if (Node == nullptr) 128 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 129 | 130 | if (*Node != nullptr) 131 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 132 | 133 | auto tracker = &(Partition->MemoryLayout.MemoryArena.AllocatedMemoryBlocks); 134 | 135 | auto first = 136 | reinterpret_cast(GetDListHead(tracker)); 137 | 138 | // Iterate over the list 139 | // 140 | auto current = first; 141 | while (current != nullptr) { 142 | if (current->BlockType == MEMORY_BLOCK_TYPE::MemoryBlockVirtual && 143 | current->GuestVirtualAddress <= GuestVa && 144 | GuestVa < (current->GuestVirtualAddress + current->Size)) { 145 | *Node = current; 146 | break; 147 | } 148 | 149 | current = reinterpret_cast(current->Next); 150 | } 151 | 152 | return HRESULT_FROM_WIN32(*Node != nullptr ? S_OK : ERROR_NOT_FOUND); 153 | } 154 | HRESULT WhSeFindAllocationGpaByGva(whpx_state *Partition, uintptr_t GuestVa, 155 | uintptr_t *GuestPa) 156 | { 157 | WHSE_ALLOCATION_NODE *Node; 158 | HRESULT hresult = WhSeFindAllocationNodeByGva(Partition, GuestVa, &Node); 159 | 160 | if (SUCCEEDED(hresult)) { 161 | 162 | *GuestPa = Node->GuestPhysicalAddress; 163 | } else { 164 | *GuestPa = 0; 165 | } 166 | return hresult; 167 | } 168 | 169 | HRESULT WhSeFindAllocationGvaByGpa(whpx_state *Partition, uintptr_t GuestPa, 170 | uintptr_t *GuestVa) 171 | { 172 | WHSE_ALLOCATION_NODE *Node; 173 | HRESULT hresult = WhSeFindAllocationNodeByGpa(Partition, GuestPa, &Node); 174 | 175 | if (SUCCEEDED(hresult)) { 176 | 177 | *GuestVa = Node->GuestVirtualAddress; 178 | }else { 179 | *GuestVa = 0; 180 | } 181 | return hresult; 182 | } 183 | /** 184 | * @brief Find the first allocation node matching the guest physical address 185 | * 186 | * @param Partition The VM partition 187 | * @param GuestPa The guest physical address to match 188 | * @param Node The node to be returned 189 | * @return A result code 190 | */ 191 | HRESULT WhSeFindAllocationNodeByGpa(whpx_state *Partition, uintptr_t GuestPa, 192 | WHSE_ALLOCATION_NODE **Node) 193 | { 194 | if (Partition == nullptr) 195 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 196 | 197 | if (Node == nullptr) 198 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 199 | 200 | if (*Node != nullptr) 201 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 202 | 203 | auto tracker = &(Partition->MemoryLayout.MemoryArena.AllocatedMemoryBlocks); 204 | 205 | auto first = 206 | reinterpret_cast(GetDListHead(tracker)); 207 | 208 | // Iterate over the list 209 | // 210 | auto current = first; 211 | while (current != nullptr) { 212 | if (current->GuestPhysicalAddress <= GuestPa && 213 | GuestPa < (current->GuestPhysicalAddress + current->Size)) { 214 | *Node = current; 215 | break; 216 | } 217 | 218 | current = reinterpret_cast(current->Next); 219 | } 220 | 221 | return HRESULT_FROM_WIN32(*Node != nullptr ? S_OK : ERROR_NOT_FOUND); 222 | } 223 | 224 | /** 225 | * @brief Insert a tracking node 226 | * 227 | * @param Partition The VM partition 228 | * @param Node The node to insert 229 | * @return A result code 230 | */ 231 | HRESULT WhSeInsertAllocationTrackingNode(whpx_state *Partition, 232 | WHSE_ALLOCATION_NODE Node) 233 | { 234 | WHSE_ALLOCATION_NODE *node = reinterpret_cast( 235 | malloc(sizeof(decltype(*node)))); 236 | if (node == nullptr) 237 | return HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); 238 | 239 | memcpy_s(node, sizeof(decltype(*node)), &Node, sizeof(decltype(Node))); 240 | 241 | auto tracker = &(Partition->MemoryLayout.MemoryArena.AllocatedMemoryBlocks); 242 | ::PushBackDListEntry(tracker, node); 243 | 244 | return S_OK; 245 | } 246 | -------------------------------------------------------------------------------- /unicorn-whpx-core/target/WinHvAllocationTracker.hpp: -------------------------------------------------------------------------------- 1 | #ifndef WINHVMEMORYALLOCATIONTRACKER_HPP 2 | #define WINHVMEMORYALLOCATIONTRACKER_HPP 3 | 4 | #include 5 | #include "WinHvDefs.hpp" 6 | 7 | 8 | /** 9 | * @brief A structure to represent a guest memory allocation 10 | * 11 | * This structure a guest memory allocation. 12 | * In case of physical memory allocation, the field is set 13 | * to null and the field is set with the guest physical 14 | * memory address. In case of virtual memory allocation, the 15 | * field is set to the guest address space virtual address 16 | * and the field is set with the guest physical memory 17 | * address backing this virtual address. In either case the 18 | * is set to the host address space virtual address that is backing the 19 | * allocated guest physical address space. The field must be set to the 20 | * size that represent the allocated memory ranges (and must be non zero). 21 | */ 22 | typedef struct _WHSE_ALLOCATION_NODE : DLIST_ENTRY { 23 | // The type of block 24 | // A block type of MemoryBlockPhysical represent guest physical memory (a 25 | // Gpa backed by an Hva) A block type of MemoryBlockVirtual represent guest 26 | // virtual memory (a Gva backed by a Gpa backed by an Hva) 27 | // 28 | MEMORY_BLOCK_TYPE BlockType; 29 | 30 | // The Host Virtual Address (HVA) backing the Guest Physical Address (GPA) 31 | // 32 | uintptr_t HostVirtualAddress; 33 | 34 | // The Guest Physical Address (GPA) 35 | // 36 | uintptr_t GuestPhysicalAddress; 37 | 38 | // The Guest Virtual Address (GVA) 39 | // 40 | uintptr_t GuestVirtualAddress; 41 | 42 | // The size of the allocation 43 | // 44 | size_t Size; 45 | } WHSE_ALLOCATION_NODE, *PWHSE_ALLOCATION_NODE; 46 | 47 | // Initialize allocation tracker structures 48 | // 49 | //HRESULT WhSeInitializeAllocationTracker( whpx_state* Partition ); 50 | 51 | // Free allocation tracker structures 52 | // 53 | HRESULT WhSeFreeAllocationTracker( whpx_state* Partition ); 54 | 55 | typedef bool ( *WHSE_ALLOCATION_TRACKER_PREDICATE )( const WHSE_ALLOCATION_NODE* ); 56 | 57 | // Find the first allocation node matching the predicate 58 | // 59 | HRESULT WhSeFindAllocationNode( whpx_state* Partition, WHSE_ALLOCATION_TRACKER_PREDICATE Predicate, WHSE_ALLOCATION_NODE** Node ); 60 | 61 | // Find the first allocation node matching the guest virtual address 62 | // 63 | HRESULT WhSeFindAllocationNodeByGva( whpx_state* Partition, uintptr_t GuestVa, WHSE_ALLOCATION_NODE** Node ); 64 | 65 | // Find the first allocation node matching the guest physical address 66 | // 67 | HRESULT WhSeFindAllocationNodeByGpa( whpx_state* Partition, uintptr_t GuestPa, WHSE_ALLOCATION_NODE** Node ); 68 | 69 | // Insert a tracking node 70 | // 71 | HRESULT WhSeInsertAllocationTrackingNode( whpx_state* Partition, WHSE_ALLOCATION_NODE Node ); 72 | 73 | #endif // !WINHVMEMORYALLOCATIONTRACKER_HPP 74 | -------------------------------------------------------------------------------- /unicorn-whpx-core/target/WinHvDefs.hpp: -------------------------------------------------------------------------------- 1 | #ifndef WINHVDEFS_HPP 2 | #define WINHVDEFS_HPP 3 | 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "whpx-internal.h" 11 | 12 | #include "DoubleLinkedList.hpp" 13 | 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | /** @file 18 | * @brief This file expose the structure used in the library 19 | * 20 | */ 21 | 22 | typedef WHV_MAP_GPA_RANGE_FLAGS WHSE_MEMORY_ACCESS_FLAGS; 23 | 24 | typedef WHV_REGISTER_NAME WHSE_REGISTER_NAME; 25 | typedef WHV_REGISTER_VALUE WHSE_REGISTER_VALUE; 26 | 27 | typedef WHV_RUN_VP_EXIT_CONTEXT WHSE_VP_EXIT_CONTEXT; 28 | typedef WHV_RUN_VP_EXIT_REASON WHSE_VP_EXIT_REASON; 29 | 30 | /** 31 | * @brief Enumeration describing the processor mode 32 | */ 33 | typedef enum WHSE_PROCESSOR_MODE{ 34 | None, 35 | KernelMode, 36 | UserMode, 37 | 38 | NumberOfModes 39 | } WHSE_PROCESSOR_MODE; 40 | 41 | typedef WHV_PROCESSOR_VENDOR WHSE_PROCESSOR_VENDOR; 42 | 43 | 44 | 45 | 46 | 47 | static size_t NUMBER_OF_GDT_DESCRIPTORS = 5; 48 | 49 | #define X64_TASK_STATE_SEGMENT_NUMBER_OF_ISTS 7 50 | #define X64_TASK_STATE_SEGMENT_NUMBER_OF_SPS 3 51 | 52 | #pragma pack( push, 1 ) 53 | struct _X64_TASK_STATE_SEGMENT { 54 | uint32_t Reserved00; 55 | 56 | // The Stack pointers used to load the stack on a privilege level change 57 | // (from a lower privileged ring to a higher one) 58 | // 59 | union { 60 | struct { 61 | uint64_t Rsp0; 62 | uint64_t Rsp1; 63 | uint64_t Rsp2; 64 | }; 65 | uint64_t Rsp[ X64_TASK_STATE_SEGMENT_NUMBER_OF_SPS ]; 66 | }; 67 | 68 | uint32_t Reserved1C; 69 | uint32_t Reserved20; 70 | 71 | // Interrupt Stack Table 72 | // The Stack Pointers that are used to load the stack when an entry in the 73 | // Interrupt Stack Table has an IST value other than 0 74 | // 75 | union { 76 | struct { 77 | uint64_t Ist1; 78 | uint64_t Ist2; 79 | uint64_t Ist3; 80 | uint64_t Ist4; 81 | uint64_t Ist5; 82 | uint64_t Ist6; 83 | uint64_t Ist7; 84 | }; 85 | uint64_t Ist[ X64_TASK_STATE_SEGMENT_NUMBER_OF_ISTS ]; 86 | }; 87 | 88 | uint32_t Reserved5C; 89 | uint32_t Reserved60; 90 | uint16_t Reserved64; 91 | 92 | // I/O Map base Address Field 93 | // It contains a 16-bit offset from the base of the TSS to the 94 | // I/O Permission Bit Map 95 | // 96 | uint16_t Iopb; 97 | }; 98 | #pragma pack( pop ) 99 | 100 | //static_assert( sizeof( _X64_TASK_STATE_SEGMENT ) == 0x68 ); 101 | 102 | typedef struct _X64_TASK_STATE_SEGMENT X64_TASK_STATE_SEGMENT; 103 | typedef struct _X64_TASK_STATE_SEGMENT* PX64_TASK_STATE_SEGMENT; 104 | 105 | #define X64_TASK_STATE_SEGMENT_IOPB_NONE 0 106 | 107 | uint16_t TssComputeIopbOffset(uint16_t offset); 108 | 109 | 110 | typedef struct _WHSE_SYSCALL_DATA WHSE_SYSCALL_DATA; 111 | typedef struct _WHSE_SYSCALL_DATA* PWHSE_SYSCALL_DATA; 112 | 113 | /** 114 | * @brief A structure representing a virtual processor 115 | * 116 | * This structure represent a virtual processor, its index (number), 117 | * the exit context upon a virtual processor exit and its registers state 118 | */ 119 | typedef struct _WHSE_VIRTUAL_PROCESSOR { 120 | uint32_t Index; 121 | WHSE_PROCESSOR_MODE Mode; 122 | WHSE_PROCESSOR_VENDOR Vendor; 123 | PX64_TASK_STATE_SEGMENT Tss; 124 | LPVOID Gdt; 125 | WHSE_VP_EXIT_CONTEXT ExitContext; 126 | } WHSE_VIRTUAL_PROCESSOR, * PWHSE_VIRTUAL_PROCESSOR; 127 | 128 | enum _MEMORY_BLOCK_TYPE { 129 | MemoryBlockPhysical, 130 | MemoryBlockVirtual, 131 | MemoryBlockPte, 132 | 133 | NumberOfMemoryBlockType 134 | }; 135 | 136 | typedef enum _MEMORY_BLOCK_TYPE MEMORY_BLOCK_TYPE; 137 | 138 | 139 | /** 140 | * @brief A structure to represent the boundaries of address space 141 | */ 142 | struct _ADDRESS_SPACE { 143 | uintptr_t LowestAddress; 144 | uintptr_t HighestAddress; 145 | size_t Size; 146 | }; 147 | 148 | typedef struct _ADDRESS_SPACE ADDRESS_SPACE; 149 | typedef struct _ADDRESS_SPACE* PADDRESS_SPACE; 150 | 151 | /** 152 | * @brief A structure to represent the boundaries of virtual address space 153 | */ 154 | struct _VIRTUAL_ADDRESS_SPACE { 155 | ADDRESS_SPACE UserSpace; 156 | ADDRESS_SPACE SystemSpace; 157 | }; 158 | 159 | typedef struct _VIRTUAL_ADDRESS_SPACE VIRTUAL_ADDRESS_SPACE; 160 | typedef struct _VIRTUAL_ADDRESS_SPACE* PVIRTUAL_ADDRESS_SPACE; 161 | 162 | /** 163 | * @brief A structure maintaining the necessary data to manage memory allocation 164 | */ 165 | struct _MEMORY_ARENA { 166 | ADDRESS_SPACE PhysicalAddressSpace; 167 | VIRTUAL_ADDRESS_SPACE VirtualAddressSpace; 168 | DLIST_HEADER AllocatedMemoryBlocks; 169 | }; 170 | 171 | typedef struct _MEMORY_ARENA MEMORY_ARENA; 172 | typedef struct _MEMORY_ARENA* PMEMORY_ARENA; 173 | 174 | /** 175 | * @brief A structure to store the memory layout 176 | * 177 | * The structure holds the physical memory and virtual memory boundaries. 178 | * The structure holds a list of host memory allocations backing the physical guest memory. 179 | * Paging directories guest physical address and host address is available throught 180 | * and properties. 181 | */ 182 | typedef struct _WHSE_MEMORY_LAYOUT_DESC { 183 | MEMORY_ARENA MemoryArena; 184 | uintptr_t Pml4PhysicalAddress; 185 | uintptr_t Pml4HostVa; 186 | uintptr_t InterruptDescriptorTableVirtualAddress; 187 | } WHSE_MEMORY_LAYOUT_DESC, * PWHSE_MEMORY_LAYOUT_DESC; 188 | 189 | typedef WHV_PARTITION_HANDLE WHSE_PARTITION_HANDLE; 190 | 191 | /** 192 | * @brief Enumeration to represent the virtual processor exits callbacks 193 | */ 194 | typedef enum _WHSE_EXIT_CALLBACK_SLOT{ 195 | // Standard exits caused by operations of the virtual processor 196 | // 197 | MemoryAccess, 198 | IoPortAccess, 199 | UnrecoverableException, 200 | InvalidVpRegisterValue, 201 | UnsupportedFeature, 202 | InterruptWindow, 203 | Halt, 204 | ApicEoi, 205 | 206 | // Additional exits that can be configured through partition properties 207 | // 208 | MsrAccess, 209 | Cpuid, 210 | Exception, 211 | Rdtsc, 212 | 213 | // Exits caused by the host 214 | // 215 | UserCanceled, 216 | 217 | NumberOfCallbacks 218 | }WHSE_EXIT_CALLBACK_SLOT; 219 | 220 | #define WHSECALLBACKAPI * 221 | #define WHSE_CALLBACK_RETURNTYPE bool 222 | 223 | typedef void * WHSE_CALLBACK; 224 | 225 | #pragma pack(push, 1) 226 | typedef struct _whpx_state { 227 | uint64_t mem_quota; 228 | WHV_PARTITION_HANDLE partition; 229 | int cpu_index; 230 | uint64_t exception_exit_bitmap; 231 | int32_t running_cpus; 232 | struct whpx_breakpoints breakpoints; 233 | bool step_pending; 234 | bool kernel_irqchip_allowed; 235 | bool kernel_irqchip_required; 236 | bool apic_in_platform; 237 | WHSE_MEMORY_LAYOUT_DESC MemoryLayout; 238 | WHSE_VIRTUAL_PROCESSOR VirtualProcessor; 239 | }whpx_state, *whpx_state_ptr; 240 | #pragma pack(pop) 241 | 242 | extern whpx_state whpx_global; 243 | 244 | 245 | void DumpRegs(); 246 | // Initialize paging and other stuff for the partition 247 | // 248 | HRESULT WhSeInitializeMemoryLayout(whpx_state *Partition); 249 | 250 | HRESULT WhSeInitializeAllocationTracker(whpx_state *Partition); 251 | 252 | HRESULT whpx_get_reg_value(const WHV_REGISTER_NAME RegisterName, 253 | WHV_REGISTER_VALUE *RegisterValues); 254 | 255 | HRESULT whpx_get_reg(const WHV_REGISTER_NAME RegisterName, uint64_t *regval); 256 | 257 | HRESULT whpx_set_reg_value(const WHV_REGISTER_NAME RegisterName, 258 | const WHV_REGISTER_VALUE RegisterValues); 259 | HRESULT whpx_set_reg(const WHV_REGISTER_NAME RegisterName, uint64_t regval); 260 | 261 | HRESULT WhSeMapHostToGuestVirtualMemory(whpx_state *Partition, uintptr_t HostVa, 262 | uintptr_t *GuestVa, size_t Size, 263 | WHSE_MEMORY_ACCESS_FLAGS Flags); 264 | 265 | int setuphv(WHV_PARTITION_HANDLE *partition); 266 | HRESULT WhSeFindAllocationGvaByGpa(whpx_state *Partition, uintptr_t GuestPa, 267 | uintptr_t *GuestVa); 268 | HRESULT WhSeFindAllocationGpaByGva(whpx_state *Partition, uintptr_t GuestVa, 269 | uintptr_t *GuestPa); 270 | void dumpbuf(LPVOID buf, 271 | size_t len); 272 | 273 | 274 | #ifdef __cplusplus 275 | } 276 | #endif 277 | #endif 278 | -------------------------------------------------------------------------------- /unicorn-whpx-core/target/WinHvHelpers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | #include 5 | 6 | #include "WinHvDefs.hpp" 7 | 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | size_t PAGE_SIZE = 4096; 18 | 19 | size_t inline ALIGN_UP(size_t x) 20 | { 21 | return ((PAGE_SIZE - 1) & x) ? ((x + PAGE_SIZE) & ~(PAGE_SIZE - 1)) : x; 22 | } 23 | 24 | typedef struct _RUN_OPTIONS { 25 | uint8_t *Code; 26 | size_t CodeSize; 27 | uintptr_t BaseAddress; 28 | } RUN_OPTIONS, *PRUN_OPTIONS; 29 | 30 | WHV_PARTITION_HANDLE gpartition = nullptr; 31 | 32 | 33 | 34 | static void getByteString(UINT32 startaddr, UINT8 *bytesbuf, size_t bytesread) 35 | { 36 | wchar_t debugStr[FILENAME_MAX]; 37 | char bytestr[65]; 38 | char charstr[20]; 39 | 40 | if (bytesread < 1) { 41 | return; 42 | } 43 | 44 | if (bytesread > 16) { 45 | return; 46 | } 47 | unsigned int i; 48 | 49 | char *bytestr_tmp = bytestr; 50 | unsigned char c; 51 | for (i = 0; i < bytesread; i++) { 52 | c = *(bytesbuf + i); 53 | _snprintf(bytestr_tmp, 4, "%02x ", c); 54 | bytestr_tmp += 3; 55 | } 56 | if (bytesread < 16) { 57 | for (int i = bytesread; i < 16; i++) { 58 | *bytestr_tmp = 0x20; 59 | bytestr_tmp += 1; 60 | *bytestr_tmp = 0x20; 61 | bytestr_tmp += 1; 62 | *bytestr_tmp = 0x20; 63 | bytestr_tmp += 1; 64 | } 65 | *bytestr_tmp = '\0'; 66 | } 67 | char *charstr_tmp = charstr; 68 | for (i = 0; i < bytesread; i++) { 69 | c = *(bytesbuf + i); 70 | if ((c < 127) && (c > 31) && (c != 92) && 71 | (c != 34)) // exclude '\'=92 and "=34 for JSON comp. 72 | { 73 | _snprintf(charstr_tmp++, 2, "%c", c); 74 | } 75 | 76 | else { 77 | _snprintf(charstr_tmp++, 2, "."); 78 | } 79 | } 80 | 81 | wsprintfW(debugStr, L"%08x %S %S\n", startaddr, bytestr, charstr); 82 | wprintf(debugStr); 83 | return; 84 | } 85 | 86 | static void hexdump(UINT8 *bytesbufRef, size_t size_len) 87 | { 88 | 89 | for (int i = 0; i <= size_len / 16; i++) { 90 | 91 | getByteString((i * 16), bytesbufRef + (i * 16), 92 | (i * 16) + 16 > size_len ? size_len % 16 : 16); 93 | } 94 | } 95 | 96 | 97 | void dumpbuf(LPVOID buf, size_t len) 98 | { 99 | hexdump((UINT8 *)buf, len); 100 | 101 | } 102 | 103 | HRESULT whpx_get_reg_value1(const WHV_REGISTER_NAME RegisterName, 104 | WHV_REGISTER_VALUE *RegisterValues) 105 | { 106 | HRESULT hr; 107 | hr = WHvGetVirtualProcessorRegisters(gpartition, 0, &RegisterName, 1, 108 | RegisterValues); 109 | if (FAILED(hr)) { 110 | printf("WHPX: Failed to get virtual processor registers," 111 | " hr=%08lx", 112 | hr); 113 | } 114 | 115 | return hr; 116 | } 117 | 118 | #define DumpGpr1(name) \ 119 | whpx_get_reg_value1(name, ®value); \ 120 | printf(#name##"\t= %016llx\n", regvalue.Reg64) 121 | 122 | #define DumpSeg1(name) \ 123 | whpx_get_reg_value1(name, ®value); \ 124 | printf(#name##"\tSel= %04lx\tBase= %016llx\tLimit= %04lx\n", \ 125 | regvalue.Segment.Selector, regvalue.Segment.Base, \ 126 | regvalue.Segment.Limit) 127 | 128 | #define DumpTbl1(name) \ 129 | whpx_get_reg_value1(name, ®value); \ 130 | printf(#name##"\tBase= %016llx\tLimit= %04lx\n", regvalue.Table.Base, \ 131 | regvalue.Table.Limit) 132 | 133 | void DumpRegs1() 134 | { 135 | 136 | WHV_REGISTER_VALUE regvalue = {0}; 137 | //// Get stack HVA 138 | //// 139 | // auto rspGva = registers[ Rsp ].Reg64; 140 | // WHSE_ALLOCATION_NODE* node = nullptr; 141 | // if ( FAILED( WhSeFindAllocationNodeByGva( Partition, rspGva, &node ) ) ) 142 | // return false; 143 | 144 | // auto rspHva = node->HostVirtualAddress; 145 | // printf( "RSP = %llx (hva = %llx)\n", rspGva, rspHva ); 146 | 147 | DumpGpr1(WHvX64RegisterRip); 148 | 149 | DumpGpr1(WHvX64RegisterRsp); 150 | DumpGpr1(WHvX64RegisterRbp); 151 | 152 | DumpGpr1(WHvX64RegisterRax); 153 | DumpGpr1(WHvX64RegisterRbx); 154 | DumpGpr1(WHvX64RegisterRcx); 155 | DumpGpr1(WHvX64RegisterRdx); 156 | DumpGpr1(WHvX64RegisterRdi); 157 | DumpGpr1(WHvX64RegisterRsi); 158 | DumpGpr1(WHvX64RegisterR8); 159 | DumpGpr1(WHvX64RegisterR9); 160 | DumpGpr1(WHvX64RegisterR10); 161 | DumpGpr1(WHvX64RegisterR11); 162 | DumpGpr1(WHvX64RegisterR12); 163 | DumpGpr1(WHvX64RegisterR13); 164 | DumpGpr1(WHvX64RegisterR14); 165 | DumpGpr1(WHvX64RegisterR15); 166 | DumpGpr1(WHvX64RegisterRflags); 167 | 168 | DumpGpr1(WHvX64RegisterCr0); 169 | DumpGpr1(WHvX64RegisterCr2); 170 | DumpGpr1(WHvX64RegisterCr3); 171 | DumpGpr1(WHvX64RegisterCr4); 172 | 173 | DumpSeg1(WHvX64RegisterCs); 174 | DumpSeg1(WHvX64RegisterDs); 175 | DumpSeg1(WHvX64RegisterSs); 176 | DumpSeg1(WHvX64RegisterEs); 177 | DumpSeg1(WHvX64RegisterFs); 178 | DumpSeg1(WHvX64RegisterGs); 179 | DumpSeg1(WHvX64RegisterTr); 180 | 181 | DumpTbl1(WHvX64RegisterGdtr); 182 | DumpTbl1(WHvX64RegisterIdtr); 183 | 184 | return; 185 | } 186 | 187 | #define CHECK(x) \ 188 | do { \ 189 | if (!(x)) { \ 190 | std::cerr << "Error: " #x " is false!" << std::endl; \ 191 | std::exit(1); \ 192 | } \ 193 | } while (0) 194 | 195 | // Read input file 196 | // 197 | bool ReadInputFile(const char *Filename, uint8_t **Code, size_t *CodeSize) 198 | { 199 | if (Code == NULL) { 200 | return false; 201 | } 202 | 203 | // Open the input file 204 | // 205 | FILE *fp = NULL; 206 | if (fopen_s(&fp, Filename, "rb") != 0) 207 | return false; 208 | 209 | if (fp == NULL) 210 | return false; 211 | 212 | // Get the file size 213 | // 214 | fseek(fp, 0, SEEK_END); 215 | *CodeSize = ALIGN_UP(ftell(fp)); 216 | *Code = (uint8_t *)(VirtualAlloc(NULL, *CodeSize, MEM_COMMIT | MEM_RESERVE, 217 | PAGE_EXECUTE_READWRITE)); 218 | if (*Code == NULL) { 219 | fclose(fp); 220 | return false; 221 | } 222 | 223 | fseek(fp, 0, SEEK_SET); 224 | 225 | fread(*Code, *CodeSize, 1, fp); 226 | 227 | fclose(fp); 228 | 229 | return true; 230 | } 231 | 232 | 233 | EXTERN_C int setuphv(WHV_PARTITION_HANDLE* partition) 234 | { 235 | 236 | 237 | // 检查是否启用了 Hyper-V 238 | BOOL hypervisorPresent = false; 239 | CHECK(WHvGetCapability(WHvCapabilityCodeHypervisorPresent, 240 | &hypervisorPresent, sizeof(hypervisorPresent), 241 | nullptr) == S_OK); 242 | if (!hypervisorPresent) { 243 | std::cerr << "Hyper-V is not available" << std::endl; 244 | return 1; 245 | } 246 | 247 | // 创建一个 Hyper-V 分区,把它看作一个虚拟机就行了 248 | 249 | CHECK(WHvCreatePartition(partition) == S_OK); 250 | UINT32 processorCount = 1; // 只需要一个处理器核心就够了 251 | CHECK(WHvSetPartitionProperty( 252 | *partition, WHvPartitionPropertyCodeProcessorCount, 253 | &processorCount, sizeof(processorCount)) == S_OK); 254 | CHECK(WHvSetupPartition(*partition) == S_OK); 255 | CHECK(WHvCreateVirtualProcessor(*partition, 0, 0) == S_OK); 256 | 257 | WHV_REGISTER_NAME name = WHvX64RegisterCs; 258 | WHV_REGISTER_VALUE value = {}; 259 | value.Segment.Base = 0x00000000; 260 | value.Segment.Limit = 0x0000FFFF; 261 | value.Segment.Selector = 0x0000; 262 | value.Segment.Attributes = 263 | 0x009B; // 代码段的属性,详情可以参考该字段的定义和 Intel 手册 264 | whpx_set_reg_value(name, value); 265 | 266 | return 0; 267 | } 268 | 269 | EXTERN_C int mainfake() 270 | { 271 | RUN_OPTIONS opt; 272 | 273 | if (ReadInputFile("E:\\git\\HyperVResearch\\ida\\qiling-" 274 | "master\\examples\\rootfs\\x8664_windows\\bin\\fake.exe", 275 | &opt.Code, &opt.CodeSize)) { 276 | { 277 | setuphv(&gpartition); 278 | 279 | 280 | std::uint64_t memorySize = opt.CodeSize, memoryOffset = 0x00001000; 281 | /*std::uint64_t memorySize = 0x00001000, memoryOffset = 0x00001000; 282 | // 分配一个 4KiB 的内存页,注意这段内存要对齐到页边界,随便 malloc 283 | 或者 new 出来是不行的 void* memory = VirtualAlloc(nullptr, 284 | memorySize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); CHECK(memory 285 | != nullptr); std::memcpy(memory, code.data(), code.size()); // 286 | 把代码复制进去 287 | // 将刚刚分配的内存页设置到虚拟机的地址 0x00001000 上(0x00000000 288 | 被实模式的中断向量表占用了) CHECK(WHvMapGpaRange(partition, memory, 289 | memoryOffset, memorySize, WHvMapGpaRangeFlagRead | 290 | WHvMapGpaRangeFlagWrite | WHvMapGpaRangeFlagExecute) == S_OK);*/ 291 | 292 | CHECK(WHvMapGpaRange(gpartition, opt.Code, memoryOffset, memorySize, 293 | WHvMapGpaRangeFlagRead | 294 | WHvMapGpaRangeFlagWrite | 295 | WHvMapGpaRangeFlagExecute) == S_OK); 296 | 297 | /* 298 | //初始化 CS 寄存器(代码段寄存器) 299 | WHV_REGISTER_NAME name = WHvX64RegisterCs; 300 | WHV_REGISTER_VALUE value = {}; 301 | value.Segment.Base = 0x00000000; 302 | value.Segment.Limit = 0x0000FFFF; 303 | value.Segment.Selector = 0x0000; 304 | value.Segment.Attributes = 305 | 0x009B; // 代码段的属性,详情可以参考该字段的定义和 Intel 手册 306 | CHECK(WHvSetVirtualProcessorRegisters(gpartition, 0, &name, 1, 307 | &value) == S_OK);*/ 308 | 309 | // 获取与设置 RIP 寄存器(程序指针寄存器)的函数 310 | auto getRIP = []() -> std::uint64_t { 311 | WHV_REGISTER_NAME name = WHvX64RegisterRip; 312 | WHV_REGISTER_VALUE value = {}; 313 | CHECK(WHvGetVirtualProcessorRegisters(gpartition, 0, &name, 1, 314 | &value) == S_OK); 315 | return value.Reg64; 316 | }; 317 | auto setRIP = [](std::uint64_t rip) { 318 | WHV_REGISTER_NAME name = WHvX64RegisterRip; 319 | WHV_REGISTER_VALUE value = {}; 320 | value.Reg64 = rip; 321 | CHECK(WHvSetVirtualProcessorRegisters(gpartition, 0, &name, 1, 322 | &value) == S_OK); 323 | }; 324 | // 从创建的内存页的首地址开始执行代码 325 | // setRIP(memoryOffset); 326 | setRIP(memoryOffset + 0x400); 327 | 328 | // 虚拟机的主循环 329 | while (true) { 330 | DumpRegs1(); 331 | WHV_RUN_VP_EXIT_CONTEXT context = {}; 332 | // 启动虚拟机处理器 333 | CHECK(WHvRunVirtualProcessor(gpartition, 0, &context, 334 | sizeof(context)) == S_OK); 335 | // 虚拟机 Exit 了,可能是由于 IO 336 | // 操作、停机指令或者各种其他原因造成的 337 | switch (context.ExitReason) { 338 | case WHvRunVpExitReasonX64IoPortAccess: { 339 | // 如果是往 0x3F8 端口里写一个字节的数据,就输出出来 340 | if (context.IoPortAccess.AccessInfo.IsWrite && 341 | context.IoPortAccess.AccessInfo.AccessSize == 1 && 342 | !context.IoPortAccess.AccessInfo.StringOp && 343 | context.IoPortAccess.PortNumber == 0x3F8) 344 | std::cout << char(context.IoPortAccess.Rax); 345 | // Hyper-V 在 IO 时处理方式与 KVM 346 | // 不同,不会自动指向下一条指令,所以需要手动改 RIP 347 | setRIP(getRIP() + context.VpContext.InstructionLength); 348 | break; // 处理完了,继续运行虚拟机处理器 349 | } 350 | case WHvRunVpExitReasonX64Halt: { // 停机了,删库跑路 351 | std::cerr << "VM Halt" << std::endl; 352 | WHvDeletePartition(gpartition); 353 | return 0; 354 | } 355 | default: { 356 | std::cerr 357 | << "Unexpected Exit Reason: " << context.ExitReason 358 | << std::endl; 359 | return 1; 360 | } 361 | } 362 | } 363 | } 364 | } 365 | } 366 | 367 | 368 | bool WhSeIsHypervisorPresent() { 369 | auto capabilities = WHV_CAPABILITY { 0 }; 370 | uint32_t written = 0; 371 | 372 | // Check if hypervisor is present 373 | // 374 | auto hresult = ::WHvGetCapability( WHvCapabilityCodeHypervisorPresent, &capabilities, sizeof( decltype( capabilities ) ), &written ); 375 | if ( FAILED( hresult ) || !capabilities.HypervisorPresent ) { 376 | return false; 377 | } 378 | 379 | hresult = ::WHvGetCapability( WHvCapabilityCodeExtendedVmExits, &capabilities, sizeof( decltype( capabilities ) ), &written ); 380 | if ( FAILED( hresult ) 381 | || !capabilities.ExtendedVmExits.X64CpuidExit 382 | || !capabilities.ExtendedVmExits.X64MsrExit 383 | || !capabilities.ExtendedVmExits.X64RdtscExit ) { 384 | return false; 385 | } 386 | 387 | return true; 388 | } 389 | 390 | /** 391 | * @brief Wrapper around GetLastError 392 | * 393 | * @return A code indicating the last error 394 | */ 395 | uint32_t WhSeGetLastError() { 396 | return ::GetLastError(); 397 | } 398 | 399 | /** 400 | * @brief Wrapper around GetLastError and HRESULT_FROM_WIN32 401 | * 402 | * @return A code indicating the last error 403 | */ 404 | HRESULT WhSeGetLastHresult() { 405 | auto lasterror = WhSeGetLastError(); 406 | return HRESULT_FROM_WIN32( lasterror ); 407 | } 408 | 409 | #define ARCH_X64_HIGHESTORDER_BIT_IMPLEMENTED 47 410 | #define ARCH_X64_CANONICAL_BITMASK ~( ( 1ull << ( ARCH_X64_HIGHESTORDER_BIT_IMPLEMENTED + 1 ) ) - 1 ) 411 | 412 | /** 413 | * @brief An helper function to know if a virtual address is canonical 414 | * 415 | * @return A boolean indicating if the virtual address is canonical (true) or not (false) 416 | */ 417 | bool WhSeIsCanonicalAddress( uintptr_t VirtualAddress ) { 418 | bool highest_bit_set = ( ( VirtualAddress & ( 1ull << ARCH_X64_HIGHESTORDER_BIT_IMPLEMENTED ) ) >> ARCH_X64_HIGHESTORDER_BIT_IMPLEMENTED ) == 1; 419 | 420 | uintptr_t masked = VirtualAddress & ARCH_X64_CANONICAL_BITMASK; 421 | 422 | return highest_bit_set ? masked == ARCH_X64_CANONICAL_BITMASK : masked == 0x00000000'00000000; 423 | } 424 | -------------------------------------------------------------------------------- /unicorn-whpx-core/target/WinHvHelpers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef WINHVHELPERS_HPP 2 | #define WINHVHELPERS_HPP 3 | 4 | 5 | 6 | #include 7 | #include 8 | 9 | int mainfake(); 10 | // Check if the hypervisor is available 11 | // Must be called prior to any other calls 12 | // To check for hypervisor presence 13 | // 14 | bool WhSeIsHypervisorPresent(); 15 | 16 | // Wrapper around GetLastError 17 | // 18 | uint32_t WhSeGetLastError(); 19 | 20 | // Wrapper around GetLastError and HRESULT_FROM_WIN32 21 | // 22 | HRESULT WhSeGetLastHresult(); 23 | 24 | // An helper function to know if a virtual address is canonical 25 | // 26 | bool WhSeIsCanonicalAddress( uintptr_t VirtualAddress ); 27 | 28 | #endif // !WINHVHELPERS_HPP 29 | -------------------------------------------------------------------------------- /unicorn-whpx-core/target/WinHvMemory.cpp: -------------------------------------------------------------------------------- 1 | #include "WinHvMemory.hpp" 2 | #include "WinHvMemoryInternal.hpp" 3 | #include "WinHvHelpers.hpp" 4 | #include "WinHvUtils.hpp" 5 | #include "winbase.h" 6 | #include "WinHvAllocationTracker.hpp" 7 | #include 8 | /* 9 | HRESULT WhSeSetProcessorRegisters(whpx_state *Partition, 10 | WHSE_REGISTERS Registers) 11 | { 12 | return 0; 13 | } 14 | HRESULT WhSeGetProcessorRegisters(whpx_state *Partition, 15 | WHSE_REGISTERS Registers) 16 | { 17 | return 0; 18 | }*/ 19 | /** 20 | * @brief A routine to translate a 21 | * 22 | * Internal routine 23 | * A routine to translate a to a 24 | * Protection Flags value compatible with the API 25 | * 26 | * @param Flags The flags to translate 27 | * @return The translated flags 28 | */ 29 | constexpr uint32_t AccessFlagsToProtectionFlags(WHSE_MEMORY_ACCESS_FLAGS Flags) 30 | { 31 | uint32_t protectionFlags = 0; 32 | 33 | if (Flags == WHvMapGpaRangeFlagNone) 34 | return PAGE_NOACCESS; 35 | 36 | if (HAS_FLAGS(Flags, WHvMapGpaRangeFlagRead)) 37 | protectionFlags |= (1 << 1); 38 | 39 | if (HAS_FLAGS(Flags, WHvMapGpaRangeFlagWrite)) 40 | protectionFlags <<= 1; 41 | 42 | if (HAS_FLAGS(Flags, WHvMapGpaRangeFlagExecute)) 43 | protectionFlags <<= 4; 44 | 45 | return protectionFlags; 46 | } 47 | 48 | /** 49 | * @brief Allocate memory in guest physical address space (backed by host 50 | * memory) 51 | * 52 | * Allocate memory in guest physical address space (backed by host memory), 53 | * mapping twice on the same guest physical memory address will replace any 54 | * existing mapping but will not free the existing host backing memory. 55 | * 56 | * @param Partition The VM partition 57 | * @param HostVa The host virtual memory address backing the guest physical 58 | * memory 59 | * @param GuestPa The guest physical memory address 60 | * @param Size The size of the allocated memory 61 | * @param Flags The flags that describe the allocated memory (Read Write 62 | * Execute) 63 | * @return A result code 64 | */ 65 | HRESULT WhSeAllocateGuestPhysicalMemory(whpx_state *Partition, 66 | uintptr_t *HostVa, uintptr_t *GuestPa, 67 | size_t Size, 68 | WHSE_MEMORY_ACCESS_FLAGS Flags) 69 | { 70 | if (Partition == nullptr) 71 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 72 | 73 | if (HostVa == nullptr) 74 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 75 | 76 | if (*HostVa != 0) 77 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 78 | 79 | if (GuestPa == nullptr) 80 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 81 | 82 | auto size = ALIGN_UP(Size); 83 | 84 | uintptr_t suggestedGpa = 0; 85 | if (*GuestPa == 0) { 86 | auto hresult = 87 | WhSiSuggestPhysicalAddress(Partition, size, &suggestedGpa); 88 | if (FAILED(hresult)) 89 | return hresult; 90 | } else 91 | suggestedGpa = ALIGN(*GuestPa); 92 | 93 | PWHSE_ALLOCATION_NODE existingNode = nullptr; 94 | auto hresult = 95 | WhSeFindAllocationNodeByGpa(Partition, suggestedGpa, &existingNode); 96 | if (hresult != HRESULT_FROM_WIN32(ERROR_NOT_FOUND) && FAILED(hresult)) 97 | return hresult; 98 | 99 | if (existingNode != nullptr) 100 | return HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR); 101 | 102 | // Allocate memory into host 103 | // 104 | auto protectionFlags = AccessFlagsToProtectionFlags(Flags); 105 | auto allocatedHostVa = ::VirtualAlloc( 106 | nullptr, Size, MEM_COMMIT | MEM_RESERVE, protectionFlags); 107 | if (allocatedHostVa == nullptr) 108 | return WhSeGetLastHresult(); 109 | 110 | WHSE_ALLOCATION_NODE node{ 111 | .BlockType = MEMORY_BLOCK_TYPE::MemoryBlockPhysical, 112 | .HostVirtualAddress = reinterpret_cast(allocatedHostVa), 113 | .GuestPhysicalAddress = suggestedGpa, 114 | .GuestVirtualAddress = 0, 115 | .Size = size}; 116 | 117 | hresult = WhSeInsertAllocationTrackingNode(Partition, node); 118 | if (FAILED(hresult)) 119 | return hresult; 120 | 121 | // Create the allocated range into the guest physical address space 122 | // 123 | hresult = ::WHvMapGpaRange( 124 | Partition->partition, allocatedHostVa, 125 | static_cast(suggestedGpa), size, Flags); 126 | if (FAILED(hresult)) { 127 | if (allocatedHostVa != nullptr) 128 | ::VirtualFree(allocatedHostVa, 0, MEM_RELEASE); 129 | 130 | return hresult; 131 | } 132 | 133 | *HostVa = reinterpret_cast(allocatedHostVa); 134 | *GuestPa = suggestedGpa; 135 | 136 | return hresult; 137 | } 138 | 139 | /** 140 | * @brief Map memory from host to guest physical address space (backed by host 141 | * memory) 142 | * 143 | * Map host memory to guest physical memory, mapping twice 144 | * on the same guest physical memory address will replace any existing mapping 145 | * but will not free the existing host backing memory. 146 | * 147 | * @param Partition The VM partition 148 | * @param HostVa The host virtual memory address backing the guest physical 149 | * memory 150 | * @param GuestPa The guest physical memory address 151 | * @param Size The size of the allocated memory 152 | * @param Flags The flags that describe the allocated memory (Read Write 153 | * Execute) 154 | * @return A result code 155 | */ 156 | HRESULT WhSeMapHostToGuestPhysicalMemory(whpx_state *Partition, 157 | uintptr_t HostVa, uintptr_t *GuestPa, 158 | size_t Size, 159 | WHSE_MEMORY_ACCESS_FLAGS Flags) 160 | { 161 | if (Partition == nullptr) 162 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 163 | 164 | if (HostVa == 0) 165 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 166 | 167 | if (GuestPa == nullptr) 168 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 169 | 170 | auto size = ALIGN_UP(Size); 171 | 172 | uintptr_t suggestedGpa = 0; 173 | if (*GuestPa == 0) { 174 | auto hresult = 175 | WhSiSuggestPhysicalAddress(Partition, size, &suggestedGpa); 176 | if (FAILED(hresult)) 177 | return hresult; 178 | } else 179 | suggestedGpa = ALIGN(*GuestPa); 180 | 181 | PWHSE_ALLOCATION_NODE existingNode = nullptr; 182 | auto hresult = 183 | WhSeFindAllocationNodeByGpa(Partition, suggestedGpa, &existingNode); 184 | if (hresult != HRESULT_FROM_WIN32(ERROR_NOT_FOUND) && FAILED(hresult)) 185 | return hresult; 186 | 187 | if (existingNode != nullptr) 188 | return HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR); 189 | 190 | WHSE_ALLOCATION_NODE node{.BlockType = 191 | MEMORY_BLOCK_TYPE::MemoryBlockPhysical, 192 | .HostVirtualAddress = HostVa, 193 | .GuestPhysicalAddress = suggestedGpa, 194 | .GuestVirtualAddress = 0, 195 | .Size = size}; 196 | 197 | hresult = WhSeInsertAllocationTrackingNode(Partition, node); 198 | if (FAILED(hresult)) 199 | return hresult; 200 | 201 | // Map the memory range into the guest physical address space 202 | // 203 | hresult = ::WHvMapGpaRange( 204 | Partition->partition, reinterpret_cast(HostVa), 205 | static_cast(suggestedGpa), size, Flags); 206 | if (FAILED(hresult)) 207 | return hresult; 208 | 209 | *GuestPa = suggestedGpa; 210 | 211 | return hresult; 212 | } 213 | 214 | /** 215 | * @brief Allocate memory in guest virtual address space (backed by host memory) 216 | * 217 | * Allocate memory in guest virtual address space (backed by host memory), 218 | * mapping twice on the same guest physical memory address will replace any 219 | * existing physical memory mapping but will not free the host virtual memory 220 | * nor update the guest PTEs. 221 | * 222 | * @param Partition The VM partition 223 | * @param HostVa The host virtual memory address backing the guest physical 224 | * memory 225 | * @param GuestVa The guest virtual memory address 226 | * @param Size The size of the allocated memory 227 | * @param Flags The flags that describe the allocated memory (Read Write 228 | * Execute) 229 | * @return A result code 230 | */ 231 | HRESULT WhSeAllocateGuestVirtualMemory(whpx_state *Partition, uintptr_t *HostVa, 232 | uintptr_t *GuestVa, size_t Size, 233 | WHSE_MEMORY_ACCESS_FLAGS Flags) 234 | { 235 | if (Partition == nullptr) 236 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 237 | 238 | if (HostVa == nullptr) 239 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 240 | 241 | if (*HostVa != 0) 242 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 243 | 244 | if (GuestVa == nullptr) 245 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 246 | 247 | auto size = ALIGN_UP(Size); 248 | 249 | PWHSE_ALLOCATION_NODE existingNode = nullptr; 250 | auto hresult = 251 | WhSeFindAllocationNodeByGva(Partition, *GuestVa, &existingNode); 252 | if (hresult != HRESULT_FROM_WIN32(ERROR_NOT_FOUND) && FAILED(hresult)) 253 | return hresult; 254 | 255 | uintptr_t suggestedGva = 0; 256 | if (*GuestVa == 0 || existingNode != nullptr) { 257 | auto hresult = WhSiSuggestVirtualAddress( 258 | Partition, size, &suggestedGva, Partition->VirtualProcessor.Mode); 259 | if (FAILED(hresult)) 260 | return hresult; 261 | } else 262 | suggestedGva = ALIGN(*GuestVa); 263 | 264 | existingNode = nullptr; 265 | hresult = 266 | WhSeFindAllocationNodeByGva(Partition, suggestedGva, &existingNode); 267 | if (hresult != HRESULT_FROM_WIN32(ERROR_NOT_FOUND) && FAILED(hresult)) 268 | return hresult; 269 | 270 | if (existingNode != nullptr) 271 | return HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR); 272 | 273 | // Compute starting and ending guest virtual addresses 274 | // 275 | auto startingGva = ALIGN(suggestedGva); 276 | auto endingGva = ALIGN_UP(startingGva + size); 277 | 278 | // Allocate memory into host 279 | // 280 | auto protectionFlags = AccessFlagsToProtectionFlags(Flags); 281 | auto allocatedHostVa = ::VirtualAlloc( 282 | nullptr, size, MEM_COMMIT | MEM_RESERVE, protectionFlags); 283 | if (allocatedHostVa == nullptr) 284 | return WhSeGetLastHresult(); 285 | 286 | uintptr_t suggestedGpa = 0; 287 | hresult = WhSiSuggestPhysicalAddress(Partition, size, &suggestedGpa); 288 | if (FAILED(hresult)) 289 | return hresult; 290 | 291 | WHSE_ALLOCATION_NODE node{ 292 | .BlockType = MEMORY_BLOCK_TYPE::MemoryBlockVirtual, 293 | .HostVirtualAddress = reinterpret_cast(allocatedHostVa), 294 | .GuestPhysicalAddress = suggestedGpa, 295 | .GuestVirtualAddress = startingGva, 296 | .Size = size}; 297 | 298 | hresult = WhSeInsertAllocationTrackingNode(Partition, node); 299 | if (FAILED(hresult)) 300 | return hresult; 301 | 302 | // Setup matching PTEs 303 | // 304 | for (auto gva = startingGva, page = suggestedGpa; gva < endingGva; 305 | gva += PAGE_SIZE, page += PAGE_SIZE) { 306 | hresult = WhSiInsertPageTableEntry(Partition, gva, page); 307 | if (FAILED(hresult)) 308 | return hresult; 309 | } 310 | 311 | hresult = ::WHvMapGpaRange( 312 | Partition->partition, allocatedHostVa, 313 | static_cast(suggestedGpa), size, Flags); 314 | if (FAILED(hresult)) { 315 | if (allocatedHostVa != nullptr) 316 | ::VirtualFree(allocatedHostVa, 0, MEM_RELEASE); 317 | 318 | return hresult; 319 | } 320 | 321 | *HostVa = reinterpret_cast(allocatedHostVa); 322 | *GuestVa = startingGva; 323 | 324 | return hresult; 325 | } 326 | 327 | /** 328 | * @brief Map memory from host to guest virtual address space (backed by host 329 | * memory) 330 | * 331 | * Map host memory to guest virtual memory, mapping twice 332 | * on the same guest physical memory address will replace any existing physical 333 | * memory mapping but will not free the host virtual memory nor update the guest 334 | * PTEs. 335 | * 336 | * @param Partition The VM partition 337 | * @param HostVa The host virtual memory address backing the guest physical 338 | * memory 339 | * @param GuestVa The guest virtual memory address 340 | * @param Size The size of the allocated memory 341 | * @param Flags The flags that describe the allocated memory (Read Write 342 | * Execute) 343 | * @return A result code 344 | */ 345 | HRESULT WhSeMapHostToGuestVirtualMemory(whpx_state *Partition, uintptr_t HostVa, 346 | uintptr_t *GuestVa, size_t Size, 347 | WHSE_MEMORY_ACCESS_FLAGS Flags) 348 | { 349 | if (Partition == nullptr) 350 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 351 | 352 | if (HostVa == 0) 353 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 354 | 355 | if (GuestVa == nullptr) 356 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 357 | 358 | auto size = ALIGN_UP(Size); 359 | 360 | PWHSE_ALLOCATION_NODE existingNode = nullptr; 361 | auto hresult = 362 | WhSeFindAllocationNodeByGva(Partition, *GuestVa, &existingNode); 363 | if (hresult != HRESULT_FROM_WIN32(ERROR_NOT_FOUND) && FAILED(hresult)) 364 | return hresult; 365 | 366 | uintptr_t suggestedGva = 0; 367 | if (*GuestVa == 0 || existingNode != nullptr) { 368 | auto hresult = WhSiSuggestVirtualAddress( 369 | Partition, size, &suggestedGva, Partition->VirtualProcessor.Mode); 370 | if (FAILED(hresult)) 371 | return hresult; 372 | } else 373 | suggestedGva = ALIGN(*GuestVa); 374 | 375 | existingNode = nullptr; 376 | hresult = 377 | WhSeFindAllocationNodeByGva(Partition, suggestedGva, &existingNode); 378 | if (hresult != HRESULT_FROM_WIN32(ERROR_NOT_FOUND) && FAILED(hresult)) 379 | return hresult; 380 | 381 | if (existingNode != nullptr) 382 | return HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR); 383 | 384 | // Compute starting and ending guest virtual addresses 385 | // 386 | auto startingGva = ALIGN(suggestedGva); 387 | auto endingGva = ALIGN_UP(startingGva + size); 388 | 389 | uintptr_t suggestedGpa = 0; 390 | hresult = WhSiSuggestPhysicalAddress(Partition, size, &suggestedGpa); 391 | if (FAILED(hresult)) 392 | return hresult; 393 | 394 | WHSE_ALLOCATION_NODE node{.BlockType = 395 | MEMORY_BLOCK_TYPE::MemoryBlockVirtual, 396 | .HostVirtualAddress = HostVa, 397 | .GuestPhysicalAddress = suggestedGpa, 398 | .GuestVirtualAddress = startingGva, 399 | .Size = size}; 400 | 401 | hresult = WhSeInsertAllocationTrackingNode(Partition, node); 402 | if (FAILED(hresult)) 403 | return hresult; 404 | 405 | // Setup matching PTEs 406 | // 407 | for (auto gva = startingGva, page = suggestedGpa; gva < endingGva; 408 | gva += PAGE_SIZE, page += PAGE_SIZE) { 409 | hresult = WhSiInsertPageTableEntry(Partition, gva, page); 410 | if (FAILED(hresult)) 411 | return hresult; 412 | } 413 | 414 | hresult = ::WHvMapGpaRange( 415 | Partition->partition, reinterpret_cast(HostVa), 416 | static_cast(suggestedGpa), size, Flags); 417 | if (FAILED(hresult)) 418 | return hresult; 419 | 420 | *GuestVa = startingGva; 421 | 422 | return hresult; 423 | } 424 | 425 | /** 426 | * @brief Free memory in guest physical address space 427 | * 428 | * @param Partition The VM partition 429 | * @param HostVa The host memory virtual address backing the physical guest 430 | * memory 431 | * @param GuestPa The guest memory physical address 432 | * @param Size The size of the allocation 433 | * @return A result code 434 | */ 435 | HRESULT WhSeFreeGuestPhysicalMemory(whpx_state *Partition, uintptr_t HostVa, 436 | uintptr_t GuestPa, size_t Size) 437 | { 438 | if (Partition == nullptr) 439 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 440 | 441 | if (HostVa == 0) 442 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 443 | 444 | /*WHSE_ALLOCATION_NODE* node = nullptr; 445 | auto hresult = WhSeFindAllocationNodeByGpa( Partition, GuestPa, &node ); 446 | if ( FAILED( hresult ) ) 447 | return hresult;*/ 448 | 449 | auto hresult = ::WHvUnmapGpaRange( 450 | Partition->partition, static_cast(GuestPa), 451 | ALIGN_UP(Size)); 452 | if (FAILED(hresult)) 453 | return hresult; 454 | 455 | auto result = 456 | ::VirtualFree(reinterpret_cast(HostVa), 0, MEM_RELEASE); 457 | if (!result) 458 | return WhSeGetLastHresult(); 459 | 460 | return hresult; 461 | } 462 | 463 | /** 464 | * @brief Free memory in guest virtual address space 465 | * 466 | * @param Partition The VM partition 467 | * @param HostVa The host memory virtual address backing the physical guest 468 | * memory 469 | * @param GuestVa The guest memory virtual address 470 | * @param Size The size of the allocation 471 | * @return A result code 472 | */ 473 | HRESULT WhSeFreeGuestVirtualMemory(whpx_state *Partition, uintptr_t HostVa, 474 | uintptr_t GuestVa, size_t Size) 475 | { 476 | if (Partition == nullptr) 477 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 478 | 479 | if (HostVa == 0) 480 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 481 | 482 | /*WHSE_ALLOCATION_NODE* node = nullptr; 483 | auto hresult = WhSeFindAllocationNodeByGva( Partition, GuestVa, &node ); 484 | if ( FAILED( hresult ) ) 485 | return hresult;*/ 486 | 487 | uintptr_t gpa{}; 488 | auto hresult = WhSeTranslateGvaToGpa(Partition, GuestVa, &gpa, nullptr); 489 | if (FAILED(hresult)) 490 | return hresult; 491 | 492 | hresult = ::WHvUnmapGpaRange(Partition->partition, 493 | static_cast(gpa), 494 | ALIGN_UP(Size)); 495 | if (FAILED(hresult)) 496 | return hresult; 497 | 498 | auto result = 499 | ::VirtualFree(reinterpret_cast(HostVa), 0, MEM_RELEASE); 500 | if (!result) 501 | return WhSeGetLastHresult(); 502 | 503 | return hresult; 504 | } 505 | #define CR4_OSXSAVE_MASK (1U << 18) 506 | #define CR4_OSFXSR_SHIFT 9 507 | #define CR4_OSFXSR_MASK (1U << CR4_OSFXSR_SHIFT) 508 | #define CR4_OSXMMEXCPT_MASK (1U << 10) 509 | /** 510 | * @brief Initialize paging and other memory stuff for the partition 511 | * 512 | * @param Partition The VM partition 513 | * @return A result code 514 | */ 515 | EXTERN_C HRESULT WhSeInitializeMemoryLayout(whpx_state *Partition) 516 | { 517 | HRESULT hresult = S_OK; 518 | if (Partition == nullptr) 519 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 520 | Partition->VirtualProcessor.Mode = KernelMode; 521 | 522 | hresult = WhSiInitializeMemoryArena(Partition); 523 | if (FAILED(hresult)) 524 | return hresult; 525 | 526 | uintptr_t pml4Address = 0; 527 | Partition->MemoryLayout.Pml4HostVa = 0; 528 | 529 | // Build paging tables 530 | // 531 | hresult = WhSiSetupPaging(Partition, &pml4Address); 532 | if (FAILED(hresult)) 533 | return hresult; 534 | 535 | Partition->MemoryLayout.Pml4PhysicalAddress = pml4Address; 536 | WHV_REGISTER_NAME RegisterName = WHvX64RegisterCr0; 537 | uint64_t cr0val = 0; 538 | 539 | whpx_get_reg(RegisterName, &cr0val); 540 | 541 | cr0val = (cr0val | (1ULL << 31) | (1 << 0)) & UINT32_MAX; 542 | 543 | whpx_set_reg(RegisterName, cr0val); 544 | 545 | whpx_set_reg(WHvX64RegisterCr3, pml4Address); 546 | RegisterName = WHvX64RegisterCr4; 547 | 548 | uint64_t cr4val = 0; 549 | 550 | whpx_get_reg(RegisterName, &cr4val); 551 | 552 | cr4val = (cr4val | (1ULL << 5)) & ~(1 << 24); 553 | cr4val |= CR4_OSXSAVE_MASK; 554 | cr4val |= CR4_OSFXSR_MASK; 555 | cr4val |= CR4_OSXMMEXCPT_MASK; 556 | whpx_set_reg(RegisterName, cr4val); 557 | uint64_t Eferval = 0; 558 | 559 | RegisterName = WHvX64RegisterEfer; 560 | whpx_get_reg(RegisterName, &Eferval); 561 | Eferval = (Eferval | (1ULL << 0) | (1ULL << 8)) & ~(1 << 16); 562 | whpx_set_reg(RegisterName, Eferval); 563 | printf("Cr0 :%016llx,Cr3:%016llx,Cr4:%016llx,Efer:%016llx\r\n", cr0val, 564 | pml4Address, cr4val, Eferval); 565 | 566 | uintptr_t rflagval = 567 | MAKE_RFLAGS(0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); 568 | whpx_set_reg(WHvX64RegisterRflags, rflagval); 569 | 570 | hresult = WhSiSetupGlobalDescriptorTable(Partition); 571 | if (FAILED(hresult)) 572 | return hresult; 573 | 574 | hresult = WhSiSetupInterruptDescriptorTable(Partition); 575 | if (FAILED(hresult)) 576 | return hresult; 577 | 578 | hresult = WhSpSwitchProcessor(Partition->VirtualProcessor.Mode); 579 | if (FAILED(hresult)) 580 | return hresult; 581 | 582 | return hresult; 583 | /* 584 | auto registers = Partition->VirtualProcessor.Registers; 585 | hresult = WhSeGetProcessorRegisters( Partition, registers ); 586 | if ( FAILED( hresult ) ) 587 | return hresult; 588 | 589 | // Enable paging and protected mode 590 | // 591 | registers[ Cr0 ].Reg64 = ( registers[ Cr0 ].Reg64 | ( 1ULL << 31 ) | ( 1 << 592 | 0 ) ) & UINT32_MAX; 593 | 594 | // Set the pml4 physical address 595 | // 596 | registers[ Cr3 ].Reg64 = pml4Address; 597 | 598 | // Enable PAE 599 | // 600 | registers[ Cr4 ].Reg64 = ( registers[ Cr4 ].Reg64 | ( 1ULL << 5 ) ) & ~( 1 601 | << 24 ); 602 | 603 | // Enable Long Mode and Syscall 604 | // 605 | registers[ Efer ].Reg64 = ( registers[ Efer ].Reg64 | ( 1ULL << 0 ) | ( 1ULL 606 | << 8 ) ) & ~( 1 << 16 ); 607 | 608 | // Update registers at this point 609 | // 610 | hresult = WhSeSetProcessorRegisters( Partition, registers ); 611 | if ( FAILED( hresult ) ) 612 | return hresult; 613 | 614 | // Setup GDT 615 | // 616 | 617 | 618 | // Setup IDT 619 | // 620 | hresult = WhSiSetupInterruptDescriptorTable( Partition, registers ); 621 | if ( FAILED( hresult ) ) 622 | return hresult; 623 | 624 | /#1#/ Setup Syscall 625 | // 626 | hresult = WhSiSetupSyscalls( Partition, registers ); 627 | if ( FAILED( hresult ) ) 628 | return hresult; 629 | #1# 630 | 631 | return WhSeSetProcessorRegisters( Partition, registers );*/ 632 | } 633 | 634 | /** 635 | * @brief Translate guest virtual address to guest physical address 636 | * 637 | * @param Partition The VM partition 638 | * @param VirtualAddress The guest virtual address to be translated 639 | * @param PhysicalAddress The guest physical address backing the guest virtual 640 | * address 641 | * @param TranslationResult The translation result 642 | * @return A result code 643 | */ 644 | HRESULT WhSeTranslateGvaToGpa(whpx_state *Partition, uintptr_t VirtualAddress, 645 | uintptr_t *PhysicalAddress, 646 | WHV_TRANSLATE_GVA_RESULT *TranslationResult) 647 | { 648 | if (PhysicalAddress == nullptr) 649 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 650 | 651 | WHV_GUEST_PHYSICAL_ADDRESS gpa; 652 | WHV_TRANSLATE_GVA_RESULT translationResult{}; 653 | 654 | WHV_TRANSLATE_GVA_FLAGS flags = WHvTranslateGvaFlagValidateRead | 655 | WHvTranslateGvaFlagValidateWrite | 656 | WHvTranslateGvaFlagPrivilegeExempt; 657 | 658 | auto hresult = ::WHvTranslateGva( 659 | Partition->partition, Partition->VirtualProcessor.Index, VirtualAddress, 660 | flags, &translationResult, &gpa); 661 | if (FAILED(hresult)) 662 | return hresult; 663 | 664 | if (TranslationResult != nullptr) 665 | *TranslationResult = translationResult; 666 | 667 | *PhysicalAddress = gpa; 668 | 669 | return hresult; 670 | } 671 | -------------------------------------------------------------------------------- /unicorn-whpx-core/target/WinHvMemory.hpp: -------------------------------------------------------------------------------- 1 | #ifndef WINHVMEMORY_HPP 2 | #define WINHVMEMORY_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "WinHvDefs.hpp" 8 | 9 | #include 10 | 11 | // Allocate memory in guest physical address space (backed by host memory) 12 | // 13 | HRESULT WhSeAllocateGuestPhysicalMemory( whpx_state* Partition, uintptr_t* HostVa, uintptr_t* GuestPa, size_t Size, WHSE_MEMORY_ACCESS_FLAGS Flags ); 14 | 15 | // Map memory from host to guest physical address space (backed by host memory) 16 | // 17 | HRESULT WhSeMapHostToGuestPhysicalMemory( whpx_state* Partition, uintptr_t HostVa, uintptr_t* GuestPa, size_t Size, WHSE_MEMORY_ACCESS_FLAGS Flags ); 18 | 19 | // Allocate memory in guest virtual address space (backed by host memory) 20 | // 21 | HRESULT WhSeAllocateGuestVirtualMemory( whpx_state* Partition, uintptr_t* HostVa, uintptr_t* GuestVa, size_t Size, WHSE_MEMORY_ACCESS_FLAGS Flags ); 22 | 23 | // Map memory from host to guest virtual address space (backed by host memory) 24 | // 25 | //HRESULT WhSeMapHostToGuestVirtualMemory( whpx_state* Partition, uintptr_t HostVa, uintptr_t* GuestVa, size_t Size, WHSE_MEMORY_ACCESS_FLAGS Flags ); 26 | 27 | // Free memory in guest physical address space 28 | // 29 | HRESULT WhSeFreeGuestPhysicalMemory( whpx_state* Partition, uintptr_t HostVa, uintptr_t GuestPa, size_t Size ); 30 | 31 | // Free memory in guest virtual address space 32 | // 33 | HRESULT WhSeFreeGuestVirtualMemory( whpx_state* Partition, uintptr_t HostVa, uintptr_t GuestVa, size_t Size ); 34 | 35 | // Initialize paging and other stuff for the partition 36 | // 37 | //HRESULT WhSeInitializeMemoryLayout( whpx_state* Partition ); 38 | 39 | // Translate guest virtual address to guest physical address 40 | // 41 | HRESULT WhSeTranslateGvaToGpa( whpx_state* Partition, uintptr_t VirtualAddress, uintptr_t* PhysicalAddress, WHV_TRANSLATE_GVA_RESULT* TranslationResult ); 42 | 43 | #endif // !WINHVMEMORY_HPP 44 | -------------------------------------------------------------------------------- /unicorn-whpx-core/target/WinHvMemoryInternal.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "WinHvMemoryInternal.hpp" 3 | #include "WinHvMemory.hpp" 4 | #include "WinHvUtils.hpp" 5 | #include "winbase.h" 6 | #include "winerror.h" 7 | #include "winnt.h" 8 | #include 9 | #include "WinHvMemoryPrivate.hpp" 10 | #include "WinHvAllocationTracker.hpp" 11 | #include "WinHvHelpers.hpp" 12 | 13 | // Allocation strategy : 14 | // * Allocate by block 15 | // * To get base, find holes (due to deallocation) between nodes (need sorting 16 | // periodically) 17 | // * A hole : b1 base + size <-- n free bytes there --> b2 base + size 18 | // * If a requested allocation can fit in the free bytes (the hole) then pick a 19 | // block in this hole 20 | // * Else allocate at the tail (highest base + size) 21 | 22 | void FilterNodesByPhysicalAddress(PDLIST_HEADER Destination, 23 | PDLIST_HEADER Source) 24 | { 25 | if (Destination == NULL) 26 | return; 27 | 28 | if (Source == NULL) 29 | return; 30 | 31 | WHSE_ALLOCATION_NODE *head = (WHSE_ALLOCATION_NODE *)GetDListHead(Source); 32 | for (WHSE_ALLOCATION_NODE *current = head; current != NULL; 33 | current = (WHSE_ALLOCATION_NODE *)current->Next) { 34 | if (current->BlockType == MemoryBlockPhysical || 35 | current->BlockType == MemoryBlockVirtual) { 36 | WHSE_ALLOCATION_NODE *entry = 37 | (WHSE_ALLOCATION_NODE *)malloc(sizeof(WHSE_ALLOCATION_NODE)); 38 | if (entry == nullptr) 39 | return; 40 | 41 | memcpy(entry, current, sizeof(WHSE_ALLOCATION_NODE)); 42 | 43 | entry->Prev = nullptr; 44 | entry->Next = nullptr; 45 | 46 | PushBackDListEntry(Destination, entry); 47 | } 48 | } 49 | } 50 | 51 | int ComparePhysNode(const WHSE_ALLOCATION_NODE **a, 52 | const WHSE_ALLOCATION_NODE **b) 53 | { 54 | return static_cast((*a)->GuestPhysicalAddress - 55 | (*b)->GuestPhysicalAddress); 56 | } 57 | 58 | void SortNodesByPhysicalAddress(PDLIST_HEADER Header) 59 | { 60 | if (Header == NULL) 61 | return; 62 | 63 | SortDListBy(Header, (PDLIST_COMPARATOR)ComparePhysNode); 64 | } 65 | 66 | void FilterNodesByVirtualAddress(PDLIST_HEADER Destination, 67 | PDLIST_HEADER Source, uintptr_t Lowest, 68 | uintptr_t Highest) 69 | { 70 | if (Destination == NULL) 71 | return; 72 | 73 | if (Source == NULL) 74 | return; 75 | 76 | WHSE_ALLOCATION_NODE *head = (WHSE_ALLOCATION_NODE *)GetDListHead(Source); 77 | 78 | for (WHSE_ALLOCATION_NODE *current = head; current != NULL; 79 | current = (WHSE_ALLOCATION_NODE *)current->Next) { 80 | if (current->BlockType == MemoryBlockVirtual && 81 | current->GuestVirtualAddress >= Lowest && 82 | current->GuestVirtualAddress < Highest) { 83 | PDLIST_ENTRY entry = 84 | (PDLIST_ENTRY)malloc(sizeof(WHSE_ALLOCATION_NODE)); 85 | if (entry == nullptr) 86 | return; 87 | 88 | memcpy(entry, current, sizeof(WHSE_ALLOCATION_NODE)); 89 | 90 | entry->Prev = nullptr; 91 | entry->Next = nullptr; 92 | 93 | PushBackDListEntry(Destination, entry); 94 | } 95 | } 96 | } 97 | 98 | int CompareVirtNode(const WHSE_ALLOCATION_NODE **a, 99 | const WHSE_ALLOCATION_NODE **b) 100 | { 101 | return static_cast((*a)->GuestVirtualAddress - 102 | (*b)->GuestVirtualAddress); 103 | } 104 | 105 | void SortNodesByVirtualAddress(PDLIST_HEADER Header) 106 | { 107 | if (Header == NULL) 108 | return; 109 | 110 | SortDListBy(Header, (PDLIST_COMPARATOR)CompareVirtNode); 111 | } 112 | 113 | /** 114 | * @brief Break down a virtual address to paging indexes 115 | * 116 | * @param VirtualAddress The virtual address 117 | * @param Pml4Index The Page Map Level Four (PML4) index 118 | * @param PdpIndex The Page Directory Pointers index 119 | * @param PdIndex The Page Directory index 120 | * @param PtIndex The Page Table index 121 | * @param Offset The physical page offset 122 | * @return A result code 123 | */ 124 | HRESULT WhSiDecomposeVirtualAddress(uintptr_t VirtualAddress, 125 | uint16_t *Pml4Index, uint16_t *PdpIndex, 126 | uint16_t *PdIndex, uint16_t *PtIndex, 127 | uint16_t *Offset) 128 | { 129 | 130 | *Offset = VirtualAddress & 0xFFF; 131 | *PtIndex = (VirtualAddress >> 12) & 0x1FF; 132 | *PdIndex = (VirtualAddress >> (12 + 9)) & 0x1FF; 133 | *PdpIndex = (VirtualAddress >> (12 + 9 * 2)) & 0x1FF; 134 | *Pml4Index = (VirtualAddress >> (12 + 9 * 3)) & 0x1FF; 135 | 136 | return S_OK; 137 | } 138 | 139 | /** 140 | * @brief Suggest a physical address depending on allocation size 141 | * 142 | * @param Partition The VM partition 143 | * @param Size The allocation size 144 | * @param PhysicalPageAddress The returned guest physical address 145 | * @return A result code 146 | */ 147 | HRESULT WhSiSuggestPhysicalAddress(whpx_state *Partition, size_t Size, 148 | uintptr_t *PhysicalPageAddress) 149 | { 150 | if (Partition == nullptr) 151 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 152 | 153 | if (PhysicalPageAddress == nullptr) 154 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 155 | 156 | if (*PhysicalPageAddress != 0) 157 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 158 | 159 | auto arena = &(Partition->MemoryLayout.MemoryArena); 160 | 161 | uintptr_t lowest = arena->PhysicalAddressSpace.LowestAddress; 162 | uintptr_t highest = arena->PhysicalAddressSpace.HighestAddress; 163 | 164 | uintptr_t suggestedAddress = 0; 165 | 166 | // Initialize temporary list 167 | // 168 | DLIST_HEADER physicalNodesList; 169 | memset(&physicalNodesList, 0, sizeof(DLIST_HEADER)); 170 | InitializeDListHeader(&physicalNodesList); 171 | 172 | // Build the filtered list 173 | // 174 | FilterNodesByPhysicalAddress(&physicalNodesList, 175 | &(arena->AllocatedMemoryBlocks)); 176 | 177 | // Sort the filtered list 178 | // 179 | SortNodesByPhysicalAddress(&physicalNodesList); 180 | // SortNodesByPhysicalAddress( &( arena->AllocatedMemoryBlocks ) ); 181 | 182 | // Iterate nodes from head to the one before the tail 183 | // 184 | WHSE_ALLOCATION_NODE *head = 185 | (WHSE_ALLOCATION_NODE *)GetDListHead(&physicalNodesList); 186 | // WHSE_ALLOCATION_NODE* head = ( WHSE_ALLOCATION_NODE* ) GetDListHead( &( 187 | // arena->AllocatedMemoryBlocks ) ); 188 | if (head != NULL) { 189 | WHSE_ALLOCATION_NODE *current = NULL; 190 | for (current = head; current->Next != NULL; 191 | current = (WHSE_ALLOCATION_NODE *)current->Next) { 192 | WHSE_ALLOCATION_NODE *next = (WHSE_ALLOCATION_NODE *)current->Next; 193 | 194 | if (ALIGN_UP( 195 | ALIGN_UP(current->GuestPhysicalAddress + current->Size) + 196 | Size) < next->GuestPhysicalAddress) { 197 | suggestedAddress = 198 | ALIGN_UP(current->GuestPhysicalAddress + current->Size); 199 | break; 200 | } 201 | } 202 | 203 | if (suggestedAddress == 0 && 204 | current == 205 | (WHSE_ALLOCATION_NODE *)GetDListTail(&physicalNodesList)) { 206 | // if ( suggestedAddress == 0 && current == ( WHSE_ALLOCATION_NODE* 207 | // ) GetDListTail( &( arena->AllocatedMemoryBlocks ) ) ) { 208 | // Suggest address at the tail 209 | // 210 | suggestedAddress = 211 | ALIGN_UP(current->GuestPhysicalAddress + current->Size); 212 | } 213 | } else { 214 | suggestedAddress = lowest; 215 | } 216 | 217 | FlushDList(&physicalNodesList); 218 | 219 | if (suggestedAddress < lowest || suggestedAddress >= (highest - Size)) 220 | return HRESULT_FROM_WIN32(ERROR_NOT_FOUND); 221 | 222 | *PhysicalPageAddress = suggestedAddress; 223 | 224 | return S_OK; 225 | } 226 | 227 | /** 228 | * @brief Suggest a virtual address depending on allocation size 229 | * 230 | * @param Partition The VM partition 231 | * @param Size The allocation size 232 | * @param VirtualAddress The returned guest physical address 233 | * @return A result code 234 | */ 235 | HRESULT WhSiSuggestVirtualAddress(whpx_state *Partition, size_t Size, 236 | uintptr_t *VirtualAddress, 237 | WHSE_PROCESSOR_MODE Mode) 238 | { 239 | if (Partition == nullptr) 240 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 241 | 242 | if (VirtualAddress == nullptr) 243 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 244 | 245 | if (*VirtualAddress != 0) 246 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 247 | 248 | auto arena = &(Partition->MemoryLayout.MemoryArena); 249 | 250 | uintptr_t lowest = 0; 251 | uintptr_t highest = 0; 252 | 253 | if (Mode == WHSE_PROCESSOR_MODE::KernelMode) { 254 | lowest = arena->VirtualAddressSpace.SystemSpace.LowestAddress; 255 | highest = arena->VirtualAddressSpace.SystemSpace.HighestAddress; 256 | } else if (Mode == WHSE_PROCESSOR_MODE::UserMode) { 257 | lowest = arena->VirtualAddressSpace.UserSpace.LowestAddress; 258 | highest = arena->VirtualAddressSpace.UserSpace.HighestAddress; 259 | } else 260 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 261 | 262 | uintptr_t suggestedAddress = 0; 263 | 264 | // Initialize temporary list 265 | // 266 | DLIST_HEADER virtualNodesList; 267 | memset(&virtualNodesList, 0, sizeof(DLIST_HEADER)); 268 | InitializeDListHeader(&virtualNodesList); 269 | 270 | // Build the filtered list 271 | // 272 | FilterNodesByVirtualAddress( 273 | &virtualNodesList, &(arena->AllocatedMemoryBlocks), lowest, highest); 274 | 275 | // Sort the filtered list 276 | // 277 | SortNodesByVirtualAddress(&virtualNodesList); 278 | 279 | // Iterate nodes from head to the one before the tail 280 | // 281 | WHSE_ALLOCATION_NODE *head = 282 | (WHSE_ALLOCATION_NODE *)GetDListHead(&virtualNodesList); 283 | if (head != NULL) { 284 | WHSE_ALLOCATION_NODE *current = NULL; 285 | for (current = head; current->Next != NULL; 286 | current = (WHSE_ALLOCATION_NODE *)current->Next) { 287 | WHSE_ALLOCATION_NODE *next = (WHSE_ALLOCATION_NODE *)current->Next; 288 | 289 | if (ALIGN_UP( 290 | ALIGN_UP(current->GuestVirtualAddress + current->Size) + 291 | Size) < next->GuestVirtualAddress) { 292 | suggestedAddress = 293 | ALIGN_UP(current->GuestVirtualAddress + current->Size); 294 | break; 295 | } 296 | } 297 | 298 | if (suggestedAddress == 0 && 299 | current == 300 | (WHSE_ALLOCATION_NODE *)GetDListTail(&virtualNodesList)) { 301 | // Suggest address at the tail 302 | // 303 | suggestedAddress = 304 | ALIGN_UP(current->GuestVirtualAddress + current->Size); 305 | } 306 | } else { 307 | suggestedAddress = lowest; 308 | } 309 | 310 | FlushDList(&virtualNodesList); 311 | 312 | if (suggestedAddress < lowest || suggestedAddress >= (highest - Size)) 313 | return HRESULT_FROM_WIN32(ERROR_NOT_FOUND); 314 | 315 | *VirtualAddress = suggestedAddress; 316 | 317 | return S_OK; 318 | } 319 | 320 | /** 321 | * @brief Internal function to setup paging 322 | * 323 | * @param Partition The VM partition 324 | * @param Pml4PhysicalAddress 325 | * @return A result code 326 | */ 327 | HRESULT WhSiSetupPaging(whpx_state *Partition, uintptr_t *Pml4PhysicalAddress) 328 | { 329 | // Check if already initialized 330 | // 331 | if (Partition->MemoryLayout.Pml4HostVa != 0) 332 | return HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED); 333 | 334 | // Allocate PML4 on physical memory 335 | // 336 | uintptr_t pml4Gpa = 0; 337 | uintptr_t pml4Hva = 0; 338 | auto hresult = WhSeAllocateGuestPhysicalMemory( 339 | Partition, &pml4Hva, &pml4Gpa, PAGE_SIZE, 340 | WHvMapGpaRangeFlagRead | WHvMapGpaRangeFlagWrite); 341 | if (FAILED(hresult)) 342 | return hresult; 343 | 344 | Partition->MemoryLayout.Pml4HostVa = pml4Hva; 345 | 346 | auto pml4 = reinterpret_cast(pml4Hva); 347 | 348 | for (auto i = 0; i < 512; i++) { 349 | // Allocate a Page Directory Pointers page 350 | // The memory is zeroed so every PDP entries will have the Valid 351 | // (Present) bit set to 0 352 | // 353 | hresult = WhSpInsertPageTableEntry(Partition, pml4, i); 354 | if (FAILED(hresult)) 355 | return hresult; 356 | } 357 | 358 | *Pml4PhysicalAddress = pml4Gpa; 359 | 360 | return hresult; 361 | } 362 | 363 | /** 364 | * @brief Internal function to insert page table in the paging directory 365 | * 366 | * Internal function to insert page table in the paging directory 367 | * Allocate PML4 entry, PDP entry, PD entry and PT entry 368 | * 369 | * @param Partition The VM partition 370 | * @param VirtualAddress 371 | * @param PhysicalAddress 372 | * @return A result code 373 | */ 374 | HRESULT WhSiInsertPageTableEntry(whpx_state *Partition, 375 | uintptr_t VirtualAddress, 376 | uintptr_t PhysicalAddress) 377 | { 378 | // "Explode" the VA into translation indexes 379 | // 380 | uint16_t pml4Idx; 381 | uint16_t pdpIdx; 382 | uint16_t pdIdx; 383 | uint16_t ptIdx; 384 | uint16_t phyOffset; 385 | 386 | auto hresult = WhSiDecomposeVirtualAddress( 387 | VirtualAddress, &pml4Idx, &pdpIdx, &pdIdx, &ptIdx, &phyOffset); 388 | if (FAILED(hresult)) 389 | return hresult; 390 | 391 | // Search entry in PML4 392 | // 393 | auto pml4e = reinterpret_cast( 394 | Partition->MemoryLayout.Pml4HostVa)[pml4Idx]; 395 | if (pml4e.Valid == FALSE) { 396 | // Shouldn't happen as we initialized all PLM4 entries upfront 397 | // 398 | DebugBreak(); 399 | return HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR); 400 | } 401 | 402 | // Search entry in Page Directory Pointers 403 | // 404 | uintptr_t pdpHva = 0; 405 | hresult = WhSpLookupHVAFromPFN(Partition, pml4e.PageFrameNumber, &pdpHva); 406 | if (FAILED(hresult)) 407 | return hresult; 408 | 409 | auto pdp = reinterpret_cast(pdpHva); 410 | auto pdpe = pdp[pdpIdx]; 411 | if (pdpe.Valid == FALSE) { 412 | // Allocate a Page Directory page 413 | // 414 | hresult = WhSpInsertPageTableEntry(Partition, pdp, pdpIdx); 415 | 416 | if (FAILED(hresult)) 417 | return hresult; 418 | 419 | pdpe = pdp[pdpIdx]; 420 | } 421 | 422 | // Search entry in Page Directories 423 | // 424 | uintptr_t pdHva = 0; 425 | hresult = WhSpLookupHVAFromPFN(Partition, pdpe.PageFrameNumber, &pdHva); 426 | if (FAILED(hresult)) 427 | return hresult; 428 | 429 | auto pd = reinterpret_cast(pdHva); 430 | auto pde = pd[pdIdx]; 431 | if (pde.Valid == FALSE) { 432 | // Allocate a Page Table page 433 | // 434 | hresult = WhSpInsertPageTableEntry(Partition, pd, pdIdx); 435 | 436 | if (FAILED(hresult)) 437 | return hresult; 438 | 439 | pde = pd[pdIdx]; 440 | } 441 | 442 | // Add entry in Page Tables 443 | // 444 | uintptr_t ptHva = 0; 445 | hresult = WhSpLookupHVAFromPFN(Partition, pde.PageFrameNumber, &ptHva); 446 | if (FAILED(hresult)) 447 | return hresult; 448 | 449 | auto pt = reinterpret_cast(ptHva); 450 | auto ppte = &pt[ptIdx]; 451 | if (ppte->Valid == FALSE) { 452 | /*PWHSE_ALLOCATION_NODE found = nullptr; 453 | hresult = WhSeFindAllocationNodeByGpa( Partition, PhysicalAddress, 454 | &found ); if ( hresult != HRESULT_FROM_WIN32( ERROR_NOT_FOUND ) && 455 | FAILED( hresult ) ) return hresult; 456 | 457 | if ( found != nullptr ) 458 | return HRESULT_FROM_WIN32( ERROR_INTERNAL_ERROR );*/ 459 | 460 | // Create a valid PTE 461 | // 462 | MMPTE_HARDWARE pte{}; 463 | 464 | pte.AsUlonglong = 0; // Ensure zeroed 465 | pte.Valid = 1; // Intel's Present bit 466 | pte.Write = 1; // Intel's Read/Write bit 467 | pte.Owner = 1; // Intel's User/Supervisor bit, let's say it is a user 468 | // accessible frame 469 | pte.PageFrameNumber = 470 | (PhysicalAddress / PAGE_SIZE); // Physical address of PDP page 471 | 472 | *ppte = pte; 473 | 474 | WHSE_ALLOCATION_NODE node{.BlockType = 475 | MEMORY_BLOCK_TYPE::MemoryBlockPte, 476 | .HostVirtualAddress = 0, 477 | .GuestPhysicalAddress = PhysicalAddress, 478 | .GuestVirtualAddress = 0, 479 | .Size = PAGE_SIZE}; 480 | 481 | hresult = WhSeInsertAllocationTrackingNode(Partition, node); 482 | if (FAILED(hresult)) 483 | return hresult; 484 | } 485 | 486 | return S_OK; 487 | } 488 | 489 | /** 490 | * @brief Find a suitable Guest VA 491 | * 492 | * @param Partition The VM partition 493 | * @param GuestVa 494 | * @param Size 495 | * @return A result code 496 | */ 497 | HRESULT WhSiFindBestGVA(whpx_state *Partition, uintptr_t *GuestVa, size_t Size) 498 | { 499 | if (Partition == nullptr) 500 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 501 | 502 | if (GuestVa == nullptr) 503 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 504 | 505 | auto tracker = &(Partition->MemoryLayout.MemoryArena.AllocatedMemoryBlocks); 506 | 507 | auto first = 508 | reinterpret_cast(GetDListHead(tracker)); 509 | if (first == nullptr) 510 | return HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS); 511 | 512 | uintptr_t highestExistingGva = 0; 513 | size_t highestExistingGvaSize = 0; 514 | auto current = first; 515 | while (current != nullptr) { 516 | uintptr_t currentGva = current->GuestVirtualAddress; 517 | if (currentGva > highestExistingGva) { 518 | highestExistingGva = currentGva; 519 | } 520 | 521 | current = reinterpret_cast(current->Next); 522 | } 523 | 524 | if (current == nullptr) 525 | return HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS); 526 | 527 | auto va = ALIGN_UP(highestExistingGva + highestExistingGvaSize); 528 | 529 | *GuestVa = va; 530 | 531 | return S_OK; 532 | } 533 | 534 | constexpr uint8_t MAKE_IDT_ATTRS(uint8_t Dpl, uint8_t GateType) 535 | { 536 | return ((1 << 7) // Present bit 537 | | ((Dpl << 6) & 0b11) | (0 << 4) // Reserved bit 538 | | (GateType & 0b1111)); 539 | } 540 | 541 | HRESULT WhSpSwitchProcessor(WHSE_PROCESSOR_MODE Mode) 542 | { 543 | 544 | int ring; 545 | int codeSelector; 546 | int dataSelector; 547 | 548 | switch (Mode) { 549 | using enum WHSE_PROCESSOR_MODE; 550 | case UserMode: 551 | ring = 3; 552 | codeSelector = 0x18; 553 | dataSelector = 0x20; 554 | break; 555 | case KernelMode: 556 | ring = 0; 557 | codeSelector = 0x08; 558 | dataSelector = 0x10; 559 | break; 560 | default: 561 | return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); 562 | } 563 | 564 | WHV_REGISTER_VALUE CsReg = {0}; 565 | WHV_REGISTER_VALUE SsReg = {0}; 566 | // Setup segment registers 567 | whpx_get_reg_value(WHvX64RegisterCs, &CsReg); 568 | whpx_get_reg_value(WHvX64RegisterSs, &SsReg); 569 | CsReg.Segment.Selector = (codeSelector | ring); 570 | CsReg.Segment.DescriptorPrivilegeLevel = ring; 571 | CsReg.Segment.Long = 1; 572 | 573 | SsReg.Segment.Selector = (dataSelector | ring); 574 | SsReg.Segment.DescriptorPrivilegeLevel = ring; 575 | SsReg.Segment.Default = 1; 576 | SsReg.Segment.Granularity = 1; 577 | 578 | printf("Cs Selector:%08x,DescriptorPrivilegeLevel:%08x,Long:%08x\r\n", 579 | CsReg.Segment.Selector, CsReg.Segment.DescriptorPrivilegeLevel, 580 | CsReg.Segment.Long); 581 | printf("Ss " 582 | "Selector:%08x,DescriptorPrivilegeLevel:%08x,Default:%08x," 583 | "Granularity:%08x\r\n", 584 | SsReg.Segment.Selector, SsReg.Segment.DescriptorPrivilegeLevel, 585 | SsReg.Segment.Default, SsReg.Segment.Granularity); 586 | 587 | // 初始化 CS 寄存器(代码段寄存器) 588 | /*WHV_REGISTER_NAME name = WHvX64RegisterCs; 589 | WHV_REGISTER_VALUE value = {}; 590 | value.Segment.Base = 0x00000000; 591 | value.Segment.Limit = 0x0000FFFF; 592 | value.Segment.Selector = 0x0000; 593 | value.Segment.Attributes = 594 | 0x009B; // 代码段的属性,详情可以参考该字段的定义和 Intel 手册*/ 595 | 596 | // whpx_set_reg_value(WHvX64RegisterCs, value); 597 | //return S_OK; 598 | whpx_set_reg_value(WHvX64RegisterCs, CsReg); 599 | whpx_set_reg_value(WHvX64RegisterSs, SsReg); 600 | whpx_set_reg_value(WHvX64RegisterDs, SsReg); 601 | whpx_set_reg_value(WHvX64RegisterEs, SsReg); 602 | // whpx_set_reg_value(WHvX64RegisterFs, SsReg); 603 | whpx_set_reg_value(WHvX64RegisterGs, SsReg); 604 | 605 | return S_OK; 606 | } 607 | 608 | /** @brief Setup GDT 609 | * 610 | * @param Partition The VM partition 611 | * @param Registers 612 | * @return A result code 613 | #2#*/ 614 | HRESULT WhSiSetupGlobalDescriptorTable(whpx_state *Partition) 615 | { 616 | if (Partition == nullptr) 617 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 618 | 619 | // Allocate GDT 620 | // 621 | uintptr_t gdtHva = 0; 622 | // uintptr_t gdtGva = 0xfffff800'00000000; 623 | uintptr_t gdtGva = 0; 624 | auto hresult = WhSeAllocateGuestVirtualMemory( 625 | Partition, &gdtHva, &gdtGva, PAGE_SIZE, 626 | WHvMapGpaRangeFlagRead | WHvMapGpaRangeFlagWrite); 627 | if (FAILED(hresult)) 628 | return hresult; 629 | 630 | // Create the descriptors 631 | // 632 | uintptr_t base = 0; 633 | ptrdiff_t limit = 0xfffff; 634 | 635 | GDT_ENTRY nullDesc{0}; 636 | hresult = WhSpCreateGdtEntry(&nullDesc, 0, 0, 0x00, 0x0); 637 | if (FAILED(hresult)) 638 | return hresult; 639 | 640 | GDT_ENTRY kernelModeCodeSegmentDesc{0}; 641 | hresult = 642 | WhSpCreateGdtEntry(&kernelModeCodeSegmentDesc, base, limit, 0x9a, 0xa); 643 | if (FAILED(hresult)) 644 | return hresult; 645 | 646 | GDT_ENTRY kernelModeDataSegmentDesc{0}; 647 | hresult = 648 | WhSpCreateGdtEntry(&kernelModeDataSegmentDesc, base, limit, 0x92, 0xc); 649 | if (FAILED(hresult)) 650 | return hresult; 651 | 652 | GDT_ENTRY userModeCodeSegmentDesc{0}; 653 | hresult = 654 | WhSpCreateGdtEntry(&userModeCodeSegmentDesc, base, limit, 0xfa, 0xa); 655 | if (FAILED(hresult)) 656 | return hresult; 657 | 658 | GDT_ENTRY userModeDataSegmentDesc{0}; 659 | hresult = 660 | WhSpCreateGdtEntry(&userModeDataSegmentDesc, base, limit, 0xf2, 0xc); 661 | if (FAILED(hresult)) 662 | return hresult; 663 | 664 | // Allocate a page for the TSS 665 | // 666 | uintptr_t tssHva = 0; 667 | // uintptr_t tssGva = 0xfffff800'00001000; 668 | uintptr_t tssGva = 0; 669 | hresult = WhSeAllocateGuestVirtualMemory( 670 | Partition, &tssHva, &tssGva, sizeof(X64_TASK_STATE_SEGMENT), 671 | WHvMapGpaRangeFlagRead | WHvMapGpaRangeFlagWrite); 672 | if (FAILED(hresult)) 673 | return hresult; 674 | 675 | hresult = WhSpInitializeTss( 676 | Partition, reinterpret_cast(tssHva)); 677 | if (FAILED(hresult)) 678 | return hresult; 679 | 680 | X64_TSS_ENTRY tssSegmentDesc{0}; 681 | hresult = WhSpCreateTssEntry(&tssSegmentDesc, tssGva, 682 | sizeof(X64_TASK_STATE_SEGMENT) - 1, 0x89, 0); 683 | if (FAILED(hresult)) 684 | return hresult; 685 | 686 | // Load the temp descriptors in memory 687 | // 688 | PGDT_ENTRY gdt = reinterpret_cast(gdtHva); 689 | 690 | // Offset : 0x0000 Use : Null Descriptor 691 | // 692 | gdt[0] = nullDesc; 693 | 694 | // Offset : 0x0008 Use : Kernel Mode Code Segment 695 | // 696 | gdt[1] = kernelModeCodeSegmentDesc; 697 | 698 | // Offset : 0x0010 Use : Kernel Mode Data Segment 699 | // 700 | gdt[2] = kernelModeDataSegmentDesc; 701 | 702 | // Offset : 0x0018 Use : User Mode Code Segment 703 | // 704 | gdt[3] = userModeCodeSegmentDesc; 705 | 706 | // Offset : 0x0020 Use : User Mode Data Segment 707 | // 708 | gdt[4] = userModeDataSegmentDesc; 709 | 710 | // Offset : 0x0028 Use : 64-bit Task State Segment 711 | // 712 | *reinterpret_cast(&(gdt[5])) = tssSegmentDesc; 713 | 714 | WHV_REGISTER_VALUE GdtrReg = {0}; 715 | 716 | whpx_get_reg_value(WHvX64RegisterGdtr, &GdtrReg); 717 | 718 | // Load GDTR 719 | // 720 | GdtrReg.Table.Base = gdtGva; 721 | GdtrReg.Table.Limit = static_cast( 722 | (sizeof(X64_TSS_ENTRY) + 723 | (sizeof(GDT_ENTRY) * NUMBER_OF_GDT_DESCRIPTORS)) - 724 | 1); 725 | whpx_set_reg_value(WHvX64RegisterGdtr, GdtrReg); 726 | // Load TR 727 | // 728 | printf("Gdtr Selector:%08x,Base:%016llx,Limit:%08x\r\n", 729 | GdtrReg.Segment.Selector, GdtrReg.Table.Base, GdtrReg.Table.Limit); 730 | 731 | 732 | /*WHV_REGISTER_VALUE TrReg = {0}; 733 | whpx_get_reg_value(WHvX64RegisterTr, &TrReg); 734 | TrReg.Segment.Selector = 0x0028; 735 | //TrReg.Segment.Selector = 0; 736 | whpx_set_reg_value(WHvX64RegisterTr, TrReg);*/ 737 | // Registers[ Tr ].Segment.Base = Registers[ Gdtr ].Table.Base; 738 | // Registers[ Tr ].Segment.Limit = Registers[ Gdtr ].Table.Limit; 739 | 740 | Partition->VirtualProcessor.Gdt = reinterpret_cast(gdtHva); 741 | Partition->VirtualProcessor.Tss = 742 | reinterpret_cast(tssHva); 743 | 744 | return S_OK; 745 | } 746 | 747 | /** 748 | * @brief Setup IDT 749 | * 750 | * @param Partition The VM partition 751 | * @param Registers 752 | * @return A result code 753 | */ 754 | HRESULT WhSiSetupInterruptDescriptorTable(whpx_state *Partition) 755 | { 756 | if (Partition == nullptr) { 757 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 758 | } 759 | 760 | // Allocate two pages, one for the IDT 761 | // 762 | uintptr_t idtHva = 0; 763 | // uintptr_t idtGva = 0xfffff800'00002000; 764 | uintptr_t idtGva = 0; 765 | auto hresult = WhSeAllocateGuestVirtualMemory( 766 | Partition, &idtHva, &idtGva, PAGE_SIZE, 767 | WHvMapGpaRangeFlagRead | WHvMapGpaRangeFlagWrite); 768 | if (FAILED(hresult)) 769 | return hresult; 770 | 771 | // The other one to trap ISR access 772 | // 773 | uintptr_t idtTrapHva = 0; 774 | // uintptr_t idtTrapGva = 0xfffff800'00003000; 775 | uintptr_t idtTrapGva = 0; 776 | hresult = WhSeAllocateGuestVirtualMemory( 777 | Partition, &idtTrapHva, &idtTrapGva, PAGE_SIZE, 778 | WHvMapGpaRangeFlagRead | WHvMapGpaRangeFlagWrite); 779 | if (FAILED(hresult)) 780 | return hresult; 781 | 782 | // Unmap the 2nd page immediately, keeping paging information butreleasing 783 | // the backing memory, thus it will generate a memory access exception when 784 | // trying to jumpto the Interrupt Service Routine 785 | // 786 | hresult = WhSeFreeGuestVirtualMemory(Partition, idtTrapHva, idtTrapGva, 787 | PAGE_SIZE); 788 | if (FAILED(hresult)) 789 | return hresult; 790 | 791 | // Fill IDT 792 | // 793 | auto ptr = idtTrapGva; 794 | auto idt = reinterpret_cast(idtHva); 795 | for (auto idx = 0; idx < NUMBER_OF_IDT_DESCRIPTORS; idx++) { 796 | auto entry = IDT_ENTRY{ 797 | .Low = static_cast(ptr & UINT16_MAX), 798 | .Selector = 799 | 0x0008, // Kernel CS index .InterruptStackTable = 0, .Attributes 800 | .Attributes = MAKE_IDT_ATTRS(0b00, 0b1110), 801 | .Mid = static_cast((ptr >> 16) & UINT16_MAX), 802 | .High = static_cast((ptr >> 32) & UINT32_MAX), 803 | .Reserved = 0}; 804 | 805 | idt[idx] = entry; 806 | ptr += sizeof(decltype(ptr)); 807 | } 808 | WHV_REGISTER_VALUE IdtrReg = {0}; 809 | whpx_get_reg_value(WHvX64RegisterIdtr, &IdtrReg); 810 | // Load IDTR 811 | // 812 | IdtrReg.Table.Base = idtGva; 813 | IdtrReg.Table.Limit = static_cast( 814 | (sizeof(IDT_ENTRY) * NUMBER_OF_IDT_DESCRIPTORS) - 1); 815 | 816 | Partition->MemoryLayout.InterruptDescriptorTableVirtualAddress = idtTrapGva; 817 | whpx_set_reg_value(WHvX64RegisterIdtr, IdtrReg); 818 | return S_OK; 819 | } 820 | 821 | /** 822 | * @brief Setup memory arena 823 | * 824 | * @param Partition The VM partition 825 | * @return A result code 826 | */ 827 | HRESULT WhSiInitializeMemoryArena(whpx_state *Partition) 828 | { 829 | if (Partition == nullptr) 830 | return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); 831 | 832 | auto arena = &(Partition->MemoryLayout.MemoryArena); 833 | 834 | // Get available physical memory 835 | // 836 | uint64_t totalMemInKib = 0; 837 | if (::GetPhysicallyInstalledSystemMemory(&totalMemInKib) == FALSE && 838 | totalMemInKib == 0) 839 | return WhSeGetLastHresult(); 840 | 841 | // Initialize Physical Address Space (PAS) 842 | // 843 | arena->PhysicalAddressSpace.LowestAddress = 0x00000000'00000000; 844 | arena->PhysicalAddressSpace.HighestAddress = (totalMemInKib << 10) - 1; 845 | arena->PhysicalAddressSpace.Size = totalMemInKib << 10; 846 | 847 | // Initialize Virtual Address Space (VAS) 848 | // 849 | uintptr_t lowestUserVa = 0x00000000'00000000; 850 | uintptr_t highestUserVa = 0x00007fff'ffff0000; 851 | arena->VirtualAddressSpace.UserSpace.LowestAddress = lowestUserVa; 852 | arena->VirtualAddressSpace.UserSpace.HighestAddress = highestUserVa; 853 | arena->VirtualAddressSpace.UserSpace.Size = 854 | (highestUserVa - lowestUserVa) - 1; 855 | 856 | uintptr_t lowestSystemVa = 0xffff8000'00000000; 857 | uintptr_t highestSystemVa = 0xffffffff'ffff0000; 858 | arena->VirtualAddressSpace.SystemSpace.LowestAddress = lowestSystemVa; 859 | arena->VirtualAddressSpace.SystemSpace.HighestAddress = highestSystemVa; 860 | arena->VirtualAddressSpace.SystemSpace.Size = 861 | (highestSystemVa - lowestSystemVa) - 1; 862 | 863 | // Initialize the doubly linked list that will maintain 864 | // our allocated memory regions 865 | // 866 | InitializeDListHeader(&(arena->AllocatedMemoryBlocks)); 867 | 868 | return S_OK; 869 | } 870 | 871 | /* 872 | /** 873 | * @brief Setup syscalls 874 | * 875 | * @param Partition The VM partition 876 | * @param VirtualAddress The guest virtual address to be translated 877 | * @param PhysicalAddress The guest physical address backing the guest virtual 878 | address 879 | * @param TranslationResult The translation result 880 | * @return A result code 881 | #1# 882 | HRESULT WhSiSetupSyscalls( whpx_state* Partition, WHSE_REGISTERS Registers ) { 883 | if ( Partition == nullptr ) 884 | return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); 885 | 886 | if ( Registers == nullptr ) 887 | return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); 888 | 889 | uintptr_t syscallTrapHva = 0; 890 | auto hresult = WhSiSuggestVirtualAddress( Partition, PAGE_SIZE, 891 | &syscallTrapHva, WHSE_PROCESSOR_MODE::KernelMode ); if ( FAILED( hresult ) ) 892 | return hresult; 893 | 894 | WHSE_ALLOCATION_NODE node { 895 | .BlockType = MEMORY_BLOCK_TYPE::MemoryBlockVirtual, 896 | .HostVirtualAddress = 0, 897 | .GuestPhysicalAddress = 0, 898 | .GuestVirtualAddress = syscallTrapHva, 899 | .Size = PAGE_SIZE 900 | }; 901 | 902 | hresult = WhSeInsertAllocationTrackingNode( Partition, node ); 903 | if ( FAILED( hresult ) ) 904 | return hresult; 905 | 906 | auto vp = &Partition->VirtualProcessor; 907 | 908 | vp->SyscallData.Eip = 0; // Must be ZERO 909 | vp->SyscallData.LongModeRip = syscallTrapHva; 910 | vp->SyscallData.CompModeRip = syscallTrapHva + sizeof( uintptr_t ); 911 | 912 | Registers[ Star ].Reg64 = 913 | // base selector for SYSRET CS / SS : bits 63:48 914 | // 915 | ( static_cast< uint64_t >( 0x0018 ) /*R3 CS Selector#1# << 48 ) 916 | // base selector for SYSCALL CS / SS : bits 47:32 917 | // 918 | | ( static_cast< uint64_t >( 0x0008 ) /*R0 CS Selector#1# << 32 919 | ) 920 | // target EIP : bits 31:0 921 | // This field is reserved in Long Mode 922 | // 923 | | vp->SyscallData.Eip; 924 | Registers[ Lstar ].Reg64 = vp->SyscallData.LongModeRip; 925 | Registers[ Cstar ].Reg64 = vp->SyscallData.CompModeRip; 926 | //Registers[ Sfmask ].Reg64 = 0; 927 | 928 | return S_OK; 929 | } 930 | */ 931 | -------------------------------------------------------------------------------- /unicorn-whpx-core/target/WinHvMemoryInternal.hpp: -------------------------------------------------------------------------------- 1 | #ifndef WINHVMEMORYINTERNAL_HPP 2 | #define WINHVMEMORYINTERNAL_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "WinHvDefs.hpp" 8 | 9 | #include 10 | 11 | #pragma pack( push, 1 ) 12 | typedef struct _MMPTE_HARDWARE 13 | { 14 | union 15 | { 16 | struct 17 | { 18 | UINT64 Valid : 1; 19 | UINT64 Write : 1; 20 | UINT64 Owner : 1; 21 | UINT64 WriteThrough : 1; 22 | UINT64 CacheDisable : 1; 23 | UINT64 Accessed : 1; 24 | UINT64 Dirty : 1; 25 | UINT64 LargePage : 1; 26 | UINT64 Available : 4; 27 | UINT64 PageFrameNumber : 36; 28 | UINT64 ReservedForHardware : 4; 29 | UINT64 ReservedForSoftware : 11; 30 | UINT64 NoExecute : 1; 31 | }; 32 | UINT64 AsUlonglong; 33 | }; 34 | } MMPTE_HARDWARE, * PMMPTE_HARDWARE; 35 | #pragma pack( pop ) 36 | 37 | //static_assert( sizeof( MMPTE_HARDWARE ) == 8 ); 38 | constexpr static size_t NUMBER_OF_IDT_DESCRIPTORS = 256; 39 | 40 | 41 | #pragma pack( push, 1 ) 42 | typedef struct _IDT_ENTRY { 43 | uint16_t Low; 44 | uint16_t Selector; 45 | uint8_t InterruptStackTable; 46 | uint8_t Attributes; 47 | uint16_t Mid; 48 | uint32_t High; 49 | uint32_t Reserved; 50 | } IDT_ENTRY, * PIDT_ENTRY; 51 | #pragma pack( pop ) 52 | 53 | //static_assert( sizeof( IDT_ENTRY ) == 16 ); 54 | 55 | 56 | 57 | #pragma pack(push, 1) 58 | typedef struct _GDT_ENTRY { 59 | uint16_t LimitLow; 60 | uint16_t BaseLow; 61 | uint8_t BaseMid; 62 | uint8_t Access; 63 | uint8_t LimitHigh : 4; 64 | uint8_t Flags : 4; 65 | uint8_t BaseHigh; 66 | } GDT_ENTRY, *PGDT_ENTRY; 67 | #pragma pack(pop) 68 | 69 | #pragma pack( push, 1 ) 70 | struct _X64_TSS_ENTRY { 71 | struct _GDT_ENTRY GdtEntry; 72 | uint32_t BaseHigh; 73 | uint32_t Reserved; 74 | }; 75 | #pragma pack( pop ) 76 | 77 | 78 | 79 | //static_assert( sizeof( _X64_TSS_ENTRY ) == 0x10 ); 80 | 81 | typedef struct _X64_TSS_ENTRY X64_TSS_ENTRY; 82 | typedef struct _X64_TSS_ENTRY * PX64_TSS_ENTRY; 83 | 84 | // Decompose a virtual address into paging indexes 85 | // 86 | HRESULT WhSiDecomposeVirtualAddress( uintptr_t VirtualAddress, uint16_t* Pml4Index, uint16_t* PdpIndex, uint16_t* PdIndex, uint16_t* PtIndex, uint16_t* Offset ); 87 | 88 | // Suggest a physical address depending on allocation size 89 | // 90 | HRESULT WhSiSuggestPhysicalAddress( whpx_state* Partition, size_t Size, uintptr_t* PhysicalPageAddress ); 91 | 92 | // Suggest a virtual address depending on allocation size 93 | // 94 | HRESULT WhSiSuggestVirtualAddress( whpx_state* Partition, size_t Size, uintptr_t* VirtualAddress, WHSE_PROCESSOR_MODE Mode ); 95 | 96 | // Internal function to setup paging 97 | // 98 | HRESULT WhSiSetupPaging( whpx_state* Partition, uintptr_t* Pml4PhysicalAddress ); 99 | 100 | // Internal function to insert page table in the paging directory 101 | // Allocate PML4 entry, PDP entry, PD entry and PT entry 102 | // 103 | HRESULT WhSiInsertPageTableEntry( whpx_state* Partition, uintptr_t VirtualAddress, uintptr_t PhysicalAddress ); 104 | 105 | // Find a suitable Guest VA 106 | // 107 | HRESULT WhSiFindBestGVA( whpx_state* Partition, uintptr_t* GuestVa, size_t Size ); 108 | 109 | HRESULT WhSpSwitchProcessor(WHSE_PROCESSOR_MODE Mode); 110 | 111 | // Setup GDT 112 | // 113 | HRESULT WhSiSetupGlobalDescriptorTable( whpx_state* Partition ); 114 | 115 | // Setup IDT 116 | // 117 | HRESULT WhSiSetupInterruptDescriptorTable( whpx_state* Partition ); 118 | 119 | // Setup memory arena 120 | // 121 | HRESULT WhSiInitializeMemoryArena( whpx_state* Partition ); 122 | 123 | // Setup syscalls 124 | // 125 | //HRESULT WhSiSetupSyscalls( whpx_state* Partition, WHSE_REGISTERS Registers ); 126 | 127 | #endif // !WINHVMEMORYINTERNAL_HPP 128 | -------------------------------------------------------------------------------- /unicorn-whpx-core/target/WinHvMemoryPrivate.cpp: -------------------------------------------------------------------------------- 1 | #include "WinHvMemoryPrivate.hpp" 2 | #include "WinHvUtils.hpp" 3 | #include "WinHvAllocationTracker.hpp" 4 | #include "WinHvMemory.hpp" 5 | 6 | /** 7 | * @brief Private api 8 | * 9 | * @param Partition The VM partition 10 | * @param ParentLayer 11 | * @param Index 12 | * @return A result code 13 | */ 14 | HRESULT WhSpInsertPageTableEntry( whpx_state* Partition, PMMPTE_HARDWARE ParentLayer, uint16_t Index ) { 15 | uintptr_t gpa = 0; 16 | uintptr_t hva = 0; 17 | 18 | auto hresult = WhSeAllocateGuestPhysicalMemory( Partition, &hva, &gpa, PAGE_SIZE, WHvMapGpaRangeFlagRead | WHvMapGpaRangeFlagWrite ); 19 | if ( FAILED( hresult ) ) 20 | return hresult; 21 | 22 | // Create a valid PTE 23 | // 24 | MMPTE_HARDWARE pte { }; 25 | 26 | pte.AsUlonglong = 0; // Ensure zeroed 27 | pte.Valid = 1; // Intel's Present bit 28 | pte.Write = 1; // Intel's Read/Write bit 29 | pte.Owner = 1; // Intel's User/Supervisor bit, let's say it is a user accessible frame 30 | pte.PageFrameNumber = ( gpa / PAGE_SIZE ); // Physical address of PDP page 31 | 32 | ParentLayer[ Index ] = pte; 33 | 34 | return hresult; 35 | } 36 | 37 | /** 38 | * @brief Private api 39 | * 40 | * @param Partition The VM partition 41 | * @param PageFrameNumber 42 | * @param HostVa 43 | * @return A result code 44 | */ 45 | HRESULT WhSpLookupHVAFromPFN( whpx_state* Partition, uintptr_t PageFrameNumber, uintptr_t* HostVa ) { 46 | WHSE_ALLOCATION_NODE* node = nullptr; 47 | auto hresult = WhSeFindAllocationNodeByGpa( Partition, PageFrameNumber * PAGE_SIZE, &node ); 48 | if ( FAILED( hresult ) ) 49 | return hresult; 50 | 51 | *HostVa = node->HostVirtualAddress; 52 | 53 | return hresult; 54 | } 55 | 56 | /** 57 | * @brief Private api 58 | * 59 | * @param Entry The returned GDT entry 60 | * @param Base 61 | * @param Limit 62 | * @param Access 63 | * @param Flags 64 | * @return A result code 65 | */ 66 | HRESULT WhSpCreateGdtEntry( GDT_ENTRY* Entry, uintptr_t Base, ptrdiff_t Limit, uint8_t Access, uint8_t Flags ) { 67 | if ( Entry == nullptr ) 68 | return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); 69 | 70 | Entry->LimitLow = static_cast< uint16_t >( Limit & 0xffff ); 71 | Entry->BaseLow = static_cast< uint16_t >( Base & 0xffff ); 72 | Entry->BaseMid = static_cast< uint8_t >( ( Base >> 16 ) & 0xff ); 73 | Entry->Access = Access; 74 | Entry->LimitHigh = static_cast< uint8_t >( ( Limit >> 16 ) & 0xf ); 75 | Entry->Flags = static_cast< uint8_t >( Flags & 0xf ); 76 | Entry->BaseHigh = static_cast< uint8_t >( ( Base >> 24 ) & 0xff ); 77 | 78 | return S_OK; 79 | } 80 | 81 | /** 82 | * @brief Private api 83 | * 84 | * @param TssSegmentDesc The returned TSS entry 85 | * @param Base 86 | * @param Limit 87 | * @param Access 88 | * @param Flags 89 | * @return A result code 90 | */ 91 | HRESULT WhSpCreateTssEntry( X64_TSS_ENTRY* TssSegmentDesc, uintptr_t Base, ptrdiff_t Limit, uint8_t Access, uint8_t Flags ) { 92 | if ( TssSegmentDesc == nullptr ) 93 | return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); 94 | 95 | auto hresult = WhSpCreateGdtEntry( &( TssSegmentDesc->GdtEntry ), Base, Limit, Access, Flags ); 96 | if ( FAILED( hresult ) ) 97 | return hresult; 98 | 99 | TssSegmentDesc->BaseHigh = ( Base >> 32 ) & UINT32_MAX; 100 | 101 | return S_OK; 102 | } 103 | uint16_t TssComputeIopbOffset(uint16_t offset) 104 | { 105 | return offset != X64_TASK_STATE_SEGMENT_IOPB_NONE 106 | ? offset 107 | : sizeof(X64_TASK_STATE_SEGMENT); 108 | } 109 | /** 110 | * @brief Private api 111 | * 112 | * @param Tss Pointer to the Task State Segment 113 | * @return A result code 114 | */ 115 | HRESULT WhSpInitializeTss( whpx_state* Partition, PX64_TASK_STATE_SEGMENT Tss ) { 116 | if ( Tss == nullptr ) 117 | return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); 118 | 119 | uintptr_t stackHva = 0; 120 | constexpr size_t stackSize = 1MiB; 121 | //uintptr_t stackGva = 0xffffb000'00000000; 122 | uintptr_t stackGva = 0; 123 | auto hresult = WhSeAllocateGuestVirtualMemory( Partition, &stackHva, &stackGva, stackSize, WHvMapGpaRangeFlagRead | WHvMapGpaRangeFlagWrite ); 124 | if ( FAILED( hresult ) ) 125 | return hresult; 126 | 127 | Tss->Rsp0 = stackGva; 128 | 129 | Tss->Iopb = TssComputeIopbOffset( X64_TASK_STATE_SEGMENT_IOPB_NONE ); 130 | 131 | return S_OK; 132 | } 133 | -------------------------------------------------------------------------------- /unicorn-whpx-core/target/WinHvMemoryPrivate.hpp: -------------------------------------------------------------------------------- 1 | #ifndef WINHVMEMORYPRIVATE_HPP 2 | #define WINHVMEMORYPRIVATE_HPP 3 | 4 | #include 5 | 6 | #include "WinHvDefs.hpp" 7 | #include "WinHvMemoryInternal.hpp" 8 | 9 | // Private api 10 | // 11 | HRESULT WhSpLookupHVAFromPFN( whpx_state* Partition, uintptr_t PageFrameNumber, uintptr_t* HostVa ); 12 | 13 | // Private api 14 | // 15 | HRESULT WhSpInsertPageTableEntry( whpx_state* Partition, PMMPTE_HARDWARE ParentLayer, uint16_t Index ); 16 | 17 | // Private api 18 | // 19 | HRESULT WhSpCreateGdtEntry( GDT_ENTRY* Entry, uintptr_t Base, ptrdiff_t Limit, uint8_t Access, uint8_t Flags ); 20 | 21 | // Private api 22 | // 23 | HRESULT WhSpCreateTssEntry( X64_TSS_ENTRY* TssSegmentDesc, uintptr_t Base, ptrdiff_t Limit, uint8_t Access, uint8_t Flags ); 24 | 25 | // Private api 26 | // 27 | HRESULT WhSpInitializeTss( whpx_state* Partition, PX64_TASK_STATE_SEGMENT Tss ); 28 | 29 | #endif // !WINHVMEMORYPRIVATE_HPP 30 | -------------------------------------------------------------------------------- /unicorn-whpx-core/target/unicorn-whpx.h: -------------------------------------------------------------------------------- 1 | /* Unicorn Emulator Engine */ 2 | /* By Nguyen Anh Quynh , 2015 */ 3 | /* Modified for Unicorn Engine by Chen Huitao, 2020 */ 4 | 5 | #ifndef UC_QEMU_TARGET_I386_WHPX_H 6 | #define UC_QEMU_TARGET_I386_WHPX_H 7 | 8 | // functions to read & write registers 9 | int x86_reg_read(struct uc_struct *uc, unsigned int *regs, void **vals, 10 | int count); 11 | int x86_reg_write(struct uc_struct *uc, unsigned int *regs, void *const *vals, 12 | int count); 13 | int x86_context_reg_read(struct uc_context *ctx, unsigned int *regs, 14 | void **vals, int count); 15 | int x86_context_reg_write(struct uc_context *ctx, unsigned int *regs, 16 | void *const *vals, int count); 17 | 18 | void x86_reg_reset(struct uc_struct *uc); 19 | 20 | void x86_whpx_uc_init(struct uc_struct *uc); 21 | #endif 22 | -------------------------------------------------------------------------------- /unicorn-whpx-core/target/whpx-internal.h: -------------------------------------------------------------------------------- 1 | #ifndef TARGET_I386_WHPX_INTERNAL_H 2 | #define TARGET_I386_WHPX_INTERNAL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | typedef enum WhpxBreakpointState { 13 | WHPX_BP_CLEARED = 0, 14 | WHPX_BP_SET_PENDING, 15 | WHPX_BP_SET, 16 | WHPX_BP_CLEAR_PENDING, 17 | } WhpxBreakpointState; 18 | 19 | struct whpx_breakpoint { 20 | uint64_t address; 21 | WhpxBreakpointState state; 22 | int bptype; 23 | uint8_t original_instruction; 24 | }; 25 | 26 | struct whpx_breakpoint_collection { 27 | int allocated, used; 28 | struct whpx_breakpoint data[0]; 29 | }; 30 | 31 | struct whpx_breakpoints { 32 | int original_address_count; 33 | uint64_t *original_addresses; 34 | 35 | struct whpx_breakpoint_collection *breakpoints; 36 | }; 37 | 38 | 39 | typedef enum WhpxStepMode { 40 | WHPX_STEP_NONE = 0, 41 | /* Halt other VCPUs */ 42 | WHPX_STEP_EXCLUSIVE, 43 | } WhpxStepMode; 44 | 45 | typedef struct whpx_vcpu 46 | { 47 | WHV_EMULATOR_HANDLE emulator; 48 | bool window_registered; 49 | bool interruptable; 50 | bool ready_for_pic_interrupt; 51 | uint64_t tpr; 52 | uint64_t apic_base; 53 | bool interruption_pending; 54 | 55 | /* Must be the last field as it may have a tail */ 56 | WHV_RUN_VP_EXIT_CONTEXT exit_ctx; 57 | } whpx_vcpu; 58 | 59 | /* state subset only touched by the VCPU itself during runtime */ 60 | #define WHPX_SET_RUNTIME_STATE 1 61 | /* state subset modified during VCPU reset */ 62 | #define WHPX_SET_RESET_STATE 2 63 | /* full state set, modified during initialization or on vmload */ 64 | #define WHPX_SET_FULL_STATE 3 65 | 66 | 67 | //void whpx_apic_get(DeviceState *s); 68 | 69 | #define WHV_E_UNKNOWN_CAPABILITY 0x80370300L 70 | 71 | /* This should eventually come from the Windows SDK */ 72 | #define WHV_E_UNKNOWN_PROPERTY 0x80370302 73 | 74 | #define LIST_WINHVPLATFORM_FUNCTIONS(X) \ 75 | X(HRESULT, WHvGetCapability, (WHV_CAPABILITY_CODE CapabilityCode, VOID* CapabilityBuffer, UINT32 CapabilityBufferSizeInBytes, UINT32* WrittenSizeInBytes)) \ 76 | X(HRESULT, WHvCreatePartition, (WHV_PARTITION_HANDLE* Partition)) \ 77 | X(HRESULT, WHvSetupPartition, (WHV_PARTITION_HANDLE Partition)) \ 78 | X(HRESULT, WHvDeletePartition, (WHV_PARTITION_HANDLE Partition)) \ 79 | X(HRESULT, WHvGetPartitionProperty, (WHV_PARTITION_HANDLE Partition, WHV_PARTITION_PROPERTY_CODE PropertyCode, VOID* PropertyBuffer, UINT32 PropertyBufferSizeInBytes, UINT32* WrittenSizeInBytes)) \ 80 | X(HRESULT, WHvSetPartitionProperty, (WHV_PARTITION_HANDLE Partition, WHV_PARTITION_PROPERTY_CODE PropertyCode, const VOID* PropertyBuffer, UINT32 PropertyBufferSizeInBytes)) \ 81 | X(HRESULT, WHvMapGpaRange, (WHV_PARTITION_HANDLE Partition, VOID* SourceAddress, WHV_GUEST_PHYSICAL_ADDRESS GuestAddress, UINT64 SizeInBytes, WHV_MAP_GPA_RANGE_FLAGS Flags)) \ 82 | X(HRESULT, WHvUnmapGpaRange, (WHV_PARTITION_HANDLE Partition, WHV_GUEST_PHYSICAL_ADDRESS GuestAddress, UINT64 SizeInBytes)) \ 83 | X(HRESULT, WHvTranslateGva, (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, WHV_GUEST_VIRTUAL_ADDRESS Gva, WHV_TRANSLATE_GVA_FLAGS TranslateFlags, WHV_TRANSLATE_GVA_RESULT* TranslationResult, WHV_GUEST_PHYSICAL_ADDRESS* Gpa)) \ 84 | X(HRESULT, WHvCreateVirtualProcessor, (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, UINT32 Flags)) \ 85 | X(HRESULT, WHvDeleteVirtualProcessor, (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex)) \ 86 | X(HRESULT, WHvRunVirtualProcessor, (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, VOID* ExitContext, UINT32 ExitContextSizeInBytes)) \ 87 | X(HRESULT, WHvCancelRunVirtualProcessor, (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, UINT32 Flags)) \ 88 | X(HRESULT, WHvGetVirtualProcessorRegisters, (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, const WHV_REGISTER_NAME* RegisterNames, UINT32 RegisterCount, WHV_REGISTER_VALUE* RegisterValues)) \ 89 | X(HRESULT, WHvSetVirtualProcessorRegisters, (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, const WHV_REGISTER_NAME* RegisterNames, UINT32 RegisterCount, const WHV_REGISTER_VALUE* RegisterValues)) \ 90 | 91 | /* 92 | * These are supplemental functions that may not be present 93 | * on all versions and are not critical for basic functionality. 94 | */ 95 | #define LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL(X) \ 96 | X(HRESULT, WHvSuspendPartitionTime, (WHV_PARTITION_HANDLE Partition)) \ 97 | X(HRESULT, WHvRequestInterrupt, (WHV_PARTITION_HANDLE Partition, \ 98 | WHV_INTERRUPT_CONTROL* Interrupt, UINT32 InterruptControlSize)) \ 99 | X(HRESULT, WHvGetVirtualProcessorInterruptControllerState2, \ 100 | (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, PVOID State, \ 101 | UINT32 StateSize, UINT32* WrittenSize)) \ 102 | X(HRESULT, WHvSetVirtualProcessorInterruptControllerState2, \ 103 | (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, PVOID State, \ 104 | UINT32 StateSize)) \ 105 | 106 | #define LIST_WINHVEMULATION_FUNCTIONS(X) \ 107 | X(HRESULT, WHvEmulatorCreateEmulator, (const WHV_EMULATOR_CALLBACKS* Callbacks, WHV_EMULATOR_HANDLE* Emulator)) \ 108 | X(HRESULT, WHvEmulatorDestroyEmulator, (WHV_EMULATOR_HANDLE Emulator)) \ 109 | X(HRESULT, WHvEmulatorTryIoEmulation, (WHV_EMULATOR_HANDLE Emulator, VOID* Context, const WHV_VP_EXIT_CONTEXT* VpContext, const WHV_X64_IO_PORT_ACCESS_CONTEXT* IoInstructionContext, WHV_EMULATOR_STATUS* EmulatorReturnStatus)) \ 110 | X(HRESULT, WHvEmulatorTryMmioEmulation, (WHV_EMULATOR_HANDLE Emulator, VOID* Context, const WHV_VP_EXIT_CONTEXT* VpContext, const WHV_MEMORY_ACCESS_CONTEXT* MmioInstructionContext, WHV_EMULATOR_STATUS* EmulatorReturnStatus)) \ 111 | 112 | #define WHP_DEFINE_TYPE(return_type, function_name, signature) \ 113 | typedef return_type (WINAPI *function_name ## _t) signature; 114 | 115 | #define WHP_DECLARE_MEMBER(return_type, function_name, signature) \ 116 | function_name ## _t function_name; 117 | 118 | /* Define function typedef */ 119 | LIST_WINHVPLATFORM_FUNCTIONS(WHP_DEFINE_TYPE) 120 | LIST_WINHVEMULATION_FUNCTIONS(WHP_DEFINE_TYPE) 121 | LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL(WHP_DEFINE_TYPE) 122 | 123 | struct WHPDispatch { 124 | LIST_WINHVPLATFORM_FUNCTIONS(WHP_DECLARE_MEMBER) 125 | LIST_WINHVEMULATION_FUNCTIONS(WHP_DECLARE_MEMBER) 126 | LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL(WHP_DECLARE_MEMBER) 127 | }; 128 | 129 | extern struct WHPDispatch whp_dispatch; 130 | 131 | bool init_whp_dispatch(void); 132 | 133 | typedef enum WHPFunctionList { 134 | WINHV_PLATFORM_FNS_DEFAULT, 135 | WINHV_EMULATION_FNS_DEFAULT, 136 | WINHV_PLATFORM_FNS_SUPPLEMENTAL 137 | } WHPFunctionList; 138 | 139 | #ifdef __cplusplus 140 | } 141 | #endif 142 | #endif 143 | --------------------------------------------------------------------------------