使用.NET NativeAOT模式开发hyper-v平台uefi上windbg调试引擎心得

news2024/9/22 17:30:09

引用

这篇文章的目的是介绍一款实验性的Uefi项目基于.NET NativeAOT模式实现的运行在Windows Hyper-V虚拟机上的windbg调试引擎开发心得

文章目录

    • 引用
    • 简介
    • NativeAOT模式动态接口的多态继承绑定实现方式分析
    • NativeAOT模式运行时栈回溯实现方式分析
    • 模拟vcruntime实现的c++异常捕获特性分析
    • Windows Hyper-V虚拟机Vmbus通道分析
    • 编译运行方式
    • 运行效果
    • 相关引用
    • 参与贡献

简介

笔者实现了一款采用.NET NativeAOT模式开发运行在Windows Hyper-V Uefi虚拟机上基于Vmbus通道的windbg调试协议引擎.
NativeAOT是.NET中一款将.NET程序集编译为原生二进制指令的编译模式,这种模式将.NET 带到了无需.NET Framework依赖即不允许JIT编译器的平台
比如uefi或Windows内核驱动或嵌入式设备或桌面程序等原生二进制指令目标平台.
笔者工具在原.NET NativeAOT项目的基础上拓展了动态接口的多态继承绑定实现,
还添加了执行IDT异常回调的模拟vcruntime实现的c++异常捕获处理特性,解决了在uefi程序运行过程中的不可预知的内部原生函数抛出异常处理的缓解措施.
笔者程序实现的windbg调试协议支持插件模式提供给后续的uefi应用程序调用插件绑定的接口于windbg调试引擎交互.
windbg调试器运行在hyper-v的宿主机上,通过代理程序中间人模式转发windbg内核命名管道调试数据适配到目的接口实现调试uefi程序,
记录或者实时捕捉调试数据到wireshark解析插件分析.

NativeAOT模式动态接口的多态继承绑定实现方式分析

.NET NativeAOT程序的运行时动态接口的多态继承绑定结构信息由.NET编译器在编译时生成存储在PE结构中的InterfaceDispatchCell结构体中,在每次需要进行动态接口转换的过程中
会调用RhpInitialDynamicInterfaceDispatch函数实现InterfaceDispatchCell结构体由r10寄存器传入,其中m_pStub指向RhpInitialDynamicInterfaceDispatch,m_pCache指向DispatchCellInfo结构体指针,
这个转换需要用到存入寄存器的值rcx寄存器指向的派生类this指针,它的第一个字段也就是MethodTable虚表指针用于确定派生类的多态基类绑定信息DynamicDispatchMapTable的相关属性.
而这个绑定信息结合最终需要转换的接口的DispatchCellInfo中指定的InterfaceType和InterfaceSlot,也就是目标接口类型和多态继承绑定的接口索引匹配派生类实现的接口信息.
如果找到的接口实现信息符合转换条件就会返回接口的MethodTable中实现的重载虚函数目标地址由rax返回,控制流跳转到rax实现动态接口的实现分发.

  internal unsafe struct DispatchCellInfo
  {
      public DispatchCellType CellType;
      public MethodTable* InterfaceType;
      public ushort InterfaceSlot;
      public byte HasCache;
      public uint MetadataToken;
      public uint VTableOffset;
  };
  internal unsafe struct InterfaceDispatchCell
  {
      public IntPtr m_pStub;
      public IntPtr **m_pCache**;
  };
  private unsafe static bool FindImplSlotInSimpleMap(MethodTable* pTgtType,
                             MethodTable* pItfType,
                             uint itfSlotNumber,
                             ushort* pImplSlotNumber,
                             MethodTable** ppGenericContext,
                             bool actuallyCheckVariance,
                             bool checkDefaultImplementations)
  {
if (pTgtType->HasDispatchMap)
{
 DispatchMap.DispatchMapEntry* i = fStaticDispatch ?
     pMap->GetStaticEntry(checkDefaultImplementations ? (int)pMap->NumStandardStaticEntries : 0) :
     pMap->GetEntry(checkDefaultImplementations ? (int)pMap->NumStandardEntries : 0);
 DispatchMap.DispatchMapEntry* iEnd = fStaticDispatch ?
     pMap->GetStaticEntry(checkDefaultImplementations ? (int)(pMap->NumStandardStaticEntries + pMap->NumDefaultStaticEntries) : (int)pMap->NumStandardStaticEntries) :
     pMap->GetEntry(checkDefaultImplementations ? (int)(pMap->NumStandardEntries + pMap->NumDefaultEntries) : (int)pMap->NumStandardEntries);
  for (; i != iEnd; i = fStaticDispatch ? (DispatchMap.DispatchMapEntry*)(((DispatchMap.StaticDispatchMapEntry*)i) + 1) : i + 1)
     if (i->_usInterfaceMethodSlot == itfSlotNumber)
      {
          MethodTable* pCurEntryType = pTgtType->InterfaceMap[i->_usInterfaceIndex].InterfaceEntryType;      
          if (pCurEntryType == pItfType)          
              return true;
      }     
 public unsafe static IntPtr FindInterfaceMethodImplementationTarget(MethodTable* pTgtType, MethodTable* pItfType, ushort itfSlotNumber, MethodTable** ppGenericContext)
 {
  if (FindImplSlotForCurrentType(
         pCur, pItfType, itfSlotNumber, fDoDefaultImplementationLookup, &implSlotNumber, ppGenericContext))
 {
                    if (implSlotNumber < pCur->NumVtableSlots)
                    {
                        //之后调用基类实现jmp rax
                        return pTgtType->GetVTableStartAddress()[(int)implSlotNumber];
                    }
}
private unsafe static IntPtr RhpCidResolve(object pObject, ref DispatchCellInfo cellInfo)
{
    MethodTable* pInstanceType = pObject.GetMethodTable();
    if (cellInfo.CellType == DispatchCellType.InterfaceAndSlot)
    {
        MethodTable* pResolvingInstanceType = pInstanceType;   
        IntPtr pTargetCode = DispatchResolve.FindInterfaceMethodImplementationTarget(pResolvingInstanceType, cellInfo.InterfaceType, cellInfo.InterfaceSlot, null);      
        return pTargetCode;
    }
    if (cellInfo.CellType == DispatchCellType.VTableOffset)
    {
        return *(IntPtr*)((byte*)pInstanceType + cellInfo.VTableOffset);
    }
    return IntPtr.Zero;
}
RhpInitialDynamicInterfaceDispatch PROC
sub     rsp, 78h
mov     [rsp+80h], rcx
mov     [rsp+88h], rdx
mov     [rsp+90h], r8
mov     [rsp+98h], r9
movdqa  xmmword ptr [rsp+20h], xmm0
movdqa  xmmword ptr [rsp+30h], xmm1
movdqa  xmmword ptr [rsp+40h], xmm2
movdqa  xmmword ptr [rsp+50h], xmm3
mov     rdx, r10
mov     rcx, qword ptr [rsp+80h]
call    RhpCidResolve
movdqa  xmm0, xmmword ptr [rsp+20h]
movdqa  xmm1, xmmword ptr [rsp+30h]
movdqa  xmm2, xmmword ptr [rsp+40h]
movdqa  xmm3, xmmword ptr [rsp+50h]
mov     rcx, [rsp+80h]
mov     rdx, [rsp+88h]
mov     r8, [rsp+90h]
mov     r9, [rsp+98h]
nop
add     rsp, 78h
test rax,rax
je cleanup
jmp     rax
cleanup:
ret
RhpInitialDynamicInterfaceDispatch ENDP

.NET NativeAOT程序中windows桌面程序的默认动态接口分发实现由.NET编译器内嵌的运行时静态库库的TypeManager类实现,笔者的uefi项目并没有依赖于编译器提供的编译扩展运行时支持,
通过手动方式实现,TypeManager类实现实际上是由一个包含于模块基址和一个指向pe结构中一个名为ReadyToRunSectionType.InterfaceDispatchTable节的分发结构体表组成.
只需要构造这样一个结构体指针放入ReadyToRunSectionType.TypeManagerIndirection节的首地址就可以完成,编译器会绑定每个类虚表的默认的TypeManagerHandle.TypeManager字段就指向这个节的地址.
有了这样一个结构体就可以在它的DispatchMap字段找到需要分发的目标类型DispatchMap分发信息包括接口和继承信息.最终找到目标接口实现
至此.NET的运行时动态接口的多态继承特性已经可以完美实现运行在uefi程序上.
笔者使用这个特性实现了c#的IEnumerable接口为工程提供了一套类似linq的集合类型扩展方法实现,对常用的linq函数基本上都提供了支持.
笔者项目关闭了项目选项EnableDefaultCompileItems选项,重写了.net基类的运行时实现,只适配于编译器版本Microsoft.DotNet.ILCompiler Version=7.0.19版本
笔者还修复了ReadyToRunSectionType.GCStaticRegion节数据,解决了无gc模式下,gc全局指针为空的报错问题,详见笔者项目RunGlobalPE函数实现.

NativeAOT模式运行时栈回溯实现方式分析

.NET NativeAOT程序运行时函数符号信息存储于pe节的ReadyToRunSectionType.BlobIdStackTraceMethodRvaToTokenMapping节中,需要在项目配置文件开启StackTraceSupport选项.
相对于c编写的二进制程序,默认函数的符号信息存在于pdb文件中,二进制主程序不包含函数的符号信息,在没有pdb文件的情况下获取函数名等符号信息是不可行的.对于NativeAOT程序只要开启了开启StackTraceSupport选项,
尽管会增加最后生成的二进制文件大小,但是对于异常的处理工作可以就可以实现在不需要pdb文件的情况下实现栈回溯的辅助功能.BlobIdStackTraceMethodRvaToTokenMapping节中节保存了所有运行时函数的rva(相对偏移量)信息,
每个rva关联一个token组成一个字典结构,通过这个token可以在ReadyToRunSectionType.ReflectionMapBlobEmbeddedMetadata找到以明文存储的函数名的字符串数据,具体偏移量解码方式只需要调用微软官方的NativePrimitiveDecoder函数即可.
在抛出异常时可以在idt中获取当前抛出异常原的栈rsp和函数地址rip,首先展示rip的函数信息,再从rsp向上遍历直到找到一个在缓存字典中符号的函数信息,就可以完整展示当前抛出异常时的栈回溯信息.

List<FunctionTraceMap> FunctionTokenMap = new List<FunctionTraceMap>();
IntPtr pMap =StartupCodeHelpers.GetModuleSectionWithLength(moduleSeg, ReadyToRunSectionType.BlobIdStackTraceMethodRvaToTokenMapping, ref length);
int* rvaToTokenMap = (int*)pMap;
int rvaToTokenMapEntryCount = (int)((int)length / (2 * sizeof(int)));
for (int entryIndex = 0; entryIndex < rvaToTokenMapEntryCount; entryIndex++)
{
    int* pRelPtr32 = &rvaToTokenMap[2 * entryIndex + 0];    
    IntPtr pointer = (IntPtr)((IntPtr)pRelPtr32 + *pRelPtr32);
    int methodRva = (int)(pointer - UefiApplication.ImageBase);
    int token = rvaToTokenMap[2 * entryIndex + 1];
      if (!methodRvaToTokenMap.ContainsKey(methodRva))
    {
        methodRvaToTokenMap.Add(methodRva, token);
    }
}
IntPtr length = 0;
IntPtr pEmbeddedMetadata = StartupCodeHelpers.GetModuleSectionWithLength(moduleSeg, ReadyToRunSectionType.ReflectionMapBlobEmbeddedMetadata, ref length);
byte* pCurrent = (byte*)pEmbeddedMetadata;
byte* pCurrentsave = (byte*)pEmbeddedMetadata;
foreach (KeyValuePair<int, int> kv in methodRvaToTokenMap)
{            
      IntPtr methodRva =new IntPtr(kv.Key);
      IntPtr metthodPtr = methodRva + UefiApplication.ImageBase;   
      int token = kv.Value & 0xffffff;
      pCurrent = (byte*)pEmbeddedMetadata;
      pCurrent += token;              
      UInt32 memberReference_parent = NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent);          
      UInt32 memberReference_name = NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent);            
      IntPtr pCurrentstringsave = pEmbeddedMetadata + memberReference_name + 1;
      //这个就是函数名
      string methodname = string.FromASCII(pCurrentstringsave).Trim();
      FunctionTraceMap traceMap = new FunctionTraceMap(metthodPtr, methodname);
      FunctionTokenMap.Add(traceMap);
}
//获取某个函数的符号信息
private static bool DumpFunctionAddressAndName(IntPtr FunPtr)
{
int len = FunctionTokenMap.Count;
for (int j = 0; j < len; j++)
 {
     int k = j + 1;
     FunctionTraceMap tmpj = FunctionTokenMap[j];
     IntPtr FunPtrj = tmpj.FunctionAddress - modbase;
     IntPtr FunDiff = FunPtr - tmpj.FunctionAddress;    
     FunctionTraceMap tmpk = FunctionTokenMap[k];
     if (tmpk.FunctionAddress > FunPtr && FunPtr > tmpj.FunctionAddress - difflen)
     {   
           Console.WriteLine(FunPtr.ToString("x") + ":=>" +":base+" + FunPtrj.ToString("x") + "=" +  tmpj.FunctionName + "+" + FunDiff.ToString("x"));     
     }
}

笔者为字符串匹配提供了一个string.FromASCII函数,过滤了其中无效ASCII字符,只保留可打印的函数名字符串,具体源码可以在笔者开源项目中找到.
这个栈回溯信息结合了下面节介绍的异常捕获处理为程序的调试工作提供了很好的辅助功能.栈回溯效果如图:
在这里插入图片描述

模拟vcruntime实现的c++异常捕获特性分析

C和C++程序都可以在x86平台指令集使用结构化异常处理(structured exception handling SEH)机制.
c的异常称为asynchronous structured exception,c++的异常称为synchronous structured exception
C的异常处理SEH中的概念类似于C++异常中的概念,SEH使用__try、__except和__finally构造而不是C++异常中的try和catch.
在微软C++编译器(MSVC)中,为SEH实现了C++异常。但是写C++代码时,需要使用C++异常语法,SEH同样可以捕获C++异常,c异常也同理,需要在vs从开启配置(配置属性->c/c+±>代码生成->启用c++异常->是但有seh异常(/EHA)),这个选项可以同时捕获C和C++异常.
下面这段代码展示一个一段简单C++异常语法.

EXTERN_C EFI_STATUS OutputStringWrapper(IN CHAR16* buf)
{
	try {
		ConsoleOutputString(buf);
	}
	catch (...)
	{
	    //OutputStringWrapper$catch$0
		Print(L"Exception OutputString Handler\r\n");
	}
	return 0;
}

对于上面这段函数默认会编译出OutputStringWrapper和OutputStringWrapper$catch$0两个内部的函数形式,前者用于在函数入口处理,后者用于在捕获异常时由栈迭代找到跳栈上的异常捕获函数转到这个异常捕获函数执行.
运行时异常信息存在pe结构的EFI_IMAGE_DIRECTORY_ENTRY_EXCEPTION表OptionalHeader中,这个表中保存的是一个IMAGE_RUNTIME_FUNCTION_ENTRY数组具体结构如下,
windbg提供一个功能.fnent用于异常结构的展示,运行效果如下

kd> .fnent nt!OutputStringWrapper
Debugger function entry 00000145`3c7ce180 for:
 [E:\git\UefiAotHyperV\WindbgUefiSharp\NativeUefi\nativelib.cpp @ 63] (00000000`f6744ac0)   nt!OutputStringWrapper   |  (00000000`f6744ae0)   nt!WriteLineWrapper
Exact matches:
    nt!OutputStringWrapper (unsigned short *)

BeginAddress      = 00000000`00005ac0
EndAddress        = 00000000`00005add
UnwindInfoAddress = 00000000`0002ebc8

Unwind info at 00000000`f676dbc8, 10 bytes
  version 1, flags 3, prolog 9, codes 1
  handler routine: nt!__CxxFrameHandler4 (00000000`f6743360), data 2ebd8
  00: offs 9, unwind op 2, op info 4	UWOP_ALLOC_SMALL.

这个结构包含了所有运行时函数的起始地址和结束地址RVA,紧跟在其后的是一个指向UnwindData的PUNWIND_CODE指针偏移量,这个结构保存了当异常栈帧需要展开的时候需要在栈上开辟的空间和用于在栈上保存非易失寄存器值的称为
prolog(序言)和epilog(末言)由异常处理程序处理的结构化信息.prolog例程执行完成后执行PUNWIND_CODE尾部保存的真正的异常处理回调.
一些典型的UNWIND_CODE包括:
ALLOC_SMALL/LARGE(为局部参数分配小/大内存,例如sub rsp, 80h)
PUSH_NONVOL(将非易失性寄存器推送到堆栈,例如push rdi)
在开启的包含异常代码的x86平台程序时编译器默认会将异常处理回调绑定到一个__CxxFrameHandler4函数,
这个函数默认在vcruntime.dll实现借助了ntdll的RtlVirtualUnwind系统自带异常栈帧展开功能找到异常函数对于的捕获函数,并跳转到捕获函数执行.这个过程实际上是对于PUNWIND_CODE尾部保存回调后面的ExceptionData数据的展开,
可以在开源的vcruntime运行库源码
中找到实现,展开数据后最终得到的一个TryBlockMap4._handler.dispOfHandler就是异常捕获处理的函数地址OutputStringWrapper$catch$0.笔者是uefi程序没有vc运行时,采用手动方式实现,具体方法如下.

typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY {
	DWORD BeginAddress;
	DWORD EndAddress;
	union {
		DWORD UnwindInfoAddress;
		DWORD UnwindData;
	} u;
} _IMAGE_RUNTIME_FUNCTION_ENTRY, * _PIMAGE_RUNTIME_FUNCTION_ENTRY;
typedef union _UNWIND_CODE {
    struct {
        UBYTE CodeOffset;
        UBYTE UnwindOp : 4;
        UBYTE OpInfo   : 4;
    };
    USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;
typedef struct _UNWIND_INFO {
    UBYTE Version       : 3;
    UBYTE Flags         : 5;
    UBYTE SizeOfProlog;
    UBYTE CountOfCodes;
    UBYTE FrameRegister : 4;
    UBYTE FrameOffset   : 4;
    UNWIND_CODE UnwindCode[1];
/*  UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
    OPTIONAL ULONG ExceptionHandler;
*   OPTIONAL ULONG ExceptionData[]; */
} UNWIND_INFO, *PUNWIND_INFO;
EXTERN_C  UINT64 EFIAPI FindRuntimeFunction(UINT64 modbase,UINT64 fakefun)
{	
	FuncInfo4 FuncInfoDe;		
	UINT32 fakefunstart = (UINT64)fakefun - modbase;	;
	for (PRUNTIME_FUNCTION RuntimeFunctionEntry = pImageRuntimeFunctionDirectory; RuntimeFunctionEntry < pImageRuntimeFunctionDirectoryEnd; RuntimeFunctionEntry++)
	{
		if (RuntimeFunctionEntry->BeginAddress + modbase <= fakefun && RuntimeFunctionEntry->EndAddress + modbase > fakefun)
		{			
			PUNWIND_INFO UnwindDatainfo = (PUNWIND_INFO)(modbase + RuntimeFunctionEntry->u.UnwindInfoAddress);
			PUNWIND_CODE UnwindCode = (PUNWIND_CODE)((UINT64)UnwindDatainfo + sizeof(UNWIND_INFO));
			int len = ((UnwindDatainfo->CountOfCodes + 1) & ~1) + 2;
			UINT32 ExceptionData = *(UINT32*)(UnwindCode + len);		
			uint8_t* buffer = (uint8_t*)modbase + ExceptionData;
			DecompFuncInfo(buffer, FuncInfoDe, modbase, RuntimeFunctionEntry->BeginAddress);		
			TryBlockMap4 trymap(&FuncInfoDe, modbase);	
			HandlerMap4 handlermap(&trymap._tryBlock, modbase, RuntimeFunctionEntry->BeginAddress);	
			UINT64 realHandler = handlermap._handler.dispOfHandler + modbase;			
	     	实际上就是返回OutputStringWrapper$catch$地址
			return (UINT64)realHandler;
		}
	}
	return 0;
}

在抛出异常时可以在idt中获取当前抛出异常原的栈rsp和函数地址rip,对于x86windows应用程序由内核idt分发到用户层teb结构的异常处理分发函数执行,不过在uefi环境并没有teb.根据以上代码在异常rip找到异常捕获回调,根据rsp向上迭代直到找到一个符合条件运行时函数PIMAGE_RUNTIME_FUNCTION作为当前异常捕获回调的上层函数返回地址,
当前迭代到的这个rsp它的值也同样作为异常捕获回调分配的rsp地址,对这个rsp完成prolog例程执行后,执行完异常捕获回调会自动从栈中弹出异常捕获函数所在主函数的上层调用地址,返回到这个地址继续执行程序,实现了异常修复工作.
至此就完成了实现在uefi平台上模拟vcruntime实现的c++异常捕获特性功能.这种方式并不和调试器模式冲突,可以在idt中判断异常的int3向量触发的话中断到调试器,如果异常处理函数未找到或者处理失败情况下中断到调试器,
否则调用异常捕获回调函数恢复继续执行.

Windows Hyper-V虚拟机Vmbus通道分析

笔者用.net代码重构了用c代码实现的vmbus通道接口功能,hyper-v包含很多内置的uefi模块,同样需要使用simp和siep通信页面,所以在运行笔者uefi程序时获取这些msr寄存器是有值的,
使用原来的共享页面有个问题就是当退出笔者uefi程序的时候由于旧sint被笔者使用了导致内置的uefi模块的控制台功能也需要借助通道页面通信,存在冲突问题导致控制台卡住.
但是笔者后来发现了一种方法可以解决这个问题正常退出的uefi控制台,具体实现方式是如果旧uefi模块通道的sint为2,笔者使用一个新的sint比如说5,为这个sint分配一个新的idt回调向量同样可以收到vsp发来的触发通道消息,
在CHANNELMSG_INITIATE_CONTACT协商消息中使用绑定这个sint,这样可以在回复消息中vmbus_channel_version_response->messageConnectionId得到一个新的event连接id,
这样就可以用在后续的调用HvCallPostMessage使用使用这个新的连接id,后续通信的gpadl页面使用新申请的页面,实现了不和内置的uefi模块同时共享通道冲突解决方法.
笔者uefi程序退出后控制台可以正常使用,对后续其他uefi程序没有影响.

Vmbus通道分析见作者上篇文章windbg原生调试协议在hyper-v平台uefi上多种实现方式探索

编译运行方式

这个项目是一种uefi驱动编译工程,使用vs2022编译,工程生成后选择发布NativeAOT模式,不需要依赖edk2静态库文件,笔者项目只支持hyper-v不支持vmware和qemu

如果看到Windbg\bin\Release\net7.0\publish\win-x64\Windbg.exe文件表示发布成功

DiskGenius新建虚拟硬盘文件.img 自定义256mb 快速分区guid模式

自定义1个分区建立esp分区建立msr分区esp大小200mb

esp分区添加

制作iso文件shell.efi放到esp分区这个位置重命名

\EFI\BOOT\bootx64.efi

创建如下文件

\EFI\BOOT\startup.nsh

内容如下

load fs0:windbg.efi

编译出来的windbg.efi放到esp分区根目录下

可以使用作者iso制作工具制作iso

ISOTOOL.exe “pathto\EspPartitionDir” “pathto\IsoFilePath.iso”

或者使用如下powershell命令制作hyper-v的vhdx磁盘文件

$psdir = Split-Path $MyInvocation.MyCommand.Path -Parent
$vhdxpath = Join-Path $psdir "hv.vhdx"
$uefishell = Join-Path $psdir "bootx64.efi"
$uefistartup = Join-Path $psdir "startup.nsh"
$uefiapp = Join-Path $psdir "windbg.efi"
$uefidir= "N:\EFI\BOOT\"
Write-Host $vhdxpath
$disk = New-VHD -Path $vhdxpath -Dynamic -SizeBytes 10GB  | Mount-VHD -PassThru | Get-Disk
Initialize-Disk -Number $disk.Number -PartitionStyle GPT
New-Partition -DiskNumber $disk.Number -Size 500MB -GptType "{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}" -DriveLetter "N"
Format-Volume -DriveLetter  "N" -FileSystem FAT32 -Force -Confirm:$false
New-Item -ItemType "directory" -Path $uefidir
Copy-Item $uefishell  -Destination $uefidir
Copy-Item $uefistartup  -Destination $uefidir
Copy-Item $uefiapp   -Destination "N:\"
Dismount-VHD -Path  $vhdxpath

更新vhdx文件中的主程序windbg.efi可以参考笔者bat脚本,vhdx挂载k盘

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -exec bypass -Command "Stop-VM -Name hv -TurnOff -Force"
"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\symstore.exe" add /r /f "E:\git\UefiAotHyperV\WindbgUefiSharp\Windbg\bin\Release\net7.0\win-x64\publish" /s "%SYMBOL_STORE%" /t niii
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -exec bypass -Command " Mount-VHD -Path 'F:\hyperv\hv\Virtual Hard Disks\hv.vhdx'"
copy "E:\git\UefiAotHyperV\WindbgUefiSharp\Windbg\bin\Release\net7.0\win-x64\native\Windbg.exe" "K:\Windbg.efi"  /Y
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -exec bypass -Command "Dismount-VHD 'F:\hyperv\hv\Virtual Hard Disks\hv.vhdx'"

虚拟机选项第二代虚拟机,关闭安全引导,挂载iso或者vhdx设为首选启动项

编译pipe工程,注意必须编译为x64模式:

运行windbg调试协议wireshark抓包解析插件pipe.exe

pathto\bin\x64\Debug\pipe.exe spy windbg bacnet auto vmname

启动"C:\Program Files\Wireshark\Wireshark.exe" -ni \.\pipe\bacnet

选择\.\pipe\bacnet进入实时捕捉模式

等待windbg调试器中断

历史记录数据在pipe.exe目录下可以找到pcap文件

运行效果

以下是笔者工具运行的效果,如图:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

相关引用

原作者工具wireshark抓包解析插件

作者工具

VisualUefi

edk2

Windows Hypervisor Platform API

VirtualKD-Redux-windbg实现

reactos-windbg实现

VmBusDriver

pxe-vmbus

linux-vmbus

mac-vmbus

VmBusPipe

Hyper-V socket

edk2-hv-net项目

原.NET NativeAOT项目

vcruntime开源版

vcruntime实现的c++异常捕获

UNWIND_INFO分析

windbg原生调试协议在hyper-v平台uefi上多种实现方式探索

作者iso制作工具

作者工具c项目

作者工具主项目

参与贡献

作者来自ZheJiang Guoli Security Technology,邮箱cbwang505@hotmail.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1976903.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

江科大/江协科技 STM32学习笔记P17

文章目录 一、TIM输入捕获输入捕获与输出比较的关系频率测量测频法测周法 输入捕获的电路异或门的执行逻辑 输入捕获通道主从触发模式输入捕获基本结构PWMI基本结构输入捕获模式测频率main.c 输入捕获模式测占空比main.c 一、TIM输入捕获 输入捕获与输出比较的关系 在输出比较中…

C语言第13篇

1.下面程序是计算n个数的平均值,请填空.______ #include<stdio.h> void main( ) { int i,n; float x,avg0.0; scanf("%d",&n); for(i0;i<n;i) { scanf("%f",&x); avgavg______; } avg________; printf("avg%f\n",avg); } A) …

[Git][分支管理][上]详细讲解

目录 1.理解分支2.创建分支3.切换分支4.合并分支5.删除分支 1.理解分支 感性理解&#xff1a;分支可以理解为平行宇宙&#xff0c;但是在用户需要的时候&#xff0c;可以将两个平行宇宙合并&#xff0c;此时两个平行宇宙的效果将会"叠加"理性理解&#xff1a;每次提…

树、二叉树、森林的转换

一、树和二叉树的转换 1、 2、 二、森林二叉树的转换 1、 2、

springboot医嘱管理系统-计算机毕业设计源码16053

摘 要 随着医疗信息化水平的不断提升&#xff0c;医嘱管理作为医院日常运营中不可或缺的一环&#xff0c;其重要性日益凸显。传统的医嘱管理方式往往存在效率低下、易出错等问题&#xff0c;已无法满足现代医疗服务的快速发展需求。因此&#xff0c;基于Spring Boot框架开发的…

MySQL-InnoDB引擎

目录 逻辑存储结构 架构 概述 内存结构 Buffer Pool&#xff08;缓冲池&#xff09; Change Buffer&#xff08;更改缓冲区&#xff09; Adaptive Hash Index&#xff08;自适应hash索引&#xff09; Log Buffer&#xff08;日志缓冲区&#xff09; 磁盘结构 System T…

基于CUDA12.1+CUDNN8.9+PYTORCH2.3.1,实现自定义数据集训练

目录 0 结果预览 1 核心点 2 参考链接 0 结果预览 1 核心点 yolo命令行CL需要将虚拟环境的yolo程序加入系统路径。 遇到conda install 失效问题&#xff0c;重建新的虚拟环境&#xff0c;再进行安装。 whl可以下载好后再安装。 pip install F:\tool\ai\torch-2.3.1cu…

使用VM安装K8S

VM 部署K8S 前言 本次使用VM搭建k8s&#xff0c;由于搭建流程复杂&#xff0c;在此记录。 需提前安装好VM&#xff08;可参考&#xff1a;VM安装&#xff09;&#xff0c;起两台虚拟机(模拟master和worker)&#xff0c;且VM里已安装好Docker&#xff08;可参考&#xff1a;D…

GO语言 4 收集器

劳苦功高的数组 声明数组并访问其元素 以下数组不多不少正好包含 8 个元素 var planets [8]string同一个数组中的每个元素都具有相同的类型&#xff0c;比如以上代码就是由 8 个字符串组成&#xff0c;简称字符串数组。 数组的长度可以通过内置的 len 函数确定。在声明数组…

03。仓颉程序结构

1&#xff0c;什么是 仓颉的“源代码”、“源文件”&#xff1f; ~通常以 “ .cj ” 为后缀的文本文件里面包含的仓颉代码&#xff0c;我们称为仓颉源代码、源文件。 / / test.txt --------------- test.cj 2&#xff0c;什么是 “ 作用域” &#xff1f;“嵌套作…

C/C++关键字大全

目录 一、const 二、static 三、#define 和 typedef 四、#define 和 inline 五、#define 和 const 六、new 和 malloc 七、const 和 constexpr 八、volatile 九、extern 十、前置 和后置 十一、atomic 十二、struct 和 class 一、const 1、const 关键字可用于定义…

在vscode中远程连接linux进行开发

目录 引言 配置过程 1.本机安装OpenSSH 2.本机生成RSA公钥和私钥 3.将rsa公钥添加到远程linux的authorized_keys文件中 4.vscode安装Remote - SSH插件 5.在vscode中ssh连接服务器 6.在本地vscode操作远程linux文件进行开发 7.在vscode上给远程linux机器需安装插件 常…

路径规划——贪婪最佳优先搜索

路径规划——贪婪最佳优先搜索 学习A算法前先学习一下贪婪最佳优先搜索算法&#xff0c;在学习过程中在网上找了一些博客、文章还有视频来看以深入理解此算法&#xff0c;但是实际情况却是非常令人恼怒。有些文章标题是贪婪最佳优先搜索&#xff0c;内容却是其他的算法&#x…

【密码学】椭圆曲线密码体制(ECC)

椭圆曲线密码体制&#xff08;Elliptic Curve Cryptography, ECC&#xff09;是一种基于椭圆曲线数学特性的公钥密码系统。在介绍椭圆曲线之前&#xff0c;我们先来了解一下椭圆曲线的基本概念。 一、椭圆曲线是什么&#xff1f; &#xff08;1&#xff09;椭圆曲线的数学定义…

PolSARPro软件安装处理TerraSAR数据(CSDN_20240804)

1. 打开polSARPro软件&#xff0c;点击Enter 2. 点击OK 3. 点击左侧第一个图像&#xff0c;进入PolSARPro Bio。 4. 点击Enter. 5. 点击Environment&#xff0c;选择Single Data Set 6. 选择工作路径 7. 点击No 8. Import -> Spaceborne Sensors ->TerraSAR-X->Quad-P…

C++的vector类

目录 简介 特点 接口 构造函数 迭代器函数 Capacity系列 element access系列 modifiers系列 定义在全局的重载函数 find 总结 简介 vector 是 C 标准模板库&#xff08;Standard Template Library&#xff0c;简称 STL&#xff09;中的一个模板类&#xff0c;用于表…

【iOS】——GCD总结

同步和异步的区别 同步执行等待操作完成&#xff0c;而异步执行允许程序在操作完成前继续运行&#xff0c;提高了效率和响应性。这里的关键就是上一个操作需不需要等待当前操作的执行&#xff0c;如果需要就是同步&#xff0c;如果不需要就是异步。 异步有开启新线程的能力但…

如何构建AI产品:OpenAI与前Shopify产品负责人Miqdad Jaffer的经验分享

一、引言 构建AI产品是一项复杂且充满挑战的任务&#xff0c;尤其是当涉及到面向消费者的解决方案时。在最近的一期播客节目中&#xff0c;OpenAI 和前Shopify产品负责人 Miqdad Jaffer 分享了他在构建AI产品的经验和策略。下面我们将探讨构建AI产品的最佳实践&#xff0c;以及…

行为型设计模式1:状态/策略/命令

行为型设计模式&#xff1a;状态/策略/命令 (qq.com)

【秋招笔试】24-08-03-米哈游-秋招提前批笔试题

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍰 米哈游提前批笔试也是来了,本次题目…