Search code examples
c++virtual-machinecpu-architecturecpu-registers

How can I do a step-by-step tracing on amd-v(svm)?


Introduction:

I can't figure out how I can do a trace on an amd hypervisor. Is there something like eflags.tf in svm? so that I can trace the instructions

Main problem:

I initially thought that amd has something like intel, where I vmwrite in CPU_BASED_VM_EXEC_CONTROL(0x00004002) value CPU_BASED_MONITOR_TRAP_FLAG(0x08000000), but amd-v does not have this, and the tf flag does not work. I also found information that the rf flag can also affect this somehow, but I didn't understand how, since when I switch the rf flag to 1, nothing is intercepted in SvmExitHandler either.

Main question:

Thus the main question: Can someone tell me how can I trace instructions in the case of amd-v? i.e., for example, initially I virtualize the entire system, after that, for example, I compiled a large application through the clang compiler with my optimization phases, then I need to trace it like:

  1. vmrun
  2. instruction from a guest
  3. vmexit
  4. my SvmExitHandler function with Exitcode dispatcher
  5. some actions in Exitcode dispatcher
  6. back to the first stage

Solution

  • In short, you need to intercept DB exception, and then process it in SvmExitHandler. Only I haven't figured out how to get out of it yet, because after turning on the tf flag, I'm staying there forever.

    How can you do that:

    https://github.com/HoShiMin/Kernel-Bridge

    You need to Enable this interception

    Private->Guest.ControlArea.InterceptExceptions.Bitmap.InterceptionVectorDB = TRUE;

    and you can catch DB exception:

    extern "C" VMM_STATUS SvmVmexitHandler(PRIVATE_VM_DATA* Private, GUEST_CONTEXT* Context, GUEST_SSE_CONTEXT* SSeContext, GUEST_AVX_CONTEXT* AvxContext)
        {
            // Load the host state:
            __svm_vmload(reinterpret_cast<size_t>(Private->VmmStack.Layout.InitialStack.HostVmcbPa));
            
            // Restore the guest's RAX that was overwritten by host's RAX on #VMEXIT:
            Context->Rax = Private->Guest.StateSaveArea.Rax;
    
            VMM_STATUS Status = VMM_STATUS::VMM_CONTINUE;
    
            switch (Private->Guest.ControlArea.ExitCode)
            {
            case VMEXIT_CPUID:
            {
                CPUID_REGS Regs = {};
                int Function = static_cast<int>(Context->Rax);
                int SubLeaf = static_cast<int>(Context->Rcx);
                __cpuidex(Regs.Raw, Function, SubLeaf);
    
                switch (Function) {
                case CPUID_VMM_SHUTDOWN:
                {
                    // Shutdown was triggered:
                    Status = VMM_STATUS::VMM_SHUTDOWN;
                    break;
                }
                case CPUID::Generic::CPUID_MAXIMUM_FUNCTION_NUMBER_AND_VENDOR_ID:
                {
                    Context->Rax = Regs.Regs.Eax;
                    GetHvCpuName(Context->Rbx, Context->Rcx, Context->Rdx);
                    break;
                }
                default:
                {
                    Context->Rax = Regs.Regs.Eax;
                    Context->Rbx = Regs.Regs.Ebx;
                    Context->Rcx = Regs.Regs.Ecx;
                    Context->Rdx = Regs.Regs.Edx;
                    break;
                }
                }
                break;
            }
            case VMEXIT_MSR:
            {
                if ((Context->Rcx & MAXUINT32) == static_cast<unsigned int>(AMD_MSR::MSR_EFER) && Private->Guest.ControlArea.ExitInfo1)
                {
                    EFER Efer = {};
                    Efer.Value = ((Context->Rdx & MAXUINT32) << 32) | (Context->Rax & MAXUINT32);
                    if (!Efer.Bitmap.SecureVirtualMachineEnable)
                    {
                        InjectEvent(&Private->Guest, INTERRUPT_VECTOR::GeneralProtection, EXCEPTION_VECTOR::FaultTrapException, 0); // #GP (Vector = 13, Type = Exception)
                        break;
                    }
                    Private->Guest.StateSaveArea.Efer = Efer.Value;
                }
                break;
            }
            case VMEXIT_VMRUN:
            {
                InjectEvent(&Private->Guest, INTERRUPT_VECTOR::GeneralProtection, EXCEPTION_VECTOR::FaultTrapException, 0); // #GP (Vector = 13, Type = Exception)
                break;
            }
            case VMEXIT_EXCP_DB://Trace, you can call this by TF flag switch to 1
            {
                        KdPrint(("RIP %p\n", Private->Guest.StateSaveArea.Rip));
    
                Private->Guest.StateSaveArea.Rax = Context->Rax;
                return Status;
            }
            }
    
            if (Status == VMM_STATUS::VMM_SHUTDOWN)
            {
                // We should to devirtualize this processor:
                Context->Rax = reinterpret_cast<UINT64>(Private) & MAXUINT32; // Low part
                Context->Rbx = Private->Guest.ControlArea.NextRip;
                Context->Rcx = Private->Guest.StateSaveArea.Rsp;
                Context->Rdx = reinterpret_cast<UINT64>(Private) >> 32; // High part
    
                // Load the guest's state:
                __svm_vmload(reinterpret_cast<size_t>(Private->VmmStack.Layout.InitialStack.GuestVmcbPa));
                
                // Store the GIF - Global Interrupt Flag:
                _disable();
                __svm_stgi();
    
                // Disable the SVM by resetting the EFER.SVME bit:
                EFER Efer = {};
                Efer.Value = __readmsr(static_cast<unsigned long>(AMD_MSR::MSR_EFER));
                Efer.Bitmap.SecureVirtualMachineEnable = FALSE;
                __writemsr(static_cast<unsigned long>(AMD_MSR::MSR_EFER), Efer.Value);
    
                // Restoring the EFlags:
                __writeeflags(Private->Guest.StateSaveArea.Rflags);
            }
    
            Private->Guest.StateSaveArea.Rax = Context->Rax;
            
            // Go to the next instruction:
            Private->Guest.StateSaveArea.Rip = Private->Guest.ControlArea.NextRip;
    
            return Status;
        }