本文為看雪論壇優秀文章
看雪論壇作者ID:hyjxiaobia
由於windows設備驅動程序被設計成以設備堆棧(Device stack)的形式,下層驅動向上提供支持,所以我們習慣於不加思考的在設備棧的上層使用來自下層驅動的各種已有功能。當我還在做驅動程序時,經常見到這些句話:A1."功能驅動(Fdo)收到IRP_MJ_PNP&IRP_MN_START後,會向總線驅動(Pdo)查詢並正確配置所需的硬體資源,如IO Port/IRQ/Mem等,然後才開始工作"。這段話我一直感到困惑:功能驅動的硬體資源來自總線驅動,那麼總線驅動的資源由誰提供?可能有人會這樣釋疑:由物理總線仲裁分配。A2."設備驅動從D0進入D3,保存設備上下文以後,設備(特指硬體上的設備)將進入D3 Sleep狀態"。這段話就更困惑了,為什麼保存設備上下文(往往是驅動層面上的上下文)後,硬體就能下電了?A3.OS進入Sleep後,當支持喚醒功能的設備收到中斷信號後,喚醒OS。其實,進入Sleep狀態後,CPU不會運行任何指令,OS和驅動程序不可能執行;另外,OS作為驅動程序的載體,不可能驅動程序先於OS提前醒來並喚醒OS的情況(驅動程序在夢遊?)。上面有些問題看似很難有答案,另一些問題的答案又回答的很模糊,像是順著設備堆棧的思路,把問題拋給更底層。這種不能釋懷的情緒伴隨了很多年(我簡直是帶著問題工作!),直到我轉做Bios一段時間,並無意中得到xp洩露的源碼後,我覺得我可能釋疑了。所以打算用幾篇文章輸出一下自己的觀點。本文暫不回答上面3個問題,只用於引入文章的主角----作為溝通OS和Bios的橋梁,ACPI.sys(它是windows對ACPI spec中反覆出現的OSPM的平臺實現)。windows通過ACPI.sys,藉助BIOS的實現上述功能A1/A2;GPIO信號跳變後,觸發SCI中斷,引起Bios執行平臺喚醒功能,並在此過程中重新加載OS,最終實現上述功能A3。ACPI如此重要,值得我們好好研究。本文從兩個側面介紹win7 x86上開啟ACPI的過程:Bios側和windows側。SwContext.SwSmiInputValue = (UINTN) PcdGet8 (PcdAcpiEnableSwSmi); Status = SwDispatch->Register ( SwDispatch, EnableAcpiCallback, &SwContext, &SwHandle );同理,Bios為了支持windows開啟ACPI,也會以同樣的方式註冊SMI處理函數。對於VMware,它註冊的值是0xF0,註冊後,往ACPI table Fadt->ACPI_Enable域寫入0xF0,之後默默的等windows開啟ACPI。bcdedit /debug onbcdedit /bootdebug on ;不開這個,windows開啟ACPI的瞬間,windbg和目標機的調試會話可能會被重置根據洩露的windows xp源碼,可以發現Xp時代,開啟ACPI的相關代碼位於ACPIEnableEnterACPIMode 函數中://busdrv\acpi\driver\shared\acpienbl.cVOIDACPIEnableEnterACPIMode ( VOID );/*++Routine Description: This routine is called to enter ACPI modeArguments: NoneReturn Value: None--*/不過這段代碼來自Xp,我們調試的目標是win7,調試前得先確認下這部分代碼是否存在變化:kd>x ACPI!ACPIEnableEnterACPIMode ;搜索函數名ACPI!ACPIEnableEnterACPIMode ;居然函數還在,看來Win7還是用這個函數開啟ACPIkd> bp acpi!ACPIEnableEnterACPIMode ;那就下斷點kd> gBreakpoint 0 hitACPI!ACPIEnableEnterACPIMode:88cdbce0 8bff mov edi,edi當觸發斷點後,看下調用堆棧等信息,可以看出此時OS處於初始化Phase1階段。kd> gBreakpoint 0 hitACPI!ACPIEnableEnterACPIMode:88cc2ce0 8bff mov edi,edikd> kb # ChildEBP RetAddr Args to Child 00 8078b644 88cc2e25 00000000 8078b66c 88cfde24 ACPI!ACPIEnableEnterACPIMode01 8078b650 88cfde24 00000000 82a0f940 88cf5ae0 ACPI!ACPIEnableInitializeACPI+0x1f02 8078b66c 88cbe556 87d4cc60 87e548a0 00000000 ACPI!ACPIInitialize+0xe203 8078b69c 88d050c2 87d4cc60 886e81a0 88d04f38 ACPI!ACPIInitStartACPI+0x6a04 8078b6c8 88cb927e 87d4cc60 886e8100 87d4cc60 ACPI!ACPIRootIrpStartDevice+0x18a05 8078b6f8 82a7c11a 87d4cc60 87e548a0 886e8258 ACPI!ACPIDispatchIrp+0x13a06 8078b718 82f8f531 00000000 87e535d8 87d4b008 nt!IofCallDriver+0x7e07 8078b734 82a89506 8078b76c 82a892ea 87d4e4a0 nt!PnpAsynchronousCall+0x10908 8078b7a0 82f976be 82a892ea 87d4e4a0 00000000 nt!PnpStartDevice+0x18409 8078b7fc 82f96b27 00000012 00000000 87d4e4a0 nt!PnpStartDeviceNode+0x2a60a 8078b818 82f8b0b8 00000000 00000000 00000000 nt!PipProcessStartPhase1+0x870b 8078ba14 82a87f83 87df5748 00000000 8078ba50 nt!PipProcessDevNodeTree+0x1cc0c 8078ba5c 82a87e4a 00000000 87d4cab8 00000000 nt!PnpDeviceActionWorker+0x1290d 8078ba74 832b06ba 00000000 00000000 00000000 nt!PnpRequestDeviceAction+0x15e0e 8078baec 832ad52b 8080f8c0 00000000 80810e50 nt!IopInitializeBootDrivers+0x4140f 8078bb6c 832a29b7 8080f8c0 87de25e0 87de2270 nt!IoInitSystem+0x59310 8078bc4c 82ecd012 8078bc90 8313d0da 8080f8c0 nt!Phase1InitializationDiscard+0xd6711 8078bc54 8313d0da 8080f8c0 0010209f 00000000 nt!Phase1Initialization+0xd12 8078bc90 82be6555 82ecd005 8080f8c0 00000000 nt!PspSystemThreadStartup+0x17813 00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x19另外,調用堆棧Frame#06有個熟悉的函數調用:IofCallDriver,參數顯示的DeviceInst:ACPI_HAL\PNP0C08,它就是ACPI spec中指定ACPI.sys(OSPM)。kd> !devstack 87e535d8 !DevObj !DrvObj !DevExt ObjectName 87d4cc60 \Driver\ACPI 87e548a0 > 87e535d8 \Driver\ACPI_HAL 87e53690 0000006c!DevNode 87d4b008 : DeviceInst is "ACPI_HAL\PNP0C08\0" ;<---PNP0C08 ServiceName is "ACPI"繼續往下調試,會看到OS從全局變量ACPI!AcpiInformation+0x04處獲得信息,感覺ACPI!AcpiInformation是個結構體指針:kd> pACPI!ACPIEnableEnterACPIMode+0x81:88cdbd61 a194bbd088 mov eax,dword ptr [ACPI!AcpiInformation (88d0bb94)] kd> u ACPI!ACPIEnableEnterACPIMode+0x818bf8 mov edi,eaxa1942bcf88 mov eax,dword ptr [ACPI!AcpiInformation (88cf2b94)]8b4004 mov eax,dword ptr [eax+4] ;<---從結構體偏移取值660fb64034 movzx ax,byte ptr [eax+34h] ;<---ACPI!AcpiInformation+4處取得的值仍然是結構體指針0fb7c0 movzx eax,axACPI!AcpiInformation結構體長成什麼樣,dump出來看一下?順帶一提,dt的輸出結果中,從ACPI!_ACPIInformation!PM1a_BLK開始到ACPI!_ACPIInformation!GpeSize都是ACPI spec中定義的Fixed Hardware Register。kd> dt _ACPIInformationACPI!_ACPIInformation +0x000 RootSystemDescTable : Ptr32 _RSDT_32 +0x004 FixedACPIDescTable : Ptr32 _FADT +0x008 FirmwareACPIControlStructure : Ptr32 _FACS +0x00c DiffSystemDescTable : Ptr32 _DSDT +0x010 MultipleApicTable : Ptr32 _MAPIC +0x014 GlobalLock : Ptr32 Uint4B +0x018 GlobalLockQueue : _LIST_ENTRY +0x020 GlobalLockQueueLock : Uint4B +0x024 GlobalLockOwnerContext : Ptr32 Void +0x028 GlobalLockOwnerDepth : Uint4B +0x02c ACPIOnly : UChar +0x030 PM1a_BLK : Uint4B +0x034 PM1b_BLK : Uint4B +0x038 PM1a_CTRL_BLK : Uint4B +0x03c PM1b_CTRL_BLK : Uint4B +0x040 PM2_CTRL_BLK : Uint4B +0x044 PM_TMR : Uint4B +0x048 GP0_BLK : Uint4B +0x04c GP0_ENABLE : Uint4B +0x050 GP0_LEN : UChar +0x052 Gpe0Size : Uint2B +0x054 GP1_BLK : Uint4B +0x058 GP1_ENABLE : Uint4B +0x05c GP1_LEN : UChar +0x05e Gpe1Size : Uint2B +0x060 GP1_Base_Index : Uint2B +0x062 GpeSize : Uint2B +0x064 SMI_CMD : Uint4B +0x068 pm1_en_bits : Uint2B +0x06a pm1_wake_mask : Uint2B +0x06c pm1_wake_status : Uint2B +0x06e c2_latency : Uint2B +0x070 c3_latency : Uint2B +0x074 ACPI_Flags : Uint4B +0x078 ACPI_Capabilities : Uint4B +0x07c Dockable : UChar再dump出ACPI!AcpiInformation->FixedACPIDescTable的內存,可以發現,其中smi_cmd_io_port/acpi_on_value的值和前面RW截圖中顯示的值一致。kd> dx -id 0,0,ffffffff87de2548 -r1 ((ACPI!_FADT *)0xffd07010)((ACPI!_FADT *)0xffd07010) : 0xffd07010 [Type: _FADT *] [+0x000] Header [Type: _DESCRIPTION_HEADER] [+0x024] facs : 0x3fefffc0 [Type: unsigned long] [+0x028] dsdt : 0x3fee1652 [Type: unsigned long] [+0x02c] int_model : 0x0 [Type: unsigned char] [+0x02d] pm_profile : 0x0 [Type: unsigned char] [+0x02e] sci_int_vector : 0x9 [Type: unsigned short] [+0x030] smi_cmd_io_port : 0xb2 [Type: unsigned long] ;<---SMI_CMD [+0x034] acpi_on_value : 0xf0 [Type: unsigned char] ;<---ACPI_ENABLE [+0x035] acpi_off_value : 0xf1 [Type: unsigned char]之後,繼續跟蹤代碼運行,Windows通過調用WRITE_ACPI_REGISTER,往0xB2寫入0xf0,觸發SMI中斷,調用前面提到的Bios Smi Handler,在硬體上開啟ACPI。注意WRITE_ACPI_REGISTER是個宏定義,其實現如下。#define WRITE_ACPI_REGISTER(AcpiReg, Register, Value) ((*AcpiWriteRegisterRoutine)((AcpiReg), (Register), (Value))) PWRITE_ACPI_REGISTER AcpiWriteRegisterRoutine = DefWriteAcpiRegister;換言之,Windows通過調用是DefWriteAcpiRegister來寫0xB2寄存器,下面代碼段我只貼出和SMI_CMD相關的代碼:VOIDDefWriteAcpiRegister( ACPI_REG_TYPE AcpiReg, ULONG Register, USHORT Value )/*++ Routine Description: Write to the specified ACPI fixed register. Arguments: AcpiReg - Specifies which ACPI fixed register to write to. Register - Specifies which GP register to write to. Not used for PM1x registers. Value - Data to write. Return Value: None. --*/{ switch (AcpiReg) {.. case SMI_CMD: WRITE_PORT_UCHAR((PUCHAR)AcpiInformation->SMI_CMD, (UCHAR)Value); break; default: break; }}
看雪ID:hyjxiaobia
https://bbs.pediy.com/user-home-399589.htm
*本文由看雪論壇 hyjxiaobia 原創,轉載請註明來自看雪社區。