📄 漫谈兼容内核之一:reactos怎样实现系统调用.txt
字号:
前面讲过,寄存器%edx指向用户空间堆栈上的函数调用框架,实际上就是指向所传递的参数,现在把这个指针复制到%esi中,这是在为从用户空间堆栈复制参数做准备。但是,光有复制的起点还不够,还需要有复制的长度(字节数)、即参数的个数乘4,所以需要知道具体的系统调用有几个参数。这个信息保存在一个以系统调用号为下标的无符号字节数组中(所以每个系统调用的参数总长度不能超过255字节),SSDT_ENTRY数据结构中的第三个成分(相对位移为12、或0xc)就是指向这个数组的指针。对于常规系统调用,这个数组是MainSSPT。可想而知,这个数组的内容也应来自sysfuncs.lst。代码中先让%ecx指向MainSSPT,再以%eax中的系统调用号与其相加,就使其指向了数组中的相应元素,而movb指令就把这个字节取了出来。所以,最后%ecx持有给定系统调用的参数复制长度。从%esp的内容中减去%ecx的内容,就在系统空间堆栈上保留了若干字节,其长度等于参数复制长度,这样就为把参数从用户空间堆栈复制到系统空间堆栈做好了准备。再往下看:
[code]
/* Get pointer to function */
movl (%edi), %edi
movl (%edi, %eax, 4), %eax
/* Copy the arguments from the user stack to our stack */
shr $2, %ecx
movl %esp, %edi
cld
rep movsd
/* Do the System Call */
call *%eax
movl %eax, KTRAP_FRAME_EAX(%ebp)
/* Deallocate the kernel stack frame */
movl %ebp, %esp
[/code]
前面,寄存器%edi已经指向常规系统调用的SSDT_ENTRY数据结构,也就是指向了该数据结构中的第一个成分。SSDT_ENTRY数据结构的第一个成分是个指针,指向一个函数指针数组。对于常规系统调用,这就是MainSSDT。指令“movl (%edi), %edi”把%edi所指处的内容赋给了%edi,使原来指向这个指针的%edi现在指向了MainSSDT。这也是个以系统调用号为下标的数组,其定义为:
[code]
SSDT MainSSDT[] = {
{ (ULONG)NtAcceptConnectPort },
{ (ULONG)NtAccessCheck },
{ (ULONG)NtAccessCheckAndAuditAlarm },
……
{ (ULONG)NtReadFile },
……
}
[/code]
在我们这个例子中,指令“movl (%edi, %eax, 4), %eax”,即“把%edi加相对位移为‘系统调用号乘4’之处的内容装入%eax”,使%eax指向了NtReadFile()。然后就是把参数从用户空间堆栈拷贝到系统空间堆栈,注意%ecx中的长度是以字节为单位的,所以要右移两位变成以长字为单位。
最后,指令“call *%eax”就使CPU进入了内核里面的NtReadFile(),其代码在reactos/ntoskrnl/io/rw.c中。如果按Linux的规矩,这应该是sys_NtReadFile():
[code]
NTSTATUS STDCALL
NtReadFile (IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID Buffer,
IN ULONG Length,
IN PLARGE_INTEGER ByteOffset OPTIONAL,
IN PULONG Key OPTIONAL)
{
……
}
[/code]
这个函数的调用界面与应用程序在用户空间进行这个系统调用时所遵循的界面完全相同,而应用程序压入用户空间堆栈的9个参数已经被拷贝到了系统空间堆栈中合适的位置上。于是,对于这个函数而言,就好像其调用者、在我们这个情景中是ReadFile()、就在系统空间中一样。
回到上面的汇编代码中。当CPU从目标函数返回时,寄存器%eax持有该函数的返回值,这是要返回给用户空间的,所以把它保存在堆栈框架中。
下面就是从内核返回到用户空间的过程,我把代码留给读者自己研究。不过需要给一点提示:
(1).代码中的APC指“异步过程调用(Asynchronous Procedure Call)”,相当于Linux中的Signal。
(2).Windows把内核的运行状态分成若干级别。最高的一些级别是不允许硬件中断(不允许级别更低的硬件中断);其次(2级和1级)是不允许进程调度(但是允许硬件中断),DPC(2级,相当于bh函数)和APC(1级,相当于signal)都应该在禁止调度的条件下执行;最低(0级)就是允许进程调度。
(3).从内核中也可以通过_KiSystemService()进行系统调用(不过要经过一个内核版本的stub函数),所以代码中需要检测和区分CPU进入_KiSystemService()之前的运行模式,并且线程的KTHREAD数据结构中也有个成分PreviousMode,用来保存这个信息。而KTHREAD_PREVIOUS_MODE(%esi)就指向当前进程的PreviousMode。
[code]
KeReturnFromSystemCall:
/* Get the Current Thread */
movl %fs:KPCR_CURRENT_THREAD, %esi
/* Restore the old trap frame pointer */
movl KTRAP_FRAME_EDX(%esp), %ebx
movl %ebx, KTHREAD_TRAP_FRAME(%esi)
_KiServiceExit:
/* Get the Current Thread */
cli
movl %fs:KPCR_CURRENT_THREAD, %esi
/* Deliver APCs only if we were called from user mode */
testb $1, KTRAP_FRAME_CS(%esp)
je KiRosTrapReturn
/* And only if any are actually pending */
cmpb $0, KTHREAD_PENDING_USER_APC(%esi)
je KiRosTrapReturn
/* Save pointer to Trap Frame */
movl %esp, %ebx
/* Raise IRQL to APC_LEVEL */
movl $1, %ecx
call @KfRaiseIrql@4
/* Save old IRQL */
pushl %eax
/* Deliver APCs */
sti
pushl %ebx
pushl $0
pushl $UserMode
call _KiDeliverApc@12
cli
/* Return to old IRQL */
popl %ecx
call @KfLowerIrql@4
KiRosTrapReturn:
/* Skip debug information and unsaved registers */
addl $0x30, %esp // + 0x48
popl %gs // + 0x44
popl %es // + 0x40
popl %ds // + 0x3C
popl %edx // + 0x38
popl %ecx // + 0x34
popl %eax // + 0x30
/* Restore the old previous mode */
popl %ebx // + 0x2C
movb %bl, %ss:KTHREAD_PREVIOUS_MODE(%esi)
/* Restore the old exception handler list */
popl %fs:KPCR_EXCEPTION_LIST // + 0x28
/* Restore final registers from trap frame */
popl %fs // + 0x24
popl %edi // + 0x20
popl %esi // + 0x1C
popl %ebx // + 0x18
popl %ebp // + 0x14
add $4, %esp // + 0x10
/* Check if previous CS is from user-mode */
testl $1, 4(%esp)
/* It is, so use Fast Exit */
jnz FastRet
/*
* Restore what the stub pushed, and return back to it.
* Note that we were CALLed, so the first thing on our stack is the ret EIP!
*/
pop %edx // + 0x0C
pop %ecx // + 0x08
popf // + 0x04
jmp *%edx
IntRet:
iret
FastRet:
/* Is SYSEXIT Supported/Wanted? */
cmpl $0, %ss:_KiFastSystemCallDisable
jnz IntRet
……
[/code]
熟悉Linux的读者知道CPU在返回用户空间之前应该调用有关进程(线程)调度的函数,因而会期待在这段代码中也看到这样的操作,然而却没有看到。但是实际上确实有这样的操作,只不过是深藏在函数KfLowerIrql()里面而已。
搞懂了这个函数的读者现在应该知道我们将要怎样做了。不过,我们的目标不是把KiSystemService()与Linux的system_call()堆积、并列在一起,而是要把前者溶入到后者中去。再说,即使照搬了KiSystemService(),总不能因为这个程序调用了KfLowerIrql(),就又照搬KfLowerIrql()吧。如果按这样类推,那就势必要把整个ReactOS内核堆积到Linux内核中去了。由此可见,我们既要参考、借鉴ReactOS内核的实现,又要研究怎样把它融合、嫁接到Linux内核中去,这当然是一项富有挑战性的工作。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -