Windows系统调用架构分析—也谈KiFastCallEntry函数地址的获取 .

2023-05-16

为什么要写这篇文章

1.      因为最近在学习《软件调试》这本书,看到书中的某个调试历程中讲了Windows的系统调用的实现机制,其中讲到了从Ring3跳转到Ring0之后直接进入了KiFastCallEntry这个函数。

2.      碰巧前天又在网上看到了一篇老文章介绍xxx安全卫士对Windows系统调用的Hook,主要就是Hook到这个函数

3.      刚刚做完毕业设计,对使用中断来实现系统调用的方式记忆犹新。

 

以上原因导致我最近眼前总是出现系统调用这个词,脑海中总是出现系统调用的实现方式,所以决定写篇文章清理一下思维。本文所有举例如无明确指明,均是在Windows XP SP3系统中获得。

 

本文的目的是探索一下Windows目前的系统调用的实现架构,介绍一种中规中矩的获取KiFastCallEntry函数地址的方法,但是在介绍之前还是要把该解释的解释清除。

 

Windows API和系统调用的关系

肯定会有人说Windows API就是系统调用系统调用就是Windwos API,是这样的么?负责任的说:不是这样的!

先从广义上来说,Windows API是对于整个Windows操作系统自身的程序代码之外的应用程序来说的,而系统调用Windows内核对于非内核程序代码之外的Windows系统程序代码来说的。也就是说系统调用要比Windows API低一个层次。一个Windows API是一个函数,这个函数可能会使用系统调用,请注意是可能,因为并不是所有的API都需要进入内核去完成这个API的功能。

 

Windows内核实现文件和应用层文件

在一个安装完成的Windows操作系统中可见并有效的内核实现文件是

C:\Windows\System32\ntoskrnl.exe

C:\Windows\System32\ntkrnlpa.exe

请注意有两个内核文件,其中第二个比第一个的名字少了os多了个pa,省去的os没有任何意义,但是多出来的pa所代表的意思是PAE(物理地址扩展),这是X86CPU的一个硬件特性,Windows启动之后根据当前系统设置是否开启了PAE特性会自动选择把其中一个作为内核加载到内存中。

为什么加了这么多限定词,因为ntoskrnl.exe这个文件名并不一定是这个文件的真实名称,可以从文件属性中看到

ntoskrnl.exe原始文件名为可能为ntoskrnl.exe或者ntkrnlmp.exe

ntkrnlpa.exe原始文件名为可能为ntkrnlpa.exe或者ntkrpamp.exe

可以发现其中的不同之处就是mpmp就是Multi-processor(多处理器,也可以理解为多核,因为IA-32架构对多核处理器的编程和多处理器的编程是相似的机制)。为什么会出现这中情况呢?因为这完全是由计算机硬件的不同配置导致的。当安装Windows操作系统的时候,Windows安装程序会自动检测机器的CPU特性,根据CPU的核心数来确定使用哪一套内核。如果是单核心就只复制ntkrnlpa.exentoskrnl.exe到系统目录下,如果是多核心就复制ntkrnlpamp.exentoskrnlmp.exe到系统目录下,所以如果你有一台单核心CPU的机器,有一天你换了双核的CPU却没有重新安装操作系统,那么你就不会在看到熟悉的Windows启动画面了。类似这两个文件的还有一个文件C:\Windows\System32\hal.dll,这是Windows的硬件抽象层程序文件,这个就不做具体介绍了。额外补充一个不同的硬件配置所需要的文件列表:

 

Standard PC

hal.dll
ntkrnlpa.exe
ntoskrnl.exe

Advanced Configuration and Power Interface (ACPI) PC

halacpi.dll---->hal.dll
ntkrnlpa.exe
ntoskrnl.exe

ACPI Uniprocessor PC

halaacpi.dll--->hal.dll
ntkrnlpa.exe
ntoskrnl.exe

MPS Uniprocessor PC

halapic.dll---->hal.dll
ntkrnlpa.exe
ntoskrnl.exe

ACPI Multiprocessor PC

halmacpi.dll--->hal.dll
ntkrpamp.exe--->ntkrnlpa.exe
ntkrnlmp.exe---->ntoskrnl.exe

Compag SystemPro Multiprocessor or 100% Compatible

halsp.dll---->hal.dll
ntkrpamp.exe--->ntkrnlpa.exe
ntkrnlmp.exe--->ntoskrnl.exe

MPS Multiprocessor PC

halmps.dll----->hal.dll
ntkrpamp.exe----->ntkrnlpa.exe
ntkrnlmp.exe--->ntoskrnl.exe

Silicon Graphics Visual Workstation

halsp.dll---->hal.dll
ntkrpamp.exe----->ntkrnlpa.exe
ntkrnlmp.exe---->ntoskrnl.exe

 

不论什么配置,一旦系统安装完成后,对我们来说可见的内核文件就只有两个ntoskrnl.exentkrnlpa.exe。这两个文件中的代码就是运行于RING0下的内核代码,他们里面所包含了真正的系统调用的代码。我们用Dependency Walker看一下:

(P1)

 

可以看到很多函数,他们之中有些是系统调用,会和RING3的程序代码有联系,有些仅仅是内核中的函数,只跟内核中其他的代码有联系。有一点需要说,在用Windbg调试内核的时候,无论系统使用的是哪个内核文件,Windbg都会把这个内核文件的符号(模块名)识别为nt。所以如果我们要查看内核中的NtOpenFile函数的反汇编,只需要输入一下命令

u nt!NtNtOpenFile

如下图:

(P2)

下面再说包含Windows API的文件,这个就非常多了,最基础的User32.dllNtdll.dllKernel.dll这三个文件包含了Windows系统相关的绝大多数API,当然还有其他函数簇比如包含socket函数簇的ws2_32.dll等等,这些dll中都导出了大量的API函数以及结构。这些函数都是运行在RING3层的代码,有些会跟内核层中的代码发生联系,有写也不会,就是上面提到的Windows API不一定会使用系统调用。比如我们举个简单的例子,拿CharNextA这个Windows API来说,这个API是在User32.dll中实现的。我们可以反汇编一下看看这个API的具体实现。

(P3)

可以看到这个函数在ret之前只有一个跳转je并且目标代码仍然在本函数内,而且没有任何中断或者快速系统调用,所以这个函数并没有离开RING3层,也就是说当你编写一个程序调用了这个API之后,这个API并没有把你的程序流程带入RING0的内核层代码。

 

Windows API使用系统调用的方法

通过中断实现系统调用

Windows API如果设涉及到系统调用就要由RING3进入RING0,这就牵扯到了X86保护模式下有特权级变化的控制转移。在早期的CPU(Pentium II之前),没有快速系统调用这个机制,所以能用来进行特权级变化的控制转移的机制只有通过中断实现,保护模式下的中断的实现方式是通过IDT表来实现,IDT表中存放的是一种特殊的X86段描述符——门描述符,门描述符的格式如下

(P4)

可以看到其中有一个Selector字段和一个Offset字段,并且是不连续的,这里只介绍这两个字段的含义,其他字段的含义这里不再赘述,有兴趣的话可以自己去看下保护模式相关资料。说到底这个门描述符的作用就是描述一个程序段,对我们来说重要的就是SelectorOffset字段了,因为Selector可以帮我们找到它所描述的程序的【段】,Offset就是程序在【段】内的【偏移】,有了【段】和【偏移】就可以确定程序的线性地址。那么我们来试着找一找Windows通过中断来实现系统调用时候的流程!不会用Windbg的程序员不是好的狙击手。

首先用Windbg打开一个Calc.exe或者其他的普通应用程序,然后键入命令:

u ntdll!KiIntSystemCall

(P5)

如果有疑问为什么直接来到这个函数,现在先不忙解释,后面再说。可以看到这个函数中

int 2Eh

这一句汇编代码,我们知道了系统调用了2E号中断,从而进入了系统内核,知道了中断号下面我们要做的就是找到这个中断的服务程序,也就是RING3进入到RING0之后的第一条指令在哪里。下面就进入内核调试模式。通过

!pcr

查找IDT的线性地址

(P6)

找到了IDT的线性地址:0x8003f400,前面说过IDT中存放的是门描述符,每一个门描述符占用8个字节,所以我们要找的第2e个门描述符的地址应该为0x8003f400+2e*8,然后我们通过如下命令:

dq /c 1 8003f400+2e*8

查看内存内容:

(P7)

看到第一行的八个字节就是我们要找的2e号中断所对应的门描述符。根据门描述符的格式计算,得到这个门描述符中包含的

Offset【偏移】 = 0x8053e4a1

Selector = 0x0008

【偏移】已经找到了,下面就剩【段】了,【段】可以通过Selector【选择子】找到,这里需要说下【选择子】,选择子占用2个字节,格式如下

(P8)

选择子是真正可以存放在保护模式下的段寄存器中的结构,既然它被放在段寄存器中,那么通过它必然能够找到段的信息,保护模式下的段是通过段描述符来描述的,描述符的具体分类有三种:存储段描述符,系统段描述符,门描述符,这里只介绍存储段描述符,其他的请自行查阅保护模式相关资料。存储段描述符格式如下:

(P9)

可以看到存储段描述里包含了基址,界限和属性。对我们来说只要找到基址就行了。存储段描述符是保存在GDT中的,而选择子则包含了【描述符索引】即这个选择子所指向的描述符在GDT中的索引号,比如我们刚才计算得到的选择子Selector = 0x0008,其中索引号为1,就是说我们要找的段的段描述符在GDT中的第1项。由此可知,我们要找到【段】还需要找到段描述符,这个算法跟从IDT中找门描述符是一样的,先找到GDT的地址,从刚才的!pcr命令执行结果看到GDT = 0x8003f000,一个描述符占用8个字节,我们要找第一个描述符,计算得到描述符的地址为:0x8003f000+8*1,执行命令

dq /c 1 8003f000+1*8

得到结果:

(P10)

根据上述存储段描述符的格式计算得到该段的基地址为0x000000所以费劲千辛万苦找到了【段】=0x00000,现在【段】和【偏移】都找到了,那么我们要找的一个线性地址就找到了:

【段】:【偏移】 = 0x8053e4a1

反汇编看看这个地址的代码!

u 8053e4a1

得到结果:

(P11)

是这个函数nt!KiSystemService。为了验证我们没有计算错误,可以用Windbg直接显示2E号中断所对应的中断服务程序,执行命令:

!idt 2e

(P12)

可以看到通过脑力算出的结果与Windbg dump出来的结果是一样的,证明了我们的算法是没有问题的。

这样我们先总结一下使用中断实现系统调用时候RING3RING3的函数接续:

NtDll!KiIntSystemCall > Nt!KiSystemService

使用快速系统调用机制

Pentium II系列开始的CPU引入了快速系统调用这一特性,增加了两条指令SYSENTERSYSEXIT(AMD CPU中的指令为SYSCALLSYSRET)。这一机制的实现就是专门用于解决操作系统的系统调用的性能问题的,这种机制实现的控制转移比中断系统要快很多,因为转移的目标地址是存放在MSR寄存器内,而中断实现的系统调用目标地址存放在内存中的IDT中,所以能提高执行速度。

下面看一下在应用层是在哪里调用了这条指令的:

(P13)

可以看到在ntdll!KiFastSystemCall中有这条指令。

SYSENTER指令的工作机制是在调用SYSENTER指令前,软件必须通过下面的MSR寄存器,指定0层的代码段和代码指针,0层的堆栈段和堆栈指针:

1. IA32_SYSENTER_CS:一个32位值。低16位是0层的代码段的选择子。该值同时用来计算0层的堆栈的选择子。

2. IA32_SYSENTER_EIP:包含一个32位的0层的代码指针,指向第一条指令。

3. IA32_SYSENTER_ESP:包含一个32位的0层的堆栈指针。

MSR寄存器可以通过指令RDMSR/WRMSR来进行读写。寄存器地址如下表。这些地址值在以后的intel 64IA32处理器中是固定不变的。

MSR

地址

IA32_SYSENTER_CS

174H

IA32_SYSENTER_ESP

175H

IA32_SYSENTER_EIP

176H

当执行SYSENTER,处理器会做下面的动作:

1.IA32_SYSENTER_CS从取出段选择子加载到CS中。

2.IA32_SYSENTER_EIP取出指令指针放到EIP

3.IA32_SYSENTER_CS的值加上8,将其结果加载到SS中。

4.IA32_SYSENTER_ESP取出堆栈指针放到ESP寄存器中

5.切换到0层。

6.EFLAGSVM标志已被置,则清除VM标志。

7.开始执行选择的系统过程。

 

     又看到了【选择子】,了解了执行流程,下面还可以手工一步步计算出SYSENTER指令行执行CPU取出的第一条内核指令到底在哪里。有了上面的计算过程,这次计算就不需要写的很详细了,目标还是要找到【段】和【偏移】,很明显【偏移】放在MSR176号地址中,Windbg用如下指令读取MSR

      rdmsr 176

(P14)

【偏移】 = 0x8053e60

下面找【段】,依然是通过选择子来计算,选择子在MSR174号地址中存放

     rdmsr 174

(P15)

【选择子】 = 0x0008,跟之前的选择子一样,这里就不用再去计算了,从刚才的结果中知道这个选择子指向的描述符所描述的内存基址是线性地址0x0000

所以我们要找的目标线性地址为【段】:【偏移】 = 0x8053e60

下面反汇编一下这个地址:

(P16)

看到是函数Nt!KiFastCallEntry

总结一下使用快速系统调用机制的时候RING3RING3的函数接续:

NtDll!KiFastSystemCall > Nt!KiFastCallEntry

完整的Windows API使用系统调用的过程

XP系统之前,Windows只实现了一种系统调用的方式,那就是通过INT 2E号中断来实现的,所以从RING3RING0之后的第一个函数一定是nt!KiSystemService,这个函数就是核心的系统调用分派函数,但是XP开始,Windows系统开始使用快速系统调用这一机制了,但是并没有失去对中断方式的支持,所以XP之后的系统都是两种实现方式共存的。具体做法我们可以用Windbg来继续探索,下面的工作就要在RING3来做了,因为我们要分析一个完整的Windows API使用系统调用的过程。在这之前需要先说一下有关Nt*Zw*函数的问题。在应用层的Ntdll.dll中有大量的Nt开头的函数和Zw开头的函数,并且都是配对出现,他们所指向的地址都是相同的,所以他们的实现是完全相同的,只不过是别名问题。我们可以用Depdency Walker查看他们的入口地址发现都是相同的,也可以用Windbg反汇编看,关于这一点没什么好说的了,网上很多资料都有说。那我们就可以随便选择一个Nt开头的函数来进行分析,或者Zw开头的都是一样的。内核中也存在这样的函数,但是Nt开头的函数是真正的函数实现,Zw开头的函数是通过nt!KiSystemService函数最终调用的Nt开头的函数。我们选择NtOpenFile函数,需要说明的是这个函数是未导出的,我们在编程的时候使用的是kernel32.dll中导出的OpenFile或者OpenFile,最终是要进入NtOpenFile,从KERNEL32.dllNtdll.dll的过程省略。先反汇编NtOpenFile看看:

(P17)

看到在NtOpenFile函数中红色标注的代码,把一个内存中的一个dwrod取到了edx中,这个内存的符号为SharedUserData!SystemCallStub,然后跳转到edx中的值,所以edx中应该是一个函数的地址,我们看一下这个地址是什么

(P18)

看到这个地址中存放的地址是0x7c92e510,反汇编这个地址

(P19)

看到SharedUserData!SystemCallStub保存的是快速系统调用的入口函数的地址,其中通过sysenter进入内核,进入内核之后的过程上面已经介绍过了。

现在就可以总结一下一个Windows API如果使用了系统调用之后的流程了:

无论在何处直接调用了RING3API并且需要使用系统调用,最终都会通过Ntdll.dll这个模块来进入内核(此说法是错误的,因为Win32子系统就是个例外,Win32子系统也可以进入内核与Win32k.sys交互),例如一个函数OpenXXX,无论其在哪个DLL实现,最终都要进入Ntdll.dll这个函数中的NtOpenXXX,在该函数中的内容就是读取一个用户共享数据区中的一个变量SytemCallStub,这个变量的值就是包含实现特权级变化的控制转移代码的函数入口,其实就是ntdll!KiFastSystemCall的地址,在这个函数中使用了SYSENTER指令,可以直接进入内核的nt!KiFastCallEntry,这个函数会调用nt!KiSystemService这个函数,这个函数的任务是查找SSDT中对应系统调用号的系统调用的实现地址nt!NtOpenXXX

 

本来我是想做一个推测:在XP系统中通过中断方式实现系统调用的代码仍然存在,那么我们能不能根据需要选择我们希望使用的系统调用的实现方式呢?产生这个想法的原因是Windows并没有直接把实现特权级变化的控制转移代码的函数入口硬编码到每一个API中,而是使用了一个变量—SharedUserData!SytemCallStub中,既然这样实现肯定是为了可以方便切换这个函数的地址,在现在的XP以及更新的操作系统中,这个变量中存放的是ntdll!KiFastSystemCall,就是通过快速系统调用的方式来实现系统调用,那么如果我们修改这个变量的值,使其等于ntdll!KiIntSystemCall的话,是不是就可以改变整个系统的系统调用方式为中断方式呢?关于这个猜想还我还没有做下验证,打算过几天再验证。

 

nt!KiFastCallEntry函数地址的获取

下面说一下nt!KiFastCallEntry这个函数,无论通过何种系统调用的实现方式,都会调用这个函数来进行系统调用,所以这个函数也就成了一是十分重要的领地,就相当于兵家必争之地,无论是安全软件还是恶意软件。但是这个这个函数地址的获取还是有一点技巧的。

在这里先说一下XXX安全卫士的获取nt!KiFastCallEntry这个函数地址的方式,因为网上有人分析过它的实现方式,并且被大量转载,所以这里只是简单的提一下。我们已知的信息是,Windows的凡是涉及系统调用的API在内核中的调用顺序是:

快速系统调用:nt!KiFastCallEntry->Nt*

中断方式系统调用:nt!KiSystemService-> nt!KiFastCallEntry->Nt*

所以为了过滤系统调用,只要Hooknt!KiFastCallEntry这个函数就一定能拦截所有的系统调用。XXX安全卫士的做法是先Hook住了一个nt!NtSetEvent函数,当进入这个函数的时候利用栈回溯,找到当前函数的返回地址,这个地址一定是在上层函数内的,得到这个地址就以后,就可以根据特征码搜索需要Inline Hook的目标地址了。借助nt!ZwCreateFile函数来动态跟踪分析,如下图:

(P20)

很多人说这种方法巧妙,通过栈回溯找到目标函数地址域内的一地址,然后特征码匹配,为何不直接找到函数入口然后直接特征码匹配呢?栈回溯的方法固然看起来很巧妙,可是如果内核中对nt!KiFastCallEntry函数启用了FPO(帧指针优化),那这种方法就完全被挫败了,所以这种方法并不能称为完美。

 

网上也有很多其他方法来获取nt!KiFastCallEntry函数入口地址,有暴力搜索的,也有通过MSR寄存器的。通过MSR寄存器读取的确是一种中规中矩的方法,但是看过不少代码,都是建立在一个假设上:nt!KiFastCallEntry的段基址一定是线性地址0x00开始。这些代码都直接忽略了对MSR 174号地址内的段选择子的处理,完整的做法应该利用本文中之前的方法计算段和偏移求得nt!KiFastCallEntry的入口地址,由于汇编语言处理这些计算相对方便,所以本人用汇编实现了一个得到nt!KiFastCallEntry函数地址的函数。代码如下:

 

[html] view plain copy print ?
  1. #define IA32_SYSENTER_CS 174H  
  2. #define IA32_SYSENTER_ESP 175H  
  3. #define IA32_SYSENTER_EIP 176H  
  4.   
  5. ULONG GetAddressOfKiFastCallEntry()  
  6. {  
  7.     ULONG dwAddress = 0;  
  8.     __asm  
  9.     {  
  10.         jmp func_main  
  11. vgdtr:  
  12.         _emit 0x00  
  13.         _emit 0x00  
  14.         _emit 0x00  
  15.         _emit 0x00  
  16.         _emit 0x00  
  17.         _emit 0x00  
  18.         _emit 0x00  
  19.         _emit 0x00  
  20.   
  21. func_main:  
  22.         push eax  
  23.         push ebx  
  24.         push ecx  
  25.         push edx  
  26.         mov ecx, 0x174  
  27.         rdmsr  
  28.         mov ebx, eax        //Selector offset  
  29.           
  30.         sgdt vgdtr  
  31.         mov edx, vgdtr  
  32.         add edx, 0x02  
  33.         mov eax, [edx]      //GDT base  
  34.         add ebx, eax        //Selector base  
  35.   
  36.         mov edx, ebx  
  37.         add edx, 0x07  
  38.         mov eax, [edx]  
  39.         shl eax, 24;  
  40.         mov edx, ebx  
  41.         add edx, 0x02  
  42.         mov ecx, [edx]  
  43.         and ecx, 0x00FFFFFF  
  44.         add eax, ecx        //Address CodeSegment  
  45.         mov ebx, eax  
  46.   
  47.         mov ecx, 0x176  
  48.         rdmsr  
  49.         add eax, ebx  
  50.   
  51.         mov dwAddress, eax  
  52.   
  53.         pop edx  
  54.         pop ecx  
  55.         pop ebx  
  56.         pop eax  
  57.     }  
  58.   
  59.     return dwAddress;  
  60. }   
#define IA32_SYSENTER_CS 174H
#define IA32_SYSENTER_ESP 175H
#define IA32_SYSENTER_EIP 176H

ULONG GetAddressOfKiFastCallEntry()
{
	ULONG dwAddress = 0;
	__asm
	{
		jmp func_main
vgdtr:
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00

func_main:
		push eax
		push ebx
		push ecx
		push edx
		mov ecx, 0x174
		rdmsr
		mov ebx, eax		//Selector offset
		
		sgdt vgdtr
		mov edx, vgdtr
		add edx, 0x02
		mov eax, [edx]		//GDT base
		add ebx, eax		//Selector base

		mov edx, ebx
		add edx, 0x07
		mov eax, [edx]
		shl eax, 24;
		mov edx, ebx
		add edx, 0x02
		mov ecx, [edx]
		and ecx, 0x00FFFFFF
		add eax, ecx		//Address CodeSegment
		mov ebx, eax

		mov ecx, 0x176
		rdmsr
		add eax, ebx

		mov dwAddress, eax

		pop edx
		pop ecx
		pop ebx
		pop eax
	}

	return dwAddress;
} 

 

测试用驱动代码及结果如下图:

(P21)

(P22)

这个结果和我们用Windbg查看得到的是一样的。

 

这篇文章写的有点长,而且知识有点砸碎希望各位看过之后能理解。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Windows系统调用架构分析—也谈KiFastCallEntry函数地址的获取 . 的相关文章

  • cmake -D CMAKE_PREFIX_PATH=/home/nvidia/data/wyy/openpose/build

    cmake D CMAKE PREFIX PATH 61 home nvidia data wyy openpose build 表示上层目录 D 相当于就是定义 D 可以理解为告诉cmake 后边我要定义一些参数了 你每定义一个就在前边加
  • VNC 的应用及灰屏鼠标变X问题

    Ubuntu中vnc服务器端的安装很简单 xff0c 运行如下命令 xff1a sudo apt get install vnc4server 第一次启动vncserver后 xff0c 在用户家目录中会生成 vnc 目录 xff0c 注意
  • 第七章 MapReduce详解

    MapReduce是一种并行编程模型 用于大规模数据集 大于1TB 的并行计算 它将复杂的 运行于大规模集群上的并行计算过程高度抽象为两个函数 Map和Reduce MapReduce是单输入 两阶段 粗粒度数据并行 分布式计算框架 适合用
  • 跨域-Vue-Cli配置代理转发

    目标 xff1a 通过配置vue cli请求代理解决开发环境下的跨域问题 vue cli中集成的跨域解决方案 思路 xff1a 在前端服务和后端接口服务之间 架设一个中间代理服务 xff0c 它的地址保持和前端服务一致 xff0c 那么 x
  • Unable to determine the device handle for GPU 0000:83:00.0: GPU is lost.Reboot the system to recover

    更新 xff1a 发现是机房空调太差了 xff0c 显卡温度过高的原因 在跑程序的时候遇到了这个问题 xff0c 还没有找到解决的方法 xff1a Unable to determine the device handle for GPU
  • Windows自带的远程桌面共享工具mstsc

    Windows自带的远程桌面共享工具mstsc 1 运行Windows自带的mstsc2 输入远程IP地址3 配置 本地资源 4 然后点击 连接 远程电脑 1 运行Windows自带的mstsc 按 34 Win 43 R 34 组合键打开
  • 单目纯视觉避障方案——2020中国机器人大赛FIRA避障仿真组决赛代码开源

    单目纯视觉避障方案 2020中国机器人大赛FIRA避障仿真组决赛代码开源 目录 单目纯视觉避障方案 2020中国机器人大赛FIRA避障仿真组决赛代码开源比赛环境介绍赛题要求比赛思路该方案的出彩点仍需优化的地方 代码效果展示 特别鸣谢两位吴同
  • ROS问题:gazebo没有想要的模型,而且不报错

    问题 在运行一些demo时 xff0c 检查launch文件 lt startup simulated world gt lt include file 61 34 find gazebo ros launch empty world la
  • ROS报错:ROS-Melodic中cv_bridge报错

    我是ubuntu18 04 ROS Melodic ImportError dynamic module does not define module export function PyInit cv bridge boost 这个报错是
  • vnc工具登录,vnc工具登录方法,多用户登录vnc服务器教程

    VNC是虚拟网络控制台的缩写 它是一款优秀的远程控制工具软件 xff0c 远程控制能力强大 xff0c 高效实用 xff0c 其性能可以和 Windows中的任何远程控制软件媲美 但是它只能进行单一的用户登录和操控 xff0c 具有比较大的
  • MFC最近文件列表的使用[转]

    MFC框架中对最近文件列表的支持 MFC建立的标准框架程序中有记录最近操作文件的能力 xff0c 这些最近文件的路径被记录到注册表 xff0c 在程序运行时 xff0c 又将添加到文件菜单中 在CWinApp中有个 CRecentFileL
  • 8.ROS编程学习:自定义服务数据python调用

    目录 一 准备工作 vscode配置 二 服务端实现 1 创建demo01 server p py 2 添加可执行权限 3 配置CMakeList txt melodic不用配置也行 xff0c noetic需要配置 4 测试服务端 三 客
  • Keil出现Error:Flash Download failed - Could not load file错误 解决点拨

    根本原因 很多大学生刚接触keil在新建模版时可能会出现 Error Flash Download failed Could not load file 的错误 由于机器人工程专业的新开办 xff0c 可能老师也无法解决 xff0c 其实原
  • aarch64 Centos7 No such file or directory和No module named sqlitecachec

    莫名其妙的出现yum无法使用 xff0c 提示No such file or directory 可能是yum被误删除了 xff0c 根据提示需要重新安装一下yum xff0c 检查版本 cat etc redhat release Cen
  • Pid控制算法-位置型pid算法的C++实现

    PID控制算法的C 43 43 语言实现 三 位置型PID的C 43 43 语言实现 上一节中已经抽象出了位置性PID和增量型PID的数学表达式 xff0c 这一节 xff0c 重点讲解C 43 43 代码的实现过程 xff0c 算法的C
  • Pid控制算法-变积分的pid算法的C++实现

    PID控制算法的C 43 43 实现 七 变积分的PID控制算法C 43 43 实现 变积分PID可以看成是积分分离的PID算法的更一般的形式 在普通的PID控制算法中 xff0c 由于积分系数ki是常数 xff0c 所以在整个控制过程中
  • Ceres Solver 在Windows下安装配置笔记

    引子 因为项目需要用到Ceres Solver这个库实现非线性优化 xff0c 这几天投入了很多时间来学习研究 没想到的是 xff0c 这个库的安装就非常繁琐 官方的安装指南写的却过于简略 尤其是在Windows环境下 xff0c 问题bu
  • STM32开发选择CubeIDE还是keil

    一 xff0c 先说结论 xff1a 选择keil MDK 二 xff0c 原因 xff1a 1 xff0c 优势 cubeIDE代码编辑能力确实比keil方便 包括 xff1a 代码提示 xff0c 自己可以网上找个代码自动补全插件 xf
  • cmake(三十一)Cmake之get_filename_component指令

    一 基础知识 cmake获取 39 文件名 39 的 39 特定 39 部分 xff0c 提供了 39 三种 39 调用方式 注意 xff1a 39 key 39 关键字 方式1 方式2 方式3 二 实践 xff08 1 xff09 路径
  • cmake(三十九)Cmake之execute_process指令

    一 官网博客 用途 xff1a 执行一个或多个 39 子进程 39 场景 xff1a 通过git命令 39 读取版本号 39 在代码中使用 列出某些 39 文件的名称 39 在代码中使用 1 按指定的 39 先后顺序 39 运行一个或多个命

随机推荐