?? 漫談兼容內核之一: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 + -