VT虚拟化框架编写

news2024/12/28 3:36:45

文章目录

      • 前言
      • VT架构基础
      • VT框架编写步骤一:检测VT是否开启
      • VMM和VM
      • VMON和VMCS
      • VT框架编写步骤二 填充VMON
      • VT框架编写步骤三 进入VT
      • VT框架编写步骤四 初始化VMCS
      • VT框架编写步骤五 初始化VMCS数据区
      • VT框架编写步骤六 处理必要事件

前言

学习VT相关的知识,需要具备WIN32基础和内核相关的知识,包括Windows段机制,页机制等等,VT里面都会有所涉及,也就是得把Windows编程和驱动基本通关,新手建议劝退。

整个VT的知识结构有大量的新的概念,新的寄存器,新的指令还有各种标志位,初次学习VT建议结合代码来理解相关的理论基础,重点在于梳理整个VT的流程,不要深究具体的某个细节。整个流程走完之后就有了阅读别人代码的能力,等实际应用的时候再去查阅相关资料补齐技术细节的部分。我的笔记,很多能省的基本都省掉了,只把整体必须的流程整理出来。

VT架构基础

**什么是VT:**我理解的VT是将操作系统运行在自己模拟的一个虚拟环境之上,然后通过这个来截获所有的事件和中断,达到过反调试和过保护的效果,可以理解为一个HOOK系统吧,毕竟也是通过拦截的方式来处理事件。

三种虚拟化技术:

  1. 基于处理器的虚拟化VT-x
  2. 基于PCI总线设备实现的IO虚拟化 VT-d
  3. 基于网络的虚拟化 VT-c

目前主要研究的是基于处理器的虚拟化技术VT-x

VT框架编写步骤一:检测VT是否开启

编写VT框架的第一步,就是用代码实现判断当前环境是否支持VT。这里有三个地方需要进行判断

  1. 首先检测CPUID是否支持VT,ecx第六位,如果为1支持VT,否则不支持
typedef union _CPUID_ECX
{
	struct
	{
		unsigned SSE3 : 1;
		unsigned PCLMULQDQ : 1;
		unsigned DTES64 : 1;
		unsigned MONITOR : 1;
		unsigned DS_CPL : 1;
		unsigned VMX : 1;
		unsigned SMX : 1;
		unsigned EIST : 1;
		unsigned TM2 : 1;
		unsigned SSSE3 : 1;
		unsigned Reserved : 22;
	};

}CPUID_ECX,*PCPUID_ECX;

//检测CPUID是否支持VT
BOOLEAN VmxCheckCPUIDIsSupportVT()
{
	int cpuidInfo[4];
	__cpuidex(cpuidInfo, 1, 0);

	PCPUID_ECX cpuid = &cpuidInfo[2];
	
	return cpuid->VMX;
}

尽量用这种结构体位判断的写法,不要用左移右移的那种算法,比较直观,可读性好。

  1. 检测BIOS是否支持开启VT,在MSR寄存器里面
typedef union _IA32_FEATURE_CONTROL_MSR
{
	ULONG Value;
	struct
	{
		unsigned Lock : 1;		// Bit 0 is the lock bit - cannot be modified once lock is set
		unsigned Reserved1 : 1;		// Undefined
		unsigned EnableVmxon : 1;		// Bit 2. If this bit is clear, VMXON causes a general protection exception
		unsigned Reserved2 : 29;	// Undefined
		unsigned Reserved3 : 32;	// Undefined
	}Fields;

} IA32_FEATURE_CONTROL_MSR,*PIA32_FEATURE_CONTROL_MSR;

//检测BIOS是否开启VT
BOOLEAN VmxCheckBIOSIsSupportVT()
{
	IA32_FEATURE_CONTROL_MSR msr;
	msr.Value = __readmsr(IA32_FEATURE_CONTROL);

	return msr.Fields.Lock;
}
  1. 检测CR4.VMXE位,这个位如果被设置为1代表有VT已经开启了,你不能再开启,否则可以开启
BOOLEAN VmxCheckCR4IsSupportVT()
{
	_CR0 cr0 = { .Value = __readcr0() };
	_CR4 cr4 = { .Value = __readcr4() };
	
	if (cr0.Fields.PE != 1 || cr0.Fields.PG != 1 || cr0.Fields.NE != 1)
	{
		return FALSE;
	}

	if (cr4.Fields.VMXE==1)
	{
		return FALSE;
	}

	return TRUE;
}

VMM和VM

在VMX架构下定义了两类软件的角色和环境

  • VMM 虚拟机监管者
  • VM 虚拟机

VMM代表一类在VMX架构下的管理者角色,拥有控制权,监管每个VM的运行。

VM代表虚拟机实例,一个VMX架构可以有多个实例,每个VM都是一套独立的环境,且VM本身不会意识到自己运行在虚拟机的环境里。

host端对应VMM,guest端对应VM

VMON和VMCS

在这里插入图片描述

在VMX架构下,至少有一个VMON和VMCS的物理区域,一个VMM可以有多个VM,所以也可以有多块区域。

VMON区域是给VMM用的,VMM使用这块区域对数据进行记录和维护;每个VM也需要有自己的VMCS区域。VMM使用VMCS区域来配置VM的运行环境

VT框架编写步骤二 填充VMON

在进入VMX模式前,必须为VMM准备一份VMXON区域,和VMCS区域,并配置VM的运行环境

填充VMON也分为三个步骤:

  1. 进入前的相关寄存器设置
  2. 申请VMON物理内存
  3. 初始化VMON区域

相关寄存器设置:

  1. CR0寄存器需要满足PG位 NE位 PE位都为1
  2. CR4需要开启VMXE位

代码如下:

	//初始化CR0 CR4
	ULONG64 vcr00 = __readmsr(IA32_VMX_CR0_FIXED0);
	ULONG64 vcr01 = __readmsr(IA32_VMX_CR0_FIXED1);
	ULONG64 vcr04 = __readmsr(IA32_VMX_CR4_FIXED0);
	ULONG64 vcr14 = __readmsr(IA32_VMX_CR4_FIXED1);

	ULONG64 mcr4 = __readcr4();
	ULONG64 mcr0 = __readcr0();

	mcr4 |= vcr04;
	mcr4 &= vcr14;

	mcr0 |= vcr00;
	mcr0 &= vcr01;

	//修改CR0 CR4
	__writecr0(mcr0);
	__writecr4(mcr4);

申请VMON物理内存

接着需要分配一块物理内存作为VMON区域,以4KB对齐,大小和内存cache类型可以通过检IA32_VMX_BASIC寄存器来获得

	//给VMON申请一块内存
	PHYSICAL_ADDRESS lowphys,highphy;
	lowphys.QuadPart = 0;
	highphy.QuadPart = -1;
	pVcpucb->VmxOnAddr = MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE,
		lowphys, highphy, lowphys, MmCached);
	if (!pVcpucb->VmxOnAddr)
	{
		return -1;
	}


	RtlZeroMemory(pVcpucb->VmxOnAddr, PAGE_SIZE);

	//获取物理地址
	pVcpucb->VmxOnAddrPhys = MmGetPhysicalAddress(pVcpucb->VmxOnAddr);

初始化VMON区域

VMXON区域前四个字节是VMCS ID,下一个四字节是VMX- abort indicator字段(这个不需要我们填),如图(其实就是一个指针):

在这里插入图片描述

VMCS ID的值可以通过IA32_VMX_BASIC获取,执行VMXON指令前必须将这个ID写入第一个四字节的位置。示例代码如下:

	//读IA32_VMX_BASIC寄存器
	ULONG64 vmxBasic= __readmsr(IA32_VMX_BASIC);

	//填充ID
	*(PULONG)pVcpucb->VmxOnAddr = (ULONG)vmxBasic;

VT框架编写步骤三 进入VT

处理器进入VMX模式需要执行VMXON指令,而这个指令需要提供一个指向VMXON区域的物理指针

int error= __vmx_on(&pVcpucb->VmxOnAddrPhys.QuadPart);

成功调用__vmx_on指令,表示处理器已经成功进入VMX Operation模式,也就是已经处于root环境。到这里我们就已经进入VT环境了,上来一堆概念和乱八七糟的字段标志已经给我干蒙了,没办法,先把流程整理清楚,后面再慢慢理解。

VT框架编写步骤四 初始化VMCS

进入VT之后,我们还需要再设置一些结构。VMCS结构存放在一个物理区域内,也是4KB对齐,有一个8字节的头部额VMCS数据区域。

在这里插入图片描述

VMCS的前8个字节也是由IA32_VMX_BASIC寄存器得到,VMCS的初始化代码和VMON区域几乎差不多

//初始化VMCS区域
int VmxInitVmcs()
{
	//获取当前核
	PVMXCPUPCB pVcpucb = VmxGetCurrentCpucb();

	//给VMCS申请一块内存
	PHYSICAL_ADDRESS lowphys, highphy;
	lowphys.QuadPart = 0;
	highphy.QuadPart = -1;
	pVcpucb->VmxCsAddr = MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE,lowphys, highphy, lowphys, MmCached);
	if (!pVcpucb->VmxCsAddr)
	{
		return -1;
	}
	RtlZeroMemory(pVcpucb->VmxCsAddr, PAGE_SIZE);

	//获取物理地址
	pVcpucb->VmxCsAddrPhys = MmGetPhysicalAddress(pVcpucb->VmxCsAddr);

	//读IA32_VMX_BASIC寄存器
	ULONG64 vmxBasic = __readmsr(IA32_VMX_BASIC);

	//填充ID
	*(PULONG)pVcpucb->VmxCsAddr = (ULONG)vmxBasic;

	//清空以前的状态
	__vmx_vmclear(&pVcpucb->VmxCsAddrPhys.QuadPart);

	//加载新的VMCS
	__vmx_vmptrld(&pVcpucb->VmxCsAddrPhys.QuadPart);
}

VT框架编写步骤五 初始化VMCS数据区

上面我们的VMCS区域还没有初始化完成,只是初始化了前8个字节,还有一块数据区需要进行初始化。

VMCS的数据区包括了6个区域:

  1. guest-state区域,保存处理器的状态信息
  2. host-state区域,保存处理器的状态信息
  3. VM-execution控制区域,这里就是VM控制区了,可以用来设置和拦截中断
  4. VM-exit控制区域,处理退出VM的行为
  5. VM-entry控制区域,处理进入VM的行为
  6. VM-exit信息区域,记录退出VM事件的原因和相关信息和错误码

这六个数据区,都要一一进行初始化设置。

访问VMCS字段

软件必须通过VMREAD和VMWRITE来访问VMCS字段,每个字段定义一个唯一ID值来对应,这个字段ID值提供给VMREAD作为index值来访问对应字段。

在这里插入图片描述

关于字段的值和具体含义,这个不需要记,有定义好的宏配合__vmx_vmwrite函数,直接拿来用就行了。

初始化VMHost和VMGuest就是把这些乱八七糟的位都给他置上对应的值,太多了,就不一个个拆开细讲了。


void VmxInitVmGuest(ULONG64 GuestEip, ULONG64 GuestEsp)
{
	//填充GDT表项
	FillGdtDataItem(0, AsmReadES());
	FillGdtDataItem(1, AsmReadCS());
	FillGdtDataItem(2, AsmReadSS());
	FillGdtDataItem(3, AsmReadDS());
	FillGdtDataItem(4, AsmReadFS());
	FillGdtDataItem(5, AsmReadGS());
	FillGdtDataItem(6, AsmReadLDTR());


	//取GDT表
	GdtTable gdtTable = { 0 };
	AsmGetGdtTable(&gdtTable);

	ULONG trSelector = AsmReadTR();

	//可能是三环的 要处理下
	trSelector &= 0xFFF8;

	ULONG trlimit = __segmentlimit(trSelector);

	LARGE_INTEGER trBase = { 0 };
	PULONG trItem = (PULONG)(gdtTable.Base + trSelector);

	//读TR.Base
	trBase.LowPart = ((trItem[0] >> 16) & 0xFFFF)|((trItem[1]&0xFF)<<16)| (trItem[1] & 0xFF000000);
	trBase.HighPart = trItem[2];

	//属性
	ULONG attr = (trItem[1] & 0x00F0FF00) >> 8;


	__vmx_vmwrite(GUEST_TR_BASE, trBase.QuadPart);
	__vmx_vmwrite(GUEST_TR_LIMIT , trlimit);
	__vmx_vmwrite(GUEST_TR_AR_BYTES , attr);
	__vmx_vmwrite(GUEST_TR_SELECTOR, trSelector);


	__vmx_vmwrite(GUEST_CR0, __readcr0());
	__vmx_vmwrite(GUEST_CR3, __readcr3());
	__vmx_vmwrite(GUEST_CR4, __readcr4());
	__vmx_vmwrite(GUEST_DR7, __readdr(7));
	__vmx_vmwrite(GUEST_RFLAGS, __readeflags());
	__vmx_vmwrite(GUEST_RSP, GuestEsp);
	__vmx_vmwrite(GUEST_RIP, GuestEip);

	__vmx_vmwrite(GUEST_SYSENTER_CS, __readmsr(0x174));
	__vmx_vmwrite(GUEST_SYSENTER_ESP, __readmsr(0x175));
	__vmx_vmwrite(GUEST_SYSENTER_EIP, __readmsr(0x176));


	__vmx_vmwrite(GUEST_IA32_DEBUGCTL, __readmsr(IA32_MSR_DEBUGCTL));
	__vmx_vmwrite(GUEST_IA32_PAT, __readmsr(IA32_MSR_PAT));
	__vmx_vmwrite(GUEST_IA32_EFER, __readmsr(IA32_MSR_EFER));
	__vmx_vmwrite(GUEST_FS_BASE, __readmsr(IA32_FS_BASE));
	__vmx_vmwrite(GUEST_GS_BASE, __readmsr(IA32_GS_BASE));
	__vmx_vmwrite(VMCS_LINK_POINTER, -1);


	//IDT GDT
	GdtTable idtTable;
	__sidt(&idtTable);

	__vmx_vmwrite(GUEST_GDTR_BASE, gdtTable.Base);
	__vmx_vmwrite(GUEST_GDTR_LIMIT, gdtTable.limit);

	__vmx_vmwrite(GUEST_IDTR_BASE, idtTable.Base);
	__vmx_vmwrite(GUEST_IDTR_LIMIT, idtTable.limit);
}

初始化Host也跟上面的代码类似。

初始化VM_ENTRY

void VMInitEntry()
{
	ULONG64 vmxBasic = __readmsr(IA32_VMX_BASIC);

	ULONG64 mseregister = ((vmxBasic >> 55) & 1) ? IA32_MSR_VMX_TRUE_ENTRY_CTLS : IA32_VMX_ENTRY_CTLS;

	ULONG64 value= VmxAdjustContorls(0x200, mseregister);
	__vmx_vmwrite(VM_ENTRY_CONTROLS, value);
	__vmx_vmwrite(VM_ENTRY_MSR_LOAD_COUNT, 0);
	__vmx_vmwrite(VM_ENTRY_INTR_INFO_FIELD, 0);
}

初始化VM_EXIT

void VMInitExit()
{
	ULONG64 vmxBasic = __readmsr(IA32_VMX_BASIC);

	ULONG64 mseregister = ((vmxBasic >> 55) & 1) ? IA32_MSR_VMX_TRUE_EXIT_CTLS : IA32_MSR_VMX_TRUE_EXIT_CTLS;

	ULONG64 value = VmxAdjustContorls(0x200|0x8000, mseregister);
	__vmx_vmwrite(VM_EXIT_CONTROLS, value);
	__vmx_vmwrite(VM_EXIT_MSR_LOAD_COUNT, 0);
	__vmx_vmwrite(VM_EXIT_INTR_INFO, 0);
}

初始化控制区


//初始化VM控制区
void VMInitControls()
{
	ULONG64 vmxBasic = __readmsr(IA32_VMX_BASIC);

	ULONG64 mseregister = ((vmxBasic >> 55) & 1) ? IA32_MSR_VMX_TRUE_PINBASED_CTLS : IA32_MSR_VMX_PINBASED_CTLS;

	ULONG64 value = VmxAdjustContorls(0, mseregister);
	__vmx_vmwrite(PIN_BASED_VM_EXEC_CONTROL, value);


	mseregister = ((vmxBasic >> 55) & 1) ? IA32_MSR_VMX_TRUE_PROCBASED_CTLS : IA32_MSR_VMX_PROCBASED_CTLS;
	value = VmxAdjustContorls(0, mseregister);
	__vmx_vmwrite(CPU_BASED_VM_EXEC_CONTROL, value);
}

至于为什么要这么初始化,我也不知道,先把流程整理出来再说。到这里整个VT框架就完成了,下一步就可以开始拦截事件。

VT框架编写步骤六 处理必要事件

到这里我们的框架就已经完成了,现在可以开始处理自己想要拦截的事件。但是在这些事件里面,有一些事件是我们必须要处理掉的,我们写完这一部分的代码,才能开始拦截我们自己想要拦截的事件

在这里插入图片描述

如上图所示,上图中的指令引起的VM-exit事件,就是我们必须要处理掉的,代码如下:

EXTERN_C VOID VmxExitHandler(PGuestContext context)
{
	ULONG64 reason = 0;
	ULONG64 intstLen = 0;
	ULONG64 instinfo = 0;
	ULONG64 mrip = 0;
	ULONG64 mrsp = 0;

	//退出事件码
	__vmx_vmread(VM_EXIT_REASON, &reason);

	//指令长度
	__vmx_vmread(VM_EXIT_INSTRUCTION_LEN, &intstLen);

	//指令详细信息
	__vmx_vmread(VMX_INSTRUCTION_INFO, &instinfo);

	//获取客户机触发VT事件的地址
	__vmx_vmread(GUEST_RIP, &mrip);
	__vmx_vmread(GUEST_RSP, &mrsp);


	//拿退出码的前16位 对下面的事件进行处理
	reason = reason & 0xFFFF;


	switch (reason)
	{
	case EXIT_REASON_CPUID:
		VmxExitHandlerCpuid(context);
		break;
	case EXIT_REASON_GETSEC:
	{
		DbgBreakPoint();
		DbgPrint("EXIT_REASON_GETSEC reson:%x rip:%llx\n",reason,mrip);
	}
		break;
	case EXIT_REASON_TRIPLE_FAULT:
	{
		DbgBreakPoint();
		DbgPrint("EXIT_REASON_TRIPLE_FAULT reson:%x rip:%llx\n", reason, mrip);
	}
	break;
	case EXIT_REASON_INVD:
	{
		AsmInvd();
	}
	break;
	case EXIT_REASON_VMCALL:
	case EXIT_REASON_VMCLEAR:
	case EXIT_REASON_VMLAUNCH:
	case EXIT_REASON_VMPTRLD:
	case EXIT_REASON_VMPTRST:
	case EXIT_REASON_VMREAD:
	case EXIT_REASON_VMRESUME:
	case EXIT_REASON_VMWRITE:
	case EXIT_REASON_VMXOFF:
	case EXIT_REASON_VMXON:
	{
		//统一返回错误,执行我们的VT后 就不往下执行了
		ULONG64 rflags = 0;
		__vmx_vmread(GUEST_RFLAGS, &rflags);
		rflags |= 0x41;
		__vmx_vmwrite(GUEST_RFLAGS, &rflags);
	}
	break;
	case EXIT_REASON_MSR_READ:
	{
		VmxExitHandlerReadMsr(context);
	}
		break;
	case EXIT_REASON_MSR_WRITE:
	{
		VmxExitHandlerWriteMsr(context);
	}
		break;
	case EXIT_REASON_XSETBV:
	{
		ULONG64 value = MAKE_REG(context->mRax , context->mRdx);
		_xsetbv(context->mRcx, value);
	}
	break;

	default:
		break;
	}

	//写入RIP和RSP  让VT能正确返回
	__vmx_vmwrite(GUEST_RIP, mrip+intstLen);
	__vmx_vmwrite(GUEST_RSP, mrsp);

}

到这里,我们的VT框架必须填充的部分就已经完成了。


VOID VmxHandlerCpuid(PGuestContext context)
{
	if (context->mRax == 0x8888)
	{
		context->mRax = 0x11111111;
		context->mRbx = 0x22222222;
		context->mRcx = 0x33333333;
		context->mRdx = 0x44444444;
	}
	else 
	{
		int cpuids[4] = {0};
		__cpuidex(cpuids,context->mRax, context->mRcx);
		context->mRax = cpuids[0];
		context->mRbx = cpuids[1];
		context->mRcx = cpuids[2];
		context->mRdx = cpuids[3];
	}
}

这里我们加一个测试的代码,拦截cpuid指令,然后修改周围的寄存器。

在这里插入图片描述

这里比较蛋疼的一个点,框架部分代码一旦出现错误没法调试,也没有错误信息,直接就是虚拟机关闭。

而且这个部分的代码都是初始化和设置各种标志位和字段,能单步调试,其实也没有多大意义,根本看不出来异常,只能一行一行代码对照。

在这里插入图片描述

如果你的VT框架可以正常加载,并且执行这两条指令以后,寄存器被修改,说明事件可以正常拦截,那么恭喜各位框架搭建成功。

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

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

相关文章

C++11新特性

文章目录说在前面花括号{}初始化new的列表初始化STL相关容器的列表初始化相关语法格式容器列表初始化的底层原理forward_list和array与类型相关的新特性decltype左值引用和右值引用什么是左值&#xff0c;什么是右值左值和右值的本质区别右值引用如何理解右值引用std::move移动…

【软考系统架构设计师】2022下综合知识历年真题

【软考系统架构设计师】2022下综合知识历年真题 【2022下架构真题第01题&#xff1a;绿色】 01.云计算服务体系结构如下图所示&#xff0c;图中①、②、③分别与SaaS、PaaS、Iaas相对应&#xff0c;图中①、②、③应为( ) A.应用层、基础设施层、平台层 B.应用层、平台层、基础…

Linux驱动开发(一)

linux驱动学习记录 一、背景 在开始学习我的linux驱动之旅之前&#xff0c;先提一下题外话&#xff0c;我是一个c语言应用层开发工作人员&#xff0c;在工作当中往往会和硬件直接进行数据的交互&#xff0c;往往遇到数据不通的情况&#xff0c;常常难以定位&#xff0c;而恰巧…

静态分析工具Cppcheck在Windows上的使用

之前在https://blog.csdn.net/fengbingchun/article/details/8887843 介绍过Cppcheck&#xff0c;那时还是1.x版本&#xff0c;现在已到2.x版本&#xff0c;这里再总结下。 Cppcheck是一个用于C/C代码的静态分析工具&#xff0c;源码地址为https://github.com/danmar/cppcheck …

Python之字符串精讲(上)

前言 字符串是所有编程语言在项目开发过程中涉及最多的一个内容。大部分项目的运行结果&#xff0c;都需要以文本的形式展示给客户&#xff0c;曾经有一位久经沙场的老程序员说过一句话&#xff1a;“开发一个项目&#xff0c;基本上就是在不断的处理字符串”。下面对Python中…

自命为缓存之王的Caffeine(3)

您好&#xff0c;我是湘王&#xff0c;这是我的CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e;缓存的存储空间是远远小于磁盘的。所以对于有些过期的数据&#xff0c;就需要定期进行清理&#xff0c;腾出存储空间。Caffeine又是怎么做的呢&#xff1f;Caffei…

SpringBoot+Vue在线小说系统

简介&#xff1a;本项目采用了基本的springbootvue设计的在线小说系统。详情请看截图。经测试&#xff0c;本项目正常运行。本项目适用于Java毕业设计、课程设计学习参考等用途。 特别说明&#xff1a;本系统设计网络爬虫&#xff0c;遵循爬虫规则&#xff0c;此项目用于学习&a…

2023关键词:挑战

未失踪人口回归… 好久不见&#xff0c;不经意间拖更2个多月。今天周末&#xff0c;外面淅淅沥沥下着小雨&#xff0c;这种窝在床上的时刻最适合写点东西了。 但是建议大家在办公或者写博客的时候尽量还是端正坐姿&#xff0c;我就是因为喜欢这样靠在床背上&#xff0c;长时间…

Spring Security 从入门到精通

前言 Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro&#xff0c;它提供了更丰富的功能&#xff0c;社区资源也比Shiro丰富。 一般来说中大型的项目都是使用SpringSecurity 来做安全框架。小项目有Shiro的比较多&#xff0c;因为相比与Spr…

Vue3+ElementPlus+koa2实现本地图片的上传

一、示例图二、实现过程利用Koa2书写提交图片的后台接口这个模块是我写的项目中的其中一个板块——上传图片&#xff0c;这个项目的后台接口主要是是使用了后端的Koa2框架&#xff0c;前端小伙伴想要试着自己书写一些增删改查的接口可以从这个入手&#xff0c;Koa2用来了解后端…

力扣HOT100 11-15

11.盛水最多的容器 思路&#xff1a;最大水量 底边 * 高度。较短的一边控制最大水量&#xff0c;因此&#xff0c;采用双指针的方式&#xff0c;左、右指针指向开始和末尾&#xff0c;逐个向中间移动&#xff0c;判断左右指针所指向的高度哪个更低&#xff0c;它就向中间移动一…

ubuntu中解决Failed to connect to 127.0.0.1 port xxxxx: Connection refused

ubuntu中解决Failed to connect to 127.0.0.1 port xxxxx: Connection refused 方法一 查看一下代理 git config --global http.proxy git config --global https.proxy 有就取消,没有就换一种方法 git config --global --unset http.proxy git config --global --unse…

计算机网络之http03:HTTPS RSA握手解析

不同的秘钥交换算法,握手过程可能略有差别 上文对HTTPS四次握手的学习 SSL/TLS Secure Sockets Layer/Transport Layer Security 协议握手过程 四次通信&#xff1a;请求服务端公钥 2次 秘钥协商 2次 &#xff08;1&#xff09;ClientHello请求 客户端向服务端发送client…

状态机设计举例

⭐本专栏针对FPGA进行入门学习&#xff0c;从数电中常见的逻辑代数讲起&#xff0c;结合Verilog HDL语言学习与仿真&#xff0c;主要对组合逻辑电路与时序逻辑电路进行分析与设计&#xff0c;对状态机FSM进行剖析与建模。 &#x1f525;文章和代码已归档至【Github仓库&#xf…

JavaScript内置支持类Array

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>内置支持类Array</title> </head> <body bgcolor"antiquewhite"> <script type"text/javasc…

【Spark分布式内存计算框架——Spark Core】8. 共享变量

第七章 共享变量 在默认情况下&#xff0c;当Spark在集群的多个不同节点的多个任务上并行运行一个函数时&#xff0c;它会把函数中涉及到的每个变量&#xff0c;在每个任务上都生成一个副本。但是&#xff0c;有时候需要在多个任务之间共享变量&#xff0c;或者在任务(Task)和…

T35,没有token是什么意思?

描述 输入一个升序数组 array 和一个数字S&#xff0c;在数组中查找两个数&#xff0c;使得他们的和正好是S&#xff0c;如果有多对数字的和等于S&#xff0c;返回任意一组即可&#xff0c;如果无法找出这样的数字&#xff0c;返回一个空数组即可。 数据范围: 0≤len(array)≤…

常规网页布局

单列布局1 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>单列布局1-头主尾等宽</title><style>.container {max-width: 960px; /*设置最大宽度为固定值*/margin: 0 auto; /*设置内部子…

Delphi 中TImageCollection和TVirtualImageList 控件实现high-DPI

一、概述RAD Studio允许你通过使用TImageCollection组件和TVirtualImageList组件&#xff0c;在你的Windows VCL应用程序中包含缩放、高DPI、多分辨率的图像。这两个组件位于Windows 10面板中&#xff1a;注意&#xff1a;如果你使用FireMonkey进行跨平台应用&#xff0c;请看T…

用VSCode在共用服务器上使用连接自己的Docker容器进行开发

问题描述 我们实验室有一台很牛的Linux服务器&#xff0c;核多卡多硬盘大&#xff0c;它是大家共用的&#xff0c;组里给我们每个人都创建了一个普通用户&#xff0c;没有sudo权限&#xff0c;所以不能用apt。 但是每个人对开发环境的需求都是不一样的&#xff0c;比如我要用…