Windbg内核调试(大杂烩)
Windbg內核調試之三: 調試驅動
這次我們通過一個實際調試驅動的例子,來逐步體會Windbg在內核調試中的作用.
由于條件所限,大多數情況下,很多人都是用VMware+Windbg調試內核(VMware的確是個好東西).但這樣的調試需要占用大量的系統資源,對于和我一樣急性子的朋友來說這是不可接受的:).利用雙機調試就可以讓你一邊喝咖啡一邊輕松的看結果,而不至于郁悶的等待每次長達數分鐘的系統響應.有關雙機調試的基本設置,請參考:
本次調試驅動所構建的環境如下:
host computer: WinXP+Windbg
Target computer:? Vista SP1
driver object: syscow
connect setting: 1394數據線
說明: 1.1394卡在很多機器上都已經沒有了,Vista也取消了1394的數據連接協議(調試還是可以的),但不可否認的是利用1394數據線連接調試要比COM口和USB速率快很多(為什么好用的東西卻得不到支持!).2.本次調試的driver是公司開發的某個軟件的驅動程序,拿來嘗試在Vista SP1下track.由于涉及到商業機密,本驅動源代碼不便公開.3.Vista SP1就沒什么好說的了,前些天才發布,MS又一個失敗的典型.
OK,Let's go!
該驅動是一個類型sr.sys(MS的System Restore驅動)的Filter Driver,屬于文件系統過濾驅動,加載在文件系統驅動上層,由Filter Manager負責與用戶層和底層通信.連接到目標機后,按下Ctrl+break中斷當前狀態.(注:你也可以進入到explorer之后再中斷,為了了解驅動加載時的進入點,以及系統啟動時內核的裝態,我們中斷到這里)
Microsoft (R) Windows Debugger? Version 6.6.0007.5
Copyright (c) Microsoft Corporation. All rights reserved.
Using 1394 for debugging
Opened //./DBG1394_INSTANCE01
Waiting to reconnect...
Connected to Windows Vista 6000 x86 compatible target, ptr64 FALSE
Kernel Debugger connection established.
Symbol search path is: D:/symbolslocal; D:/IR/SystemOK/Restore/Driver/objchk_wlh_x86/i386
Executable search path is:
*** ERROR: Symbol file could not be found.? Defaulted to export symbols for ntkrnlmp.exe -
Windows Vista Kernel Version 6000 MP (1 procs) Free x86 compatible
Built by: 6000.16584.x86fre.vista_gdr.071023-1545
Kernel base = 0x81800000 PsLoadedModuleList = 0x81908ad0
System Uptime: not available
WARNING: Whitespace at start of path element
WARNING: Whitespace at start of path element
Break instruction exception - code 80000003 (first chance)
*******************************************************************************
*??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
*?? You are seeing this message because you pressed either????????????????????????????????????????????????????????
*?????? CTRL+C (if you run kd.exe) or,???????????????????????????????????????
*?????? CTRL+BREAK (if you run WinDBG),?????????????????????????????????????
*?? on your debugger machine's keyboard.?????????????????????????????????????
*????????????????????????????????????????????????????????????????????????????
*?????????????????? THIS IS NOT A BUG OR A SYSTEM CRASH??????????????????????
*????????????????????????????????????????????????????????????????????????????
* If you did not intend to break into the debugger, press the "g" key, then??
* press the "Enter" key now.? This message might immediately reappear.? If it
* does, press "g" and "Enter" again.?????????????????????????????????????????
*????????????????????????????????????????????????????????????????????????????
*******************************************************************************
nt!RtlpBreakWithStatusInstruction:
818355e8 cc????????????? int???? 3
然后,在Command line里鍵入lm,查看當前系統加載的模塊和驅動(會發現我們的driver列在其中):
kd> lm
start??? end??????? module name
80404000 80412000?? PCIIDEX??? (deferred)????????????
80412000 80419000?? intelide?? (deferred)????????????
80419000 80429000?? mountmgr?? (deferred)????????????
80429000 80438000?? volmgr???? (deferred)????????????
80438000 8045d000?? pci??????? (deferred)????????????
8045d000 80465000?? msisadrv?? (deferred)????????????
80465000 8046e000?? WMILIB???? (deferred)????????????
8046e000 804b1000?? acpi?????? (deferred)????????????
804b1000 804be000?? WDFLDR???? (deferred)????????????
804be000 80539000?? Wdf01000?? (deferred)????????????
80539000 8061a000?? CI???????? (deferred)????????????
8061a000 80655000?? CLFS?????? (deferred)????????????
80655000 8065d000?? BOOTVID??? (pdb symbols)?????????
8065d000 80666000?? PSHED????? (deferred)????????????
80666000 806c6000?? mcupdate_GenuineIntel?? (deferred)????????????
806c6000 806ce000?? kdcom????? (deferred)????????????
81800000 81b95000?? nt???????? (pdb symbols)?????????
81b95000 81bc9000?? hal??????? (pdb symbols)????????
81c06000 81c0e000?? spldr????? (deferred)????????????
81c0e000 81c44000?? volsnap??? (deferred)????????????
81c44000 81cae000?? ksecdd???? (deferred)????????????
81cae000 81db6000?? Ntfs?????? (deferred)????????????
81db6000 81def000?? NETIO????? (deferred)????????????
81def000 81e1a000?? msrpc????? (deferred)????????????
81e1a000 81f1e000?? ndis?????? (deferred)????????????
81f1e000 81f270c0?? PxHelp20?? (deferred)????????????
81f28000 81f4f000?? syscow32v?? (private pdb symbols)?
81f4f000 81f5f000?? fileinfo?? (deferred)????????????
81f5f000 81f90000?? fltmgr???? (deferred)????????????
81f90000 81fae000?? ataport??? (deferred)????????????
81fae000 81fb6000?? atapi????? (deferred)????????????
81fb6000 82000000?? volmgrx??? (deferred)????????????
8234f000 82358000?? crcdisk??? (deferred)????????????
82358000 82368000?? agp440???? (deferred)????????????
82368000 82389000?? CLASSPNP?? (deferred)????????????
82389000 8239a000?? disk?????? (deferred)????????????
8239a000 823bd000?? fvevol???? (deferred)????????????
823bd000 823e2000?? ecache???? (deferred)????????????
823e2000 823f1000?? mup??????? (deferred)????????????
823f1000 82400000?? partmgr??? (deferred)??
注: 若符號文件沒有加載成功,Windbg會提示響應的符號找不到,不過一般Windbg會自己尋找符號文件路徑.實在找不到時,就包含
srv*c:/symbols*http://msdl.microsoft.com/download/symbols, 然后reload一下(!reload).
另外,鍵入lm t n, 我們可以查看更為詳細的模塊及驅動信息.
然后,鍵入!thread和Kp,查看當前的線程詳細信息和堆棧(或者Alt+6也可以看stack).注意當前thread的ID:
kd> !thread
THREAD 84254ae8? Cid 0004.0008? Teb: 00000000 Win32Thread: 00000000 RUNNING on processor 0
Not impersonating
Owning Process??????????? 84254d90?????? Image:???????? System
Wait Start TickCount????? 0????????????? Ticks: 1 (0:00:00:00.015)
Context Switch Count????? 1????????????
UserTime????????????????? 00:00:00.0000
KernelTime??????????????? 00:00:00.0015
Win32 Start Address nt!Phase1Initialization (0x819433ae)
Stack Init 81c06000 Current 81c05db8 Base 81c06000 Limit 81c03000 Call 0
Priority 31 BasePriority 8 PriorityDecrement 0
ChildEBP RetAddr? Args to Child?????????????
81c05af0 818aa92c 00000001 81867999 0002625a nt!RtlpBreakWithStatusInstruction (FPO: [1,0,0])
81c05af8 81867999 0002625a 00000000 00000001 nt!KdCheckForDebugBreak+0x22 (FPO: [0,0,0])
81c05b18 81836cfd 81928100 000000d1 81c05b9c nt!KeUpdateRunTime+0x270
81c05b18 81ba4130 81928100 000000d1 81c05b9c nt!KeUpdateSystemTime+0xed (FPO: [0,2] TrapFrame @ 81c05b28)
81c05b9c 81ba3fd0 81bb28a0 8181dced 81c05bc8 hal!XmGetCodeByte+0x30 (FPO: [Non-Fpo])
81c05bac 81ba40c5 81bb28a0 0000c000 00001da4 hal!XmEmulateStream+0x88 (FPO: [Non-Fpo])
81c05bc8 81ba374d 00000010 81c05c0c 8181dced hal!XmEmulateInterrupt+0x80 (FPO: [Non-Fpo])
81c05bdc 81ba0a1c 00000010 81c05c0c 00000000 hal!x86BiosExecuteInterruptShadowed+0x43 (FPO: [Non-Fpo])
81c05bf8 81ba0a5b 00000010 81c05c0c 00000000 hal!x86BiosCall+0x22 (FPO: [Non-Fpo])
81c05c2c 80656697 80806ae0 8080f438 00000000 hal!HalpBiosDisplayReset+0x25 (FPO: [Non-Fpo])
81c05c58 81b2cd6d 00000001 81b0ab01 80806ae0 BOOTVID!VidInitialize+0x135 (FPO: [Non-Fpo])
81c05c7c 81b3f098 00000001 80806ae0 00000007 nt!InbvDriverInitialize+0x81
81c05d74 819433bb 81c05dc0 819afbad 80806ae0 nt!Phase1InitializationDiscard+0xd0
81c05d7c 819afbad 80806ae0 81c0e680 00000000 nt!Phase1Initialization+0xd
81c05dc0 8189a346 819433ae 80806ae0 00000000 nt!PspSystemThreadStartup+0x9d
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16
kd> kp
ChildEBP RetAddr?
81c05af0 818aa92c nt!RtlpBreakWithStatusInstruction
81c05af8 81867999 nt!KdCheckForDebugBreak+0x22
81c05b18 81836cfd nt!KeUpdateRunTime+0x270
81c05b18 81ba4130 nt!KeUpdateSystemTime+0xed
81c05b9c 81ba3fd0 hal!XmGetCodeByte+0x30
81c05bac 81ba40c5 hal!XmEmulateStream+0x88
81c05bc8 81ba374d hal!XmEmulateInterrupt+0x80
81c05bdc 81ba0a1c hal!x86BiosExecuteInterruptShadowed+0x43
81c05bf8 81ba0a5b hal!x86BiosCall+0x22
81c05c2c 80656697 hal!HalpBiosDisplayReset+0x25
81c05c58 81b2cd6d BOOTVID!VidInitialize+0x135
81c05c7c 81b3f098 nt!InbvDriverInitialize+0x81
81c05d74 819433bb nt!Phase1InitializationDiscard+0xd0
81c05d7c 819afbad nt!Phase1Initialization+0xd
81c05dc0 8189a346 nt!PspSystemThreadStartup+0x9d
00000000 00000000 nt!KiThreadStartup+0x16
鍵入!process [PID] 0, 查到當前進程:
kd> !process 0004.0008 0
PROCESS 84254d90? SessionId: none? Cid: 0004??? Peb: 00000000? ParentCid: 0000
??? DirBase: 00122000? ObjectTable: 830001d0? HandleCount:?? 1.
??? Image: System
??? VadRoot 00000000 Vads 0 Clone 0 Private 0. Modified 0. Locked 0.
??? DeviceMap 00000000
??? Token???????????????????????????? 83003830
??? ElapsedTime?????????????????????? 00:00:00.015
??? UserTime????????????????????????? 00:00:00.000
??? KernelTime??????????????????????? 00:00:00.000
??? QuotaPoolUsage[PagedPool]???????? 0
??? QuotaPoolUsage[NonPagedPool]????? 0
??? Working Set Sizes (now,min,max)? (4, 0, 0) (16KB, 0KB, 0KB)
??? PeakWorkingSetSize??????????????? 0
??? VirtualSize?????????????????????? 0 Mb
??? PeakVirtualSize?????????????????? 0 Mb
??? PageFaultCount??????????????????? 0
??? MemoryPriority??????????????????? BACKGROUND
??? BasePriority????????????????????? 8
??? CommitCharge????????????????????? 0
??????? THREAD 84254ae8? Cid 0004.0008? Teb: 00000000 Win32Thread: 00000000 RUNNING on processor 0
在lm命令列出的信息中,start是模塊的起始地址,通過鍵入"u 驅動起始地址",我們可以反匯編出它的代碼:
kd> u 81f28000
syscow32v!SysCowAllocatePostCopyWorkItem <PERF> (syscow32v+0x0):
81f28000 4d????????????? dec???? ebp
syscow32v!SysCowAllocatePostCopyWorkItem <PERF> (syscow32v+0x1):
81f28001 5a????????????? pop???? edx
syscow32v!SysCowAllocatePostCopyWorkItem <PERF> (syscow32v+0x2):
81f28002 90????????????? nop
syscow32v!SysCowAllocatePostCopyWorkItem <PERF> (syscow32v+0x3):
81f28003 0003??????????? add???? byte ptr [ebx],al
syscow32v!SysCowAllocatePostCopyWorkItem <PERF> (syscow32v+0x5):
81f28005 0000??????????? add???? byte ptr [eax],al
syscow32v!SysCowAllocatePostCopyWorkItem <PERF> (syscow32v+0x7):
81f28007 000400????????? add???? byte ptr [eax+eax],al
syscow32v!SysCowAllocatePostCopyWorkItem <PERF> (syscow32v+0xa):
81f2800a 0000??????????? add???? byte ptr [eax],al
syscow32v!SysCowAllocatePostCopyWorkItem <PERF> (syscow32v+0xc):
81f2800c ff????????????? ???
逐步查找(enter),最終我們可以發現driver的入口點.這個過程其實是非常慢的,因為系統內核在加載驅動實際代碼的過程中進行了N多次調用.如果我們本身有驅動的代碼,也可以直接包含源代碼路徑,通過在實際代碼中設置斷點,讓Windbg自己中斷到相應的代碼位置(在實際調試內核的過程中,這幾乎是不可能的,因為你不會得到Windows內核或某個驅動程序的源代碼.Linux系列的某些driver們又另當別論).這里為了方便,我包含了syscow的源代碼,增加斷點直接走到DriverEntry例程:
kd> u 81f42780
syscow32v!DriverEntry [隱藏了address]:
81f42780 8bff??????????? mov???? edi,edi
81f42782 55????????????? push??? ebp
81f42783 8bec??????????? mov???? ebp,esp
81f42785 51????????????? push??? ecx
81f42786 c745fc010000c0? mov???? dword ptr [ebp-4],0C0000001h
81f4278d a1749cf481????? mov???? eax,dword ptr [syscow32v!SysCowDbgFlags (81f49c74)]
81f42792 83e004????????? and???? eax,4
81f42795 7424??????????? je????? syscow32v!DriverEntry+0x3b (81f427bb)
其中顯示的匯編代碼,是內核調用驅動是進行的操作,其實也和實際代碼相對應.
在driver代碼中,如果要查看當前參數值,用dv命令:
kd> dv
?? DriverObject = 0x84663730
?? RegistryPath = 0x8084b560
???????? status = 8
?????? dontload = 0
另外,用"dt 參數名"可以看某個參數的當前值.
kd> dt DriverObject
Local var @ 0x81c05af8 Type _DRIVER_OBJECT*
0x84663730
?? +0x000 Type???????????? : 4
?? +0x002 Size???????????? : 168
?? +0x004 DeviceObject???? : (null)
?? +0x008 Flags??????????? : 2
?? +0x00c DriverStart????? : 0x81f28000
?? +0x010 DriverSize?????? : 0x27000
?? +0x014 DriverSection??? : 0x84230a68
?? +0x018 DriverExtension? : 0x846637d8 _DRIVER_EXTENSION
?? +0x01c DriverName?????? : _UNICODE_STRING "/FileSystem/SysCow"
?? +0x024 HardwareDatabase : 0x81af6ed8 _UNICODE_STRING "/REGISTRY/MACHINE/HARDWARE/DESCRIPTION/SYSTEM"
?? +0x028 FastIoDispatch?? : (null)
?? +0x02c DriverInit?????? : 0x81f4a005???? syscow32v!GsDriverEntry+0
?? +0x030 DriverStartIo??? : (null)
?? +0x034 DriverUnload???? : (null)
?? +0x038 MajorFunction??? : [28] 0x8189a5c1???? nt!IopInvalidDeviceRequest+0
這樣,我們就可以通過上述的這些命令,逐步分析一個驅動程序在加載和執行過程中的情況。另外,Windbg還有眾多內核調試命令,如!irp可以查看一個對象的數據結構,!devobj可以查看設備對象等,在今后的操作系統內核學習過程中會不斷用到這些命令。
從上面的操作過程我們可以看出,利用Windbg調試驅動程序,或者說進行內核調試,是非常方便的。如果我們對Windows內核有一定的了解,同時擁有一定匯編語言的功底,就可以有Windbg進行簡單的系統排障(比如系統加載是出現藍屏,或是某個系統模塊出現問題)、驅動學習等。同時,這個過程也可以讓我們更深入的理解操作系統原理。另外,Windbg也可以進行系統服務(service)的調試,這就是User mode的調試過程了。
posted @ 2008-03-27 21:16 Da Vinci 閱讀(138) | 評論 (1) |?編輯
2008年3月25日
.NET下午茶之三: 淺析CLR應用程序域
CLR(公共語言運行庫)可以說是整個.NET平臺的核心元素.基本上托管應用程序所有的操作都是需要CLR的監管和處理.這些操作包括進程內應用程序的加載, IL語言轉換為機器語言, 異常管理, 垃圾回收,加載程序集等等.
CLR執行托管代碼前,實際上會創建三個應用程序域, 它們是系統域(System Domain),共享域(Shared Domain)和缺省應用程序域(Default AppDomain).其中系統域和共享域對于托管代碼和CLR的宿主程序(如控制臺程序,ASP.NET等)不可見.域可以通過AppDomain.CreateDomain方法創建(下文會詳細敘述這個過程),在非托管的代碼中,可以使用一個ICORRutimeHost的接口創建(這個接口我也沒用過,一般都是托管的代碼).對于復雜的宿主程序,由網站根據應用程序的數目來建立域。
???????????????????? 圖1.CLR啟動程序創建的域
?????????? (轉載自Inside .NET Framework:CLR runtime object)
注:下文中的AppDomain特指應用程序域,對于系統域和共享域保持原來的指定
要注意的是,這里的域不同于C#類中域的概念.C#類中域指類和對象相關的變量,這里的域其實是一種輕量的進程,你完全可以把它當成進程來理解.有人也許不明白既然有了進程的概念,干嗎又搞出一個域來.其實這是為了實現在服務器中加載多個應用程序才提出的.AppDomain與進程相比優勢在于:
???? 1.所需系統資源更少
???? 2.進程之間的AppDomain可以共享資源
但是千萬別把AppDomain和線程搞混,這二者之間沒有從屬的關系.地球人都知道線程是進程的孩子,但是線程和AppDomain的關系類似于正交關系.同一時間內多個線程可以運行在一個AppDomain中,多個AppDomain也可以跨越一個線程.
下面, 我們先大致看一下系統域和共享域的一些基本概念,再著重探討AppDomain的知識.
系統域(System Domain):系統域初始化共享域和AppDomain。它在共享域中加載了mscorlib.dll()。同時,系統域產生了進程的接口ID(不知道這樣描述對不對),以此來創建AppDomain的接口虛擬表映射的接口。在進程中,系統域監控所有的域,加載(load)和卸載(unload)AppDomain也有它完成。可以認為系統域是CLR中的基礎域。
共享域(Shared Domain):在.NET中,不是所有的代碼都屬于某個域,但這些代碼卻是用戶代碼所必需的。共享域就負責加載這部分代碼。比如大家熟悉的Object,Array,String,delegate等。這些代碼在CLR啟動程序時先被加載到共享域中.共享域中有一個程序集映射表,通過這個表來查找共享的程序集的依賴關系. 可以認為共享域是CLR中的帶有索引的共享文件夾.
注:用戶代碼和控制臺程序也可以加載到共享域.(具體方法?..不急..以后告訴你)
AppDomain
終于說到學習.NET要接觸最多的AppDomain了.其實CLR啟動應用程序時,就會自動創建一個AppDomain的實例,叫默認的AppDomain(Default AppDomain).大部分的應用程序在運行期間只創建一個域.有些應用程序有多個AppDomain,它們之間由.NET Remoting通信。AppDomain之間是彼此獨立的。一個AppDomain無法訪問其他的AppDomain的程序集和對象(并不意味著不能相互通信),并且可以定義自己的程序集訪問策略。
通過System.AppDomain類的實例,可以獲得進程中一個AppDomain的引用:
??? using System;
??? using System.Threading;
??? class MyAppDomain
?? {
??????? static void Main()
?????? {
?????????? Thread.CurrentThread.Name = "MyDomain";
?????????? AppDomainSetup info = new AppDomainSetup();
?????????? AppDomain myAppDomain = AppDomain.CurrentDomain;
?????????? AppDomain newDomain = AppDomain.CreateDomain("MyDomain", null, info);
???
?????????? myAppDomain.ExecuteAssembly("AssemblyName.exe"); // load your assembly name
?????????? AppDomain.unload(myDomain);
??????? }
??? }
上面代碼創建了一個AppDomain的實例,并調用ExecuteAssembly方法加載一個程序集。其中AppDomainSetup為CLR定位信息。unload為AppDomain類的卸載域方法。注意:這段代碼中ExecuteAssembly()方法的線程調用了AssemblyName.exe程序集的線程。這樣一個線程就跨越了兩個AppDomain。這也說明了AppDomain和線程之間沒有包含關系。
到這里,我們已經對CLR中域的概念,各種域的結構有了一個大概的認識。AppDomain是CLR中最基本的概念,在CLR的整個框架中都與它有著千絲萬縷的聯系,掌握它是學習CLR的基礎。有關AppDomain間通信的方法,我們在.NET Remoting篇中再詳細闡述。
posted @ 2008-03-25 22:25 Da Vinci 閱讀(32) | 評論 (2) |?編輯
2008年3月22日
Windbg內核調試之二: 常用命令
運用Windbg進行內核調試, 熟練的運用命令行是必不可少的技能. 但是面對眾多繁瑣的命令, 實在是不可能全部的了解和掌握. 而了解Kernel正是需要這些命令的指引, 不斷深入理解其基本的內容. 下面, 將介紹最常用的一些指令, 使初學Kernel調試的朋友們能有一個大致的了解. 至于如何熟練的運用它們, 還需要實際的操作過程中進行反復的琢磨.
Windbg能夠方便的進行遠程調試和本地進程調試(只限于User模式), 遠程調試又分User mode和Kernel mode兩種. 個人認為用Windbg進行遠程的User mode調試還不如用Visual Studio來的方便, 畢竟要一些相應的配置才行, 而Visual Studio只需要遠程機器的IP地址即可.(當然, 如果你在LiveCD或WinPE下進行User mode的調試, 這時候沒有VS, Windbg就是不二之選了). Windbg的優勢就在于遠程的Kernel模式的調試, 這是VS所做不到的.
1. 首先是設置符號路徑(無論在User mode下還是Kernel mode下都需要), 在Windbg的符號路徑對話框中, 輸入以下符號路徑:
????? srv*c:/symbols*http://msdl.microsoft.com/download/symbols
這個符號路徑是自動在微軟給定的symbols路徑下進行自動搜索, 一些常用的符號都可以利用這個鏈接自動找到. 當然, 如果你的本地已經有相應的符號路徑, 包含本地的也可以.
2. 等Windbg的聯機狀態設置好后, 按下Ctrl+break, 中斷當前的Kernel狀態, 在Windbg的命令行窗口輸入"?", 則會輸出幫助菜單, 在這個Menu中會顯示一些常用的命令:
? (1)斷點指令 ???? B[C|D|E] [<bps>] ???? clear|disable|enable breakpoints ???? BL ???? list breakpoints ???? BP <address> ???? set soft breakpoints ???? BA <access> <size> <addr> ???? break on access ? (2)數據查看指令 ???? D[type][<range>] ???? dump memory ???? DT [-n|y] [[mod!]name] [[-n|y]fields][address] [-l list] [-a[]|c|i|o|r[#]|v] ???? dump using type information ???? DV [<name>]? ???? dump local variables ? (3)數據修改指令 ???? E[type] <address> [<values>] ???? enter memory values ? (4)運行 ???? G[H|N] [=<address> [<address>...]]? ???? go ???? P [=<addr>] [<value>] ???? step over ? (5)堆棧操作 ???? K[b|p|P|v] ? (6)顯示加載的模塊列表 ???? LM ???? list modules ? (7)寄存器操作 ???? R [[<reg> [= <expr>]]] ???? view or set registers ? (8)Search指令 ???? S[<opts>] <range> <values> ???? search memory ? (9)跟蹤指令T,TA,TB,TC,WT,P,PA,PC ? (10)退出 ????? Q
? (11)反匯編
????? U[<range>]
其中最常用的就是反匯編操作和顯示模塊操作. LM命令顯示當前加載的模塊. 當你連接過程中, Windbg提示相應的module找不到時, 就可以運用這個命令進行查看. LM的一個擴展命令是"lm t n", 這個命令顯示當前所有加載的驅動信息(過去的命令是!driver),在調試內核驅動的過程中非常有用,可以找到相應驅動的起始地址。反匯編命令u, 可以在相應的地址中逐步的解析代碼,這在內核調試中是最常用的一種查看代碼的方式。
除上面的一些基本命令之外,還有一些非常有用的指令:
?
? (1)K[KB|KP]
???? 顯示當前的堆棧,當然也可以用alt+6直接調出窗口顯示
?
? (2)!process
???? 顯示當前的進程EXPROCESS狀態,!process 0 0 顯示所有的進程狀態
? (3)!thread
???? 顯示當前的線程狀態,dt nt!_ethread顯示ETHREAD結構
? (4)!drvobj [path]
???? 列出當前的驅動程序在驅動對象中的例程,其中path是驅動的設備路徑,例如: !drvobj /filesystem/fat 2 列出FAT文件系統驅動的例程
? (5)dt nt!_*
???? 查看內核的數據結構
?
? (6)!stack 0
???? 顯示線程當前地址
?
? (7)!ioapic
???? 查看I/O的中斷控制器
? (8)!irql
???? 查看CPU的IRQL,這在CPU中斷調試中非常有用
? (9)!exqueue
???? 可以看系統輔助的線程列表
? (10)!reg viewlist
???? 注冊表的存儲顯示,!reg hivelist顯示注冊表一個存儲的內存使用量
? (11)!vm
???? 顯示系統的內存池信息
? (12)dt _TOKEN
???? 顯示內部訪問令牌
? (13)!object /device
???? 顯示設備對象信息,用winobj工具也可以看到
以上命令是一些常用的內核調試命令, 還有非常多的命令技巧, 不可能全部一一解釋. 在今后的文章, 還會結合具體的調試過程分析進行逐步介紹. 內核的調試無非是處理器, 系統設備, 內存, 進程線程, 注冊表, 驅動這幾大類的信息, 每一類都有很多的命令, 需要我們在實際的調試過程中不斷認識和體會它的用法. 當然, 理解這些命令用法的前提是需要我們對操作系統內部構造有一個清晰的認識. 這需要不斷的學習, 對OS有一個全局的把握, 這樣才能更深入的理解它. 而通過Windbg的內核調試, 我們有一種更好的方式直接與Kernel打交道, 這對我們深入理解和認識操作系統有很大的幫助. 下一節我們將通過一個實際的調試driver的例子, 來進一步認識Windbg在內核調試中的作用.
posted @ 2008-03-22 12:30 Da Vinci 閱讀(102) | 評論 (0) |?編輯
2008年3月20日
Windbg內核調試之一: Vista Boot Config設置
Windbg進行內核調試,需要一些基本的技巧和設置,在這個系列文章中,我將使用Windbg過程中所遇到的一些問題和經驗記錄下來,算是對Kernel調試的一個總結,同時也是學習Windows系統內核的另一種過程。
很多人說Windbg不如SoftIce好用, 但是我使用過程中還是覺得Windbg能更好的反映系統狀態, 而且相比SoftIce, Windbg更穩定(雖然它的部分操作略顯復雜), 下面介紹Windbg的Kernel模式調試第一部分: 雙機連接設置.
Vista和XP不同, 沒有boot.ini文件, 需要用bcdedit進行啟動設置。(關于啟動數據配置編輯器BCD的具體設置, 參見另一篇文章: (From MS)Vista: 啟動配置數據編輯器(BCD))
在administrator權限下, 進入command line模式,? 鍵入bcdedit命令, 會出現以下界面:
?
然后, 設置端口COM1, baudrate為115200 (除COM1外, 也可以用1394或USB. 1394用起來比COM口快多了, 當然前提是你需要有1394卡及其驅動. 很惡心的是Vista不再支持1394的文件傳輸協議, 但是用windbg雙機調試還是可以的)
命令為:
bcdedit?/dbgsettings?{serial?[baudrate:value][debugport:value]?| 1394?[channel:value]?|?usb?}
接著, 我們需要復制一個開機選項, 以進入OS的debug模式
命令為:
bcdedit?/copy?{current}?/d?DebugPoint
DebugPoint為選項名稱, 名字可以自己定義. 然后復制得到的ID號.
接著增加一個新的選項到引導菜單
bcdedit?/displayorder?{current}?{ID}
這里的{ID}的ID值是剛生成的ID值.
激活DEBUG : bcdedit?/debug?{ID}?ON
這里的{ID}?的ID值還是剛才的ID值.
命令執行成功后, 重新啟動機器.
選擇DebugPoint登錄,開啟Windbg
連接成功, 則顯示如下:
Microsoft?(R)?Windows?Debugger??Version?6.6.0007.5
Copyright?(c)?Microsoft?Corporation.?All?rights?reserved.
Opened?//./pipe/com_1
Waiting?to?reconnect...
Connected?to?Windows?Vista?6000?x86?compatible?target,?ptr64?FALSE
Kernel?Debugger?connection?established.
Symbol?search?path?is:?symsrv*symsrv.dll*F:/symbols*http://msdl.microsoft.com/download/symbols
Executable?search?path?is:?
Windows?Vista?Kernel?Version?6000?MP?(1?procs)?Free?x86?compatible
Built?by:?6000.16386.x86fre.vista_rtm.061101-2205
Kernel?base?=?0x81800000?PsLoadedModuleList?=?0x81911db0
System?Uptime:?not?available
Break?instruction?exception?-?code?80000003?(first?chance)
*******************************************************************************
*????????????????????????????????????????????????????????????????????????????
*???You?are?seeing?this?message?because?you?pressed?either???????????????????
*???????CTRL+C?(if?you?run?kd.exe)?or,???????????????????????????????????????
*???????CTRL+BREAK?(if?you?run?WinDBG),??????????????????????????????????????
*???on?your?debugger?machine's?keyboard.?????????????????????????????????????
*????????????????????????????????????????????????????????????????????????????
*???????????????????THIS?IS?NOT?A?BUG?OR?A?SYSTEM?CRASH??????????????????????
*????????????????????????????????????????????????????????????????????????????
*?If?you?did?not?intend?to?break?into?the?debugger,?press?the?"g"?key,?then??
*?press?the?"Enter"?key?now.??This?message?might?immediately?reappear.??If?it
*?does,?press?"g"?and?"Enter"?again.?????????????????????????????????????????
*????????????????????????????????????????????????????????????????????????????
*******************************************************************************
nt!RtlpBreakWithStatusInstruction:
81881760?cc??????????????int?????3
總結: 雖然利用VMware虛擬機能更方便的設置雙機的調試環境, 而且這種模擬環境也是大多數人使用的(方便), 但是如果有雙機條件的話,? 還是希望大家能夠使用兩臺機器, 因為用虛擬機進行Kernel調試, 真不是一般的慢! 基本就等于死機. 即時你的主機內存2G, 分給VMware1G, 還是會相當卡(Kerenl模式與User模式不同).
posted @ 2008-03-20 14:38 Da Vinci 閱讀(71) | 評論 (0) |?編輯
Windows程序設計:文件操作(轉載)
操作文件基本上是每個應用程序都必須做的事情。除了必要的配置信息外,用戶的工作最終都要以文件的形式保存到磁盤上。保存和獲取這些信息可以使用獨立的磁盤文件,也可以使用系統自帶的數據庫——注冊表。
本章首先介紹底層操作文件的API函數和MFC中對應的CFile類;然后介紹一些與操作文件相關的邏輯驅動器和目錄方面的知識,包括驅動器的格式化和卷標設置、目錄的創建和刪除等;接著,本章介紹使用 API函數和ATL庫中的CRegKey類操作注冊表的方法;本章還重點討論了內存映射文件在讀寫磁盤文件和建立共享內存方面的應用;本章最后介紹一個多線程的文件分割系統的開發過程。
8.1? 文件操作
文件的輸入輸出(I/O)服務是操作系統的重要部分。Windows提供了一類API函數來讀、寫和管理磁盤文件。MFC將這些函數轉化為一個面向對象的類——CFile,它允許將文件視為可以由 CFile成員函數操作的對象,如Read和Write等。CFile類實現了程序開發者執行底層文件I/O需要的大部分功能。
并不是在任何時候使用CFile類都是方便的,特別是要與底層設備(如COM口、設備驅動)進行交互的時候,所以本節主要討論管理文件的API函數。事實上,了解這些函數之后,自然就會使用CFile類了。
8.1.1? 創建和讀寫文件
使用API函數讀寫文件時,首先要使用 CreateFile函數創建文件對象(即打開文件),調用成功會返回文件句柄;然后以此句柄為參數調用ReadFile和WriteFile函數,進行實際的讀寫操作;最后調用CloseHandle函數關閉不再使用的文件對象句柄。
1.打開和關閉文件
CreateFile是一個功能相當強大的函數,Windows下的底層設備差不多都是由它打開的。它可以創建或打開文件、目錄、物理磁盤、控制臺緩沖區、郵槽和管道等。調用成功后,函數返回能夠用來訪問此對象的句柄,其原型如下:
HANDLE CreateFile (
? LPCTSTR lpFileName,?? ???????????????????????????????????????????? // 要創建或打開的對象的名稱
? DWORD dwDesiredAccess, ??????????????????????????????? // 文件的存取方式
? DWORD dwShareMode, ?????????????????????????????????????????????? // 共享屬性
? LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全屬性
??DWORD dwCreationDisposition, ??????????????????????????????? // 文件存在或不存在時系統采取的行動
? DWORD dwFlagsAndAttributes,?????????????????????????????????? // 新文件的屬性
? HANDLE hTemplateFile ?????????????????????????????????????????????? // 一個文件模板的句柄
);
各參數含義如下。
(1)lpFileName參數是要創建或打開的對象的名稱。如果打開文件,直接在這里指定文件名稱即可;如果操作對象是第一個串口,則要指定“COM1”為文件名,然后就可以像操作文件一樣操作串口了;如果要打開本地電腦上的一個服務,要以“"""."服務名稱"”為文件名,其中的“.”代表本地機器;也可以使用CreateFile打開網絡中其他主機上的文件,此時的文件名應該是“""主機名"共享目錄名"文件名”。
(2)dwDesiredAcces參數是訪問方式,它指定了要對打開的對象進行何種操作。指定GENERIC_READ標志表示以只讀方式打開;指定GENERIC_WRITE標志表示以只寫方式打開;指定這兩個值的組合,表示要同時對打開的對象進行讀寫操作。
(3)dwShareMode參數指定了文件對象的共享模式,表示文件打開后是否允許其他代碼以某種方式再次打開這個文件,它可以是下列值的一個組合:
l????????? 0???????????????????????????????????????????? 不允許文件再被打開。C語言中的fopen函數就是這樣打開文件的
l????????? FILE_SHARE_DELETE????? 允許以后的程序代碼對文件刪除文件(Win98系列的系統不支持這????? ???????? ???????? ???????? ???????? 個標志)
l????????? FILE_SHARE_READ????????? 允許以后的程序代碼以讀方式打開文件
l????????? FILE_SHARE_WRITE???????? 允許以后的程序代碼以寫方式打開文件
(4)dwCreationDisposition參數指定了當文件已存在或者不存在時系統采取的動作。在這里設置不同的標志就可以決定究竟是要打開文件,還是要創建文件。參數的可能取值如下:
l????????? CREATE_ALWAYS??? 創建新文件。如果文件存在,函數會覆蓋這個文件,清除存在的屬性
l????????? CREATE_NEW??????????????????? 創建新文件。如果文件存在,函數執行失敗
l????????? OPEN_ALWAYS????????????????? 如果文件已經存在,就打開它,不存在則創建新文件
l????????? OPEN_EXISTING?????????????? 打開存在的文件。如果文件不存在,函數執行失敗
l????????? TRUNCATE_EXISTING??? 打開文件并將文件截斷為零,當文件不存在時函數執行失敗
(5)dwFlagsAndAttributes參數用來指定新建文件的屬性和標志。文件屬性可以是下面這些值的組合:
l????????? FILE_ATTRIBUTE_ARCHIVE?? ???????? 標記歸檔屬性
l????????? FILE_ATTRIBUTE_HIDDEN ???????????? 標記隱藏屬性
l????????? FILE_ATTRIBUTE_READONLY ?????? 標記只讀屬性
l????????? FILE_ATTRIBUTE_READONLY ?????? 標記系統屬性
l????????? FILE_ATTRIBUTE_TEMPORARY ?? 臨時文件。操作系統會盡量把所有文件的內容保持在內?????? ???????? ???????? ???????? ???????? ???????? ???????? 存中以加快存取速度。使用完后要盡快將它刪除
此參數還可同時指定對文件的操作方式,下面是一些比較常用的方式:
l????????? FILE_FLAG_DELETE_ON_CLOSE ?? 文件關閉后系統立即自動將它刪除
l????????? FILE_FLAG_OVERLAPPED ????????????? 使用異步讀寫文件的方式
l????????? FILE_FLAG_WRITE_THROUGH ???? 系統不會對文件使用緩存,文件的任何改變都會被系統?????? ???????? ???????? ???????? ???????? ???????? ???????? 立即寫入硬盤
(6)hTemplateFile參數指定了一個文件模板句柄。系統會復制該文件模板的所有屬性到當前創建的文件中。Windows 98系列的操作系統不支持它,必須設為NULL。
打開或創建文件成功時,函數返回文件句柄,失敗時返回INVALID_HANDLE_VALUE(-1)。如果想再詳細了解失敗的原因,可以繼續調用GetLastError函數。
用不同的參數組合調用CreateFile函數可以完成不同的功能,例如,下面的代碼為讀取數據打開了一個存在的文件。
???????? HANDLE hFile;
???????? hFile = ::CreateFile("myfile.txt",??? ???????? // 要打開的文件
?????????????????? GENERIC_READ,???????????????????????? // 要讀這個文件
?????????????????? FILE_SHARE_READ,?????????????????? ???????? // 允許其他程序已只讀形式再次打開它
?????????????????? NULL,?????????????????????????????????? ???????? // 默認安全屬性
?????????????????? OPEN_EXISTING,??????????????????????? // 僅僅打開存在的文件(如果不存不創建)
?????????????????? FILE_ATTRIBUTE_NORMAL,? // 普通文件
?????????????????? NULL);????????????????????????????????? ???????? // 沒有模板
???????? if(hFile == INVALID_HANDLE_VALUE)
???????? { ?????????????? ……// 不能夠打開文件?????? }
僅當當前目錄中存在名稱為myfile.txt的文件時,上面的CreateFile才能執行成功。由于為dwCreationDisposition參數指定了OPEN_EXISTING,所以當要打開的文件不存在時,CreateFile返回INVALID_HANDLE_VALUE,而不會創建這個文件。如果想創建一個文件以便向里面寫入數據,可以使用下面的代碼:
???????? HANDLE hFile;
???????? hFile = CreateFile("myfile.txt",????? ?????????????????? // 要創建的文件
?????????????????? GENERIC_WRITE,?????????????????????? // 要寫這個文件
?????????????????? 0,???????????????????????????????????????????????????? // 不共享
?????????????????? NULL,??????????????????????????????????????????? // 默認安全屬性
?????????????????? CREATE_ALWAYS,????????????????????? // 如果存在就覆蓋
?????????????????? FILE_ATTRIBUTE_NORMAL,? // 普通文件
?????????????????? NULL);?????????????????????????????????????????? // 沒有模板
???????? if(hFile == INVALID_HANDLE_VALUE)
???????? { ?????????????? ……// 不能夠打開文件?????? }
要關閉打開的文件,直接以CreateFile返回的文件句柄調用CloseHandle函數即可。
2.移動文件指針
系統為每個打開的文件維護一個文件指針,指定對文件的下一個讀寫操作從什么位置開始。隨著數據的讀出或寫入,文件指針也隨之移動。當文件剛被打開時,文件指針處于文件的頭部。有時候需要隨機讀取文件內容,這就需要先調整文件指針,SetFilePointer函數提供了這個功能,原型如下:
DWORD SetFilePointer (
? ?????????????? HANDLE hFile,? ???????????????????????? // 文件句柄
? ?????????????? LONG lDistanceToMove, ? ???????? // 要移動的距離
? ?????????????? PLONG lpDistanceToMoveHigh,? ?????? // 移動距離的高32位,一般設置為NULL
? ?????????????? DWORD dwMoveMethod ???????? // 移動的模式
?????????????????? );
dwMoveMethod參數指明了從什么地方開始移動,可以是下面的一個值:
l????????? FILE_BEGIN?????????????? 開始移動位置為0,即從文件頭部開始移動
l????????? FILE_CURRENT??????? 開始移動位置是文件指針的當前值
l????????? FILE_END??????????????????????????? 開始移動位置是文件的結尾,即從文件尾開始移動
函數執行失敗返回-1,否則返回新的文件指針的位置。
文件指針也可以移動到所有數據的后面,比如現在文件的長度是100 KB,但還是可以成功的將文件指針移動到1000 KB的位置。這樣做可以達到擴展文件長度的目的。
SetEndOfFile函數可以截斷或者擴展文件。該函數移動指定文件的結束標志(end-of-file,EOF)到文件指針指向的位置。如果文件擴展,舊的EOF位置和新的EOF位置間的內容是未定義的。SetEndOfFile函數的用法如下:
BOOL SetEndOfFile(HANDLE hFile );
截斷或者擴展文件時,要首先調用SetFilePointer移動文件指針,然后再調用SetFilePointer函數設置新的文件指針位置為EOF。
3.讀寫文件
讀寫文件的函數是ReadFile和WriteFile,這兩個函數既可以同步讀寫文件,又可以異步讀寫文件。而函數ReadFileEx和WriteFileEx只能異步讀寫文件。
從文件讀取數據的函數是ReadFile,向文件寫入數據的函數是WriteFile,操作的開始位置由文件指針指定。這兩個函數的原型如下:
BOOL ReadFile(
? ???? HANDLE hFile,?????????????????????????????????????? // 文件句柄
? ???? LPVOID lpBuffer,?????? ?????????????????????????????????? // 指向一個緩沖區,函數會將讀出的數據返回到這里
? ???? DWORD nNumberOfBytesToRead, ???? // 要求讀入的字節數
? ???? LPDWORD lpNumberOfBytesRead, ?? // 指向一個DWORD類型的變量,
?????????????????????????????????????????????????????????????????????????? // 用于返回實際讀入的字節數
? ???? LPOVERLAPPED lpOverlapped ?????????????????? // 以便設為NULL
);
BOOL WriteFile (hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
當用WriteFile寫文件時,寫入的數據通常被Windows暫時保存在內部的高速緩存中,等合適的時候再一并寫入磁盤。如果一定要保證所有的數據都已經被傳送,可以強制使用FlushFileBuffers函數來清空數據緩沖區,函數的惟一參數是要操作的文件句柄。
BOOL FlushFileBuffers (HANDLE hFile );
4.鎖定文件
當對文件數據的一致性要求較高時,為了防止程序在寫入的過程中其他進程剛好在讀取寫入區域的內容,可以對已打開文件的某個部分進行加鎖,這就可以防止其他進程對該區域進行讀寫。加鎖和解鎖的函數是LockFile和UnlockFile,它們的原型如下:
BOOL? LockFile(
??? HANDLE hFile, ????????????????????????????????????????????? // 文件句柄
??? DWORD dwFileOffsetLow, ?????????????????????????? // 加鎖的開始位置
??? DWORD dwFileOffsetHigh,
??? DWORD nNumberOfBytesToLockLow, ?????? // 加鎖的區域的大小
??? DWORD nNumberOfBytesToLockHigh
??? );
UnlockFile ( hFile, dwFileOffsetLow, dwFileOffsetHigh,
?????????????????? nNumberOfBytesToUnlockLow, nNumberOfBytesToUnlockHigh);
dwFileOffsetLow和 dwFileOffsetHigh參數組合起來指定了加鎖區域的開始位置,nNumberOfBytesToLockLow和 nNumberOfBytesToLockHigh參數組合起來指定了加鎖區域的大小。這兩個參數都指定了一個64位的值,在Win32中,只使用32位就夠了。
如果加鎖文件的進程終止,或者文件關閉時還未解鎖,操作系統會自動解除對文件的鎖定。但是,操作系統解鎖文件花費的時間取決于當前可用的系統資源。因此,進程終止時最好顯式地解鎖所有已鎖定的文件,以免造成這些文件無法訪問。
8.1.2? 獲取文件信息
1.獲取文件類型
Windows下的許多對象都稱之為文件,如果想知道一個文件句柄究竟對應什么對象,可以使用GetFileType函數,原型如下:
DWORD GetFileType(HANDLE hFile);
函數的返回值說明了文件類型,可以是下面的一個值:
l????????? FILE_TYPE_CHAR???????????? 指定文件是字符文件,通常是LPT設備或控制臺
l????????? FILE_TYPE_DISK?????????????? 指定文件是磁盤文件
l????????? FILE_TYPE_PIPE??????????????? 指定文件是套節字,一個命名的或未命名的管道
l????????? FILE_TYPE_UNKNOWN 不能識別指定文件,或者函數調用失敗
2.獲取文件大小
如果確定操作的對象是磁盤文件,還可以使用GetFileSize函數取得這個文件的長度。
DWORD GetFileSize(
? HANDLE hFile,??????????????? // 文件句柄
? LPDWORD lpFileSizeHigh?????? // 用于返回文件長度的高字。可以指定這個參數為NULL
);
函數執行成功將返回文件大小的低雙字,如果lpFileSizeHigh參數不是NULL,函數將文件大小的高雙字放入它指向的DWORD變量中。
如果函數執行失敗,并且 lpFileSizeHigh是NULL,返回值將是INVALID_FILE_SIZE;如果函數執行失敗,但lpFileSizeHigh不是 NULL,返回值是INVALID_FILE_SIZE,進一步調用GetLastError會返回不為NO_ERROR的值。
如果返回值是INVALID_FILE_SIZE,應用程序必須調用GetLastError來確定函數調用是否成功。原因是,當lpFileSizeHigh不為NULL或者文件大小為 0xffffffff時,函數雖然調用成功了,但依然會返回INVALID_FILE_SIZE。這種情況下,GetLastError會返回 NO_ERROR來響應成功。
3.獲取文件屬性
如果要查看文件或者目錄的屬性,可以使用GetFileAttributes函數,它會返回一系列FAT風格的屬性信息。
DWORD GetFileAttributes(LPCTSTR lpFileName);??????? // lpFileName指定了文件或者目錄的名稱
函數執行成功,返回值包含了指定文件或目錄的屬性信息,可以是下列取值的組合:
l????????? ?FILE_ATTRIBUTE_ARCHIVE ??????????????? 文件包含歸檔屬性
l????????? ?FILE_ATTRIBUTE_COMPRESSED ?????? 文件和目錄被壓縮
l????????? ?FILE_ATTRIBUTE_DIRECTORY ?????????? 這是一個目錄
l????????? ?FILE_ATTRIBUTE_HIDDEN ?????????????????????????? 文件包含隱含屬性
l????????? ?FILE_ATTRIBUTE_NORMAL ??????????????? 文件沒有其他屬性
l????????? ?FILE_ATTRIBUTE_READONLY ??????????? 文件包含只讀屬性
l????????? ?FILE_ATTRIBUTE_SYSTEM ????????????????? 文件包含系統屬性
l????????? ?FILE_ATTRIBUTE_TEMPORARY T ???? 文件是一個臨時文件
這些屬性對目錄也同樣適用。INVALID_FILE_ATTRIBUTES(0xFFFFFFFF)是函數執行失敗后的返回值。
下面是快速檢查某個文件或目錄是否存在的自定義函數,可以將它用在自己的工程中。
BOOL FileExists(LPCTSTR lpszFileName, BOOL bIsDirCheck)
{?????? // 試圖取得文件的屬性
?????????????????? DWORD dwAttributes = GetFileAttributes(lpszFileName);
??? ?if(dwAttributes == 0xFFFFFFFF)
??????? return FALSE;
???????? if ((dwAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
???????? {?????? if (bIsDirCheck)
??????????????????????????? return TRUE;
?????????????????? else
??????????????????????????? return FALSE;
???????? }
???????? else
???????? {?????? if (!bIsDirCheck)
??????????????????????????? return TRUE;
?????????????????? else
??????????????????????????? return FALSE;
???????? }
}
第2個參數bIsDirCheck指定要檢查的對象是目錄還是文件。
與GetFileAttributes相對應的函數是SetFileAttributes,這個函數用來設置文件屬性。
BOOL SetFileAttributes(
? LPCTSTR lpFileName,????????????? // 目標文件名稱
? DWORD dwFileAttributes???????????????? // 要設置的屬性值
);
8.1.3? 常用文件操作
1.拷貝文件
拷貝文件的函數是CopyFile和CopyFileEx,其作用都是復制一個存在的文件到一個新文件中。CopyFile函數的用法如下:
BOOL CopyFile(
? LPCTSTR lpExistingFileName, // 指定已存在的文件的名稱
? LPCTSTR lpNewFileName,??????????????? // 指定新文件的名稱
? BOOL bFailIfExists ?????????????????? // 如果指定的新文件存在是否按出錯處理
);
CopyFileEx函數的附加功能是允許指定一個回調函數,在拷貝過程中,函數每拷貝完一部分數據,就會調用回調函數。用戶在回調函數中可以指定是否停止拷貝,還可以顯示進度條來指示拷貝的進度。
2.刪除文件
刪除文件的函數是DeleteFile,僅有的參數是要刪除文件的名稱。
BOOL DeleteFile(LPCTSTR lpFileName);
如果應用程序試圖刪除不存在的文件,DeleteFile將執行失敗。如果目標文件是只讀的,函數也會執行失敗,出錯代碼為ERROR_ACCESS_DENIED。為了刪除只讀文件,先要去掉其只讀屬性。
DeleteFile函數可以標識一個文件為“關閉時刪除”。因此,直到最后一個到此文件的句柄關閉之后,文件才會被刪除。
下面的自定義函數RecursiveDelete示例了如何刪除指定目錄下的所有文件和子目錄。
void RecursiveDelete(CString szPath)
{?????? CFileFind ff;?????? // MFC將查找文件的API封裝到了CFileFind類。讀者可參考下面的框架使用這個類
???????? CString strPath = szPath;
???????? // 說明要查找此目錄下的所有文件
???????? if(strPath.Right(1) != """")
?????????????????? strPath += """";
???????? strPath += "*.*";
???????? BOOL bRet;
???????? if(ff.FindFile(strPath))
???????? {?????? do
?????????????????? {?????? bRet = ff.FindNextFile();
??????????????????????????? if(ff.IsDots())? // 目錄為“.”或者“..”?
???????????????????????????????????? continue;
??????????????????????????? strPath = ff.GetFilePath();
??????????????????????????? if(!ff.IsDirectory())
??????????????????????????? {?????? // 刪除此文件
???????????????????????????????????? ::SetFileAttributes(strPath, FILE_ATTRIBUTE_NORMAL);
???????????????????????????????????? ::DeleteFile(strPath);
??????????????????????????? }
??????????????????????????? else
??????????????????????????? {?????? // 遞歸調用
???????????????????????????????????? RecursiveDelete(strPath);
???????????????????????????????????? // 刪除此目錄(RemoveDirectory只能刪除空目錄)
???????????????????????????????????? ::SetFileAttributes(strPath, FILE_ATTRIBUTE_NORMAL);
???????????????????????????????????? ::RemoveDirectory(strPath);
??????????????????????????? }
?????????????????? }
?????????????????? while(bRet);
???????? }
}
用DeleteFile函數刪除的文件不會被放到回收站,它們將永遠丟失,所以請小心使用RecursiveDelete函數。
3.移動文件
移動文件的函數是MoveFile和MoveFileEx函數。它們的主要功能都是用來移動一個存在的文件或目錄。MoveFile函數用法如下:
BOOL MoveFile(
? LPCTSTR lpExistingFileName, // 存在的文件或目錄
? LPCTSTR lpNewFileName???????????????? // 新的文件或目錄
);
當需要指定如何移動文件時,請使用MoveFileEx函數。
BOOL MoveFileEx(LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName, DWORD dwFlags);
dwFlags參數可以是下列值的組合:
l????????? MOVEFILE_DELAY_UNTIL_REBOOT ???? 函數并不馬上執行,而是在操作系統下一此重新啟動時才移動文件。在AUTOCHK執行之后,系統立即移動文件,這是在創建任何分頁文件之前進行的。因此,這個值使函數能夠刪除上一次運行時使用的分頁文件。只有擁有管理員權限的用戶才可以使用這個值
l????????? MOVEFILE_REPLACE_EXISTING ??????????? 如果目標文件已存在的話,就將它替換掉
l????????? MOVEFILE_WRITE_THROUGH???????????????? 直到文件實際從磁盤移除之后函數才返回
如果指定了MOVEFILE_DELAY_UNTIL_REBOOT標記,lpNewFileName參數可以指定為NULL,這種情況下,當系統下一次啟動時,操作系統會刪除lpExistingFileName參數指定的文件。
8.1.4 ?檢查PE文件有效性的例子
PE文件格式是任何可執行模塊或者DLL的文件格式,PE文件以64字節的DOS文件頭(IMAGE_DOS_HEADER結構)開始,之后是一小段DOS程序,然后是248字節的NT文件頭(IMAGE_NT_HEADERS結構)。NT文件頭的偏移地址由IMAGE_DOS_HEADER結構的e_lfanew成員給出。
檢查文件是不是有效PE文件的一個方法是檢查IMAGE_DOS_HEADER和IMAGE_NT_HEADERS結構是否有效。IMAGE_DOS_HEADER結構定義如下:
typedef struct _IMAGE_DOS_HEADER {?????
??? WORD?? e_magic;???????????????????? ? // DOS可執行文件標記,為“MZ”。依此識別DOS頭是否有效
???????? ...????????????????????????????????????????????????????????????? // 其他成員,沒什么用途
??? LONG?? e_lfanew;??????????????????? ?? // IMAGE_NT_HEADERS結構的地址
? } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
IMAGE_NT_HEADERS結構定義如下:
typedef struct _IMAGE_NT_HEADERS {?
???????? ???????? ???????? DWORD Signature;? ?????????? // PE文件標識,為“PE"0"0”。依此識別NT文件頭是否有效
???????? ???????? ???????? IMAGE_FILE_HEADER FileHeader;?
???????? ???????? ???????? IMAGE_OPTIONAL_HEADER OptionalHeader;
???????? ???????? } IMAGE_NT_HEADERS,
為了編程方便,Windows為DOS文件標記和PE文件標記都定義了宏標識。
#define IMAGE_DOS_SIGNATURE???????????????? 0x5A4D????? // MZ
#define IMAGE_NT_SIGNATURE????????????????? 0x00004550? ?? // PE00
檢查文件是否為PE文件的步驟如下:
(1)檢驗文件頭部第一個字的值是否等于IMAGE_DOS_SIGNATURE,是則說明DOS MZ頭有效。
(2)一旦證明文件的DOS頭有效后,就可用e_lfanew來定位PE頭了。
(3)比較PE頭的第一個字是否等于IMAGE_NT_SIGNATURE。如果這個值也匹配,那么就認為該文件是一個有效的PE文件。
下面是驗證PE文件有效性的代碼,在配套光盤的08ValidPE工程下。
BOOL CMyApp::InitInstance()
{?????? // 彈出選擇文件對話框
???????? CFileDialog dlg(TRUE);
???????? if(dlg.DoModal() != IDOK)
?????????????????? return FALSE;
???????? // 打開檢查的文件
???????? HANDLE hFile = ::CreateFile(dlg.GetFileName(), GENERIC_READ,
?????????????????? FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
???????? if(hFile == INVALID_HANDLE_VALUE)
???????? ?????????????????? MessageBox(NULL, "無效文件!", "ValidPE", MB_OK);
???????? // 定義PE文件中的DOS頭和NT頭
???????? IMAGE_DOS_HEADER dosHeader;
???????? IMAGE_NT_HEADERS32 ntHeader;
???????? // 驗證過程
???????? BOOL bValid = FALSE;
???????? DWORD dwRead;
???????? // 讀取DOS頭
???????? ::ReadFile(hFile, &dosHeader, sizeof(dosHeader), &dwRead, NULL);
???????? if(dwRead == sizeof(dosHeader))
???????? {?????? if(dosHeader.e_magic == IMAGE_DOS_SIGNATURE) // 是不是有效的DOS頭?
?????????????????? {?????? // 定位NT頭
??????????????????????????? if(::SetFilePointer(hFile, dosHeader.e_lfanew, NULL, FILE_BEGIN) != -1)
??????????????????????????? {?????? // 讀取NT頭
???????????????????????????????????? ::ReadFile(hFile, &ntHeader, sizeof(ntHeader), &dwRead, NULL);
???????????????????????????????????? if(dwRead == sizeof(ntHeader))
???????????????????????????????????? {?????? if(ntHeader.Signature == IMAGE_NT_SIGNATURE)????? // 是不是有效的NT頭
??????????????????????????????????????????????????????? bValid = TRUE;
???????????????????????????????????? }
??????????????????????????? }
?????????????????? }
???????? }
???????? // 顯示結果
???????? if(bValid)
?????????????????? MessageBox(NULL, "是一個PE格式的文件!", "ValidPE", MB_OK);
???????? else
?????????????????? MessageBox(NULL, "不是一個PE格式的文件!", "ValidPE", MB_OK);
???????? ::CloseHandle(hFile);
???????? return FALSE;
}
上述代碼簡單明確,先利用Windows定義的宏 IMAGE_DOS_SIGNATURE判斷DOS頭,比較DOS頭的e_magic字段;再通過DOS頭的e_lfanew字段定位到NT頭;最后檢查 NT頭的Signature字段是不是IMAGE_NT_SIGNATURE(即“PE"0"0”)。
8.1.5? MFC的支持(CFile類)
CFile是一個相當簡單的封裝了一部分文件I/O 處理函數的類。它的成員函數用于打開和關閉文件、讀寫文件數據、刪除和重命名文件、取得文件信息。它的公開成員變量m_hFile保存了與CFile對象關聯的文件的文件句柄。一個受保護的CString類型的成員變量m_strFileName保存了文件的名稱。成員函數GetFilePath、 GetFileName和GetFileTitle能夠用來提取整個或者部分文件名。比如,如果完整的文件名是“C:"MyWork" File.txt”,GetFilePath返回整個字符串,GetFileName返回“File.txt”,GetFileTitle返回 “File”。
但是詳述這些函數就會忽略CFile類的特色,這就是用來寫數據到磁盤和從磁盤讀數據的函數。下面簡單介紹CFile類用法。
1.打開和創建文件
使用CFile類打開文件有兩種方法。
(1)構造一個未初始化的CFile對象,調用CFile::Open函數。下面的部分代碼使用這個技術以讀寫權限打開一個名稱為File.txt的文件。
CFile file;
if(file.Open(_T ("File.txt"), CFile::modeReadWrite))
{?????? // 打開文件成功}
CFile::Open函數的返回值是BOOL類型的變量。如果打開文件出錯,還想進一步了解出錯的原因,可以創建一個CFileException對象,傳遞它的地址到Open函數的第3個參數。
CFile file;
CFileException e;
if (file.Open(_T ("File.txt"), CFile::modeReadWrite, &e))
{?????? // 打開文件成功}
else
{?????? // 打開文件失敗,告訴用戶原因
???????? e.ReportError();
}
如果打開失敗,CFile::Open函數會使用描述失敗原因的信息初始化一個CFileException對象。ReportError成員函數基于這個信息顯示一個出錯對話框。可以通過檢查 CFileException類的公有成員m_cause找到導致這個錯誤的原因。
(2)使用CFile類的構造函數。可以將創建文件對象和打開文件合并成一步,如下面代碼所示。
CFile file(_T ("File.txt"), CFile::modeReadWrite);
如果文件不能被打開,CFile的構造函數會拋出一個CFileException異常。因此,使用CFile::CFile函數打開文件的代碼通常使用try和catch塊來捕獲錯誤。
try
{?????? CFile file(_T ("File.txt"), CFile::modeReadWrite);
}
catch(CFileException* e)
{?????? // 出錯了!
???????? e->ReportError();
???????? e->Delete();
}
刪除MFC拋出的異常是程序寫作者的責任,所以在程序中處理完異常之后要調用異常對象的Delete函數。
為了創建一個文件而不是打開一個存在的文件,要在CFile::Open或者CFile構造函數的第二個參數中包含上CFile::modeCreate標記,如下代碼所示。
CFile file(_T("File.txt"), CFile::modeReadWrite | CFile::modeCreate);
如果以這種方式創建的文件存在,它的長度會被截為0。為了在文件不存在時創建它,存在的時候僅打開而不截去,應再包含上CFile::modeNoTruncate標記,如下面代碼所示。
CFile file(_T("File.txt"), CFile::modeReadWrite | CFile::modeCreate | CFile::modeNoTruncate);
默認情況下,由CFile::Open或 CFile::CFile打開的文件使用的是獨占模式,即CreateFile API中的第3個參數dwShareMode被設為了0。如果需要,在打開文件時也可以指定一個共享模式,以明確同意其他訪問此文件的操作。這里是4個可以選擇的共享模式:
l????????? CFile::shareDenyNone ??????? 不獨占這個文件
l????????? CFile::shareDenyRead ???????? 拒絕其他代碼對這個文件進行讀操作
l????????? CFile::shareDenyWrite ??????? 拒絕其他代碼對這個文件進行寫操作
l????????? CFile::shareExclusive ?????????? 拒絕其他代碼對這個文件進行讀和寫操作(默認)
另外,還可以指定下面3個對象訪問類型中的一個:
l????????? CFile::modeReadWrite ??????? 請求讀寫訪問
l????????? CFile::modeRead ???????????????? 僅請求讀訪問
l????????? CFile::modeWrite ??????????????? 僅請求寫訪問
常用的做法是允許其他程序以只讀方式打開文件,但是拒絕它們寫入數據。
CFile file(_T("File.txt"), CFile::modeReadWrite | CFile::modeCreate | CFile::modeNoTruncate);
如果在上面的代碼執行之前,文件已經以可寫的方式打開了,這個調用將會失敗,CFile類會拋出CFileException異常,異常對象的m_cause成員等于CFileException::sharingViolation。
CFile類的成員函數Close會調用 CloseHandle API關閉應用程序打開的文件對象句柄。如果句柄沒有關閉,類的析構函數也會調用Close函數關閉它。顯式調用Close函數一般都是為了關閉當前打開的文件,以便使用同樣的CFile對象打開另一個文件。
2.讀寫文件
CFile類中從文件中讀取數據的成員函數是Read。例如,下面的代碼申請了一塊4KB大小的文件I/O緩沖區,每次從文件讀取4KB大小的數據。
BYTE buffer[4096];
CFile file (_T("File.txt"), CFile::modeRead);
DWORD dwBytesRemaining = file.GetLength();
while(dwBytesRemaining)
{?????? UINT nBytesRead = file.Read(buffer, sizeof(buffer));
???????? dwBytesRemaining -= nBytesRead;
}
文件中未讀取的字節數保存在 dwBytesRemaining變量里,此變量由CFile::GetLength返回的文件長度初始化。每次調用Read之后,從文件中讀取的字節數(nBytesRead)會從dwBytesRemaining變量里減去。直到dwBytesRemaining為0整個while循環才結束。
CFile類還提供了Write成員函數向文件寫入數據,Seek成員函數移動文件指針,它們都和相關API一一對應。可以通過跟蹤程序的執行來查看這些函數的實現代碼。
posted @ 2008-03-20 08:44 Da Vinci 閱讀(191) | 評論 (0) |?編輯
.NET下午茶之二: 值類型與引用類型的區別(轉載)
使用值類型還是引用類型?結構體(structs)還是類(class)?什么情況下兩者都可以使用?這并不是C++,你可以為任何類型的對象建立指針來引用他們。這也不是java,任何類型都自動聲明為引用類型。你需要想清楚你將要定義的類型會有怎樣的行為。首次能否選擇對是至關重要的,一旦你決定使用哪種類型,你就要承擔相應的后果,因為如果你后面修改了你之前定義的類型將會給你的代碼帶來潛在的不連貫性。使用struct或class關鍵字來創建你的類型是件簡單的事情,但是在后面更新或修改這些你所定義的類型將需要做更多的工作。
選擇正確的類型而不是你所偏好的類型并不是件簡單的事情。正確的選擇取決于你期望如何使用你所定義的新類型。值類型不支持多態。它更傾向于用在為你的應用程序來存儲數據上。引用類型支持多態而且應該用在定義你程序的行為上。考慮以上你所定義的新類型將會用于哪種用途,然后根據正確的用途來決定你將要創建的類型。結構體(struct)用于存儲數據。類(class)用于定義行為(beahavior)
值類型和引用類型之間的區別被加進.Net和C#中是因為在C++和Java中這些問題很普遍。在C++中,所有的參數和返回類型都是以值類型的方式傳遞的。通過值類型來傳遞具有不錯的效率,但它卻有一個問題:部分拷貝(partial copying)也有人稱作對象的切片(slicing the object)。你期望的是使用一個派生的對象,然而只有基類的部分被拷貝了。也就是你丟失了所有派生類對象中所保存的信息。甚至你對虛函數的調用也是基類的版本。
Java語言的對策是從語言中或多或少的消除值類型。所有用戶定義的類型都是引用類型。在Java語言中所有參數和返回類型都是以引用方式傳遞的。這種策略具有一致性的優點,但是它卻犧牲了效率,因為有些類型根本沒有必要是多態的。因此Java程序員需要在堆上分配空間和最終對每個變量進行垃圾回收。它們同樣需要為每個非引用類型而消耗額外的時間。所有的變量都是引用類型的。在C#中,你通過使用struct和class關鍵字來聲明你的類型是值類型還是引用類型。值類型應是當更小,更輕量的類型。引用類型組成了你的類的層次。這段例子分別使用值類型和引用類型來幫助你理解這兩者之間的區別。
在一開始,這個類型使用一個方法返回的值。
private MyData _myData; public MyData Foo() { return _myData; } ? // call it: MyData v = Foo(); TotalSum += v.Value;如果MyData是值類型,它給v返回了一份值的拷貝。更多的,v是在棧上的。然而,如果MyData是一個引用類型,你傳遞了引用,也就是你把內部數據輸出給外部變量。這樣會違反封裝的原則。
或者我們來考慮做個變化:
private MyData _myData; public MyData Foo() { return _myData.Clone( ) as MyData; } ? // call it: MyData v = Foo(); TotalSum += v.Value;現在,v就是_myData原版的一個拷貝。作為一個引用類型,它在堆上創建了兩個對象。這樣你就不會再有暴露內部數據的問題了。作為替代,你在堆上建立了額外的對象。如果v是一個局部變量,那么它很快就會變為垃圾碎片而且克隆(clone)會強制進行運行時檢查。總的來說,這是一種沒有效率可言的做法。
用于從公共方法或屬性來獲得數據的類型應當被定義為值類型。但是這并不等同于任何從公共方法返回的類型就是值類型。前面的代碼段假設MyData存儲了值,那么它的職責就是存儲這些值。
但是,考慮下面的代碼段。
private MyType _myType; public IMyInterface Foo() { return _myType as IMyInterface; } ? // call it: IMyInterface iMe = Foo(); iMe.DoWork( );_myType變量依然是通過Foo方法獲得的返回值。但是這次不同的是,這次沒有訪問返回值的數據,而是訪問了定義在接口中的方法。你訪問的并不是MyType的數據內容,而是它的行為。它的行為是通過ImyInterface這個接口所體現的,也就是說它可以表現成其他不同的行為。在這個例子中,MyType應當是一個引用類型而不是值類型。MyType的職責包含了它的行為,而不是它的數據成員。
上面的代碼段向你展示了它們之間的區別:值類型存儲數據而引用類型定義行為。現在我們更深入的觀察它們在內存中的存儲方式以及相關存儲模型所帶來性能上的問題。考慮這個類:
public class C { ? private MyType _a = new MyType( ); ? private MyType _b = new MyType( ); ? ? // Remaining implementation removed. } ? C var = new C();上面的代碼里有多少個對象被創建了?它們分別是多大?它視情況而定,如果MyType是一個值類型,你只需要進行一次分配。這次分配的大小就是 MyType大小的兩倍。然而,如果MyType是一個引用類型,你需要做3次分配:一次是為c對象來分配空間,大小是8字節(假設指針的大小是 32bit),剩下兩次則是為包含在c對象中的MyType對象。結果會有差別是因為值類型是以內聯(inline)方式存儲在對象內的,而引用類型不是。每個引用類型的變量只保留一個對象的引用,而實際的存儲則是需要額外的空間。
為了徹底的闡明以上觀點,現在來考慮一下這種情況的空間分配:
MyType [] var = new MyType[ 100 ];
如果MyType是值類型,一次空間分配將會產生MyType對象大小的一百倍空間。然而,如果MyType是引用類型,則只會發生一次空間分配。這個數組中的每一個元素都是null。在堆上分配大量的引用類型變量會使堆變得凌亂瑣碎而且降低運行速度。如果你只是為了存儲數據的話,那么值類型將會是正確的選擇。
對使用值類型還是引用類型所做的討論是很有意義的,將一個值類型轉換成引用類型將會進入更深層次的研究。考慮下面的情況:
public struct Employee { ? private string? _name; ? private int???? _ID; ? private decimal _salary; ? ? // Properties elided ? ? public void Pay( BankAccount b ) ? { ??? b.Balance += _salary; ? } }上面的簡單類型包含了一個可以讓你支付自己員工的方法。一段時間過去了,系統運行的相當不錯,但是此后你開始定義不同級別的員工:推銷員得到傭金,而經理得到獎金。你決定把Employee由值類型改為引用類型。
public class Employee { ? private string? _name; ? private int???? _ID; ? private decimal _salary; ? ? // Properties elided ? ? public virtual void Pay( BankAccount b ) ? { ??? b.Balance += _salary; ? } }這個改變破壞了你的用戶所使用的大部分代碼。返回值變成了返回引用。以前參數傳遞的是值而現在傳遞的是引用。下面這一小段的代碼行為被徹底的改變了:
Employee e1 = Employees.Find( "CEO" ); e1.Salary += Bonus; // Add one time bonus. e1.Pay( CEOBankAccount );每一次加獎金都將是永久的增加。以前通過值拷貝而如今被引用所替代。編譯器很樂于為你做這些事情。CEO肯定也很高興。但另一方面CFO會匯報這個bug。當面對這樣的實際情況時你就不能產生使用引用類型來代替值類型的想法了:因為它改變了行為。
之所以會產生這樣的問題時因為Employee類型不再遵循使用值類型的原則。你所定義的Employee類型不僅儲存了有關員工的數據信息,而且還定義了它的行為,在這個例子中它具有支付員工的行為。這些職責是屬于類(class)的范疇。類可以使得實現公共職能多態化更加簡單;而結構體就無法做到,它應當被限制為只用于數據存儲。
.Net的支持文檔里推薦根據類型的大小來考慮使用值類型或是引用類型。在實際中,值類型善于用在類型易于構造或用于攜帶數據的情況下。在某些方面值類型更加利于內存的管理:產生更少的堆碎片、更少的垃圾。最重要的是當值類型從方法或屬性返回時使用的是拷貝。這就避免了暴露內部數據結構的風險。但是你也將會為這些特點付出一定的代價。值類型在支持面相對象的技術方面有很大的限制,你無法通過值類型來簡直具有層次的對象,你只能把所有的值類型的對象看成封閉(sealed)的來考慮。你可以用值類型來實現接口,但是那需要采用拆裝箱技術,這將會帶來性能上的損失。考慮值類型只作為存儲數據的容器,而不具有面相對象的意義。
你還是會更加常用到引用類型。如果你能肯定的回答以下的這些問題,那么你就可以放心的使用值類型。比較之前的Employee的例子來考慮下面這些問題:
1. 你所要聲明的類型是否只承擔存儲數據的責任。
2. 你所要聲明的類型的數據訪問接口是否全部定義為了屬性(properties)。
3. 你是否確定它永遠都不會有派生類。
4. 你是否確定它永遠都不會被看作多態對待。
構建低等級的數據存儲類型時使用值類型。構建具有行為的程序時使用引用類型。你從你創建的類的對象中安全獲得數據拷貝。以棧作為基礎并使用內聯的值存儲方式更加有益與內存的利用,你可以利用面相對象的標準技術來建立你引用程序的邏輯。當你疑惑該使用哪種類型時,使用引用類型吧!參考:http://blog.csdn.net/knight94/archive/2006/07/01/861383.aspx
posted @ 2008-03-20 08:37 Da Vinci 閱讀(19) | 評論 (3) |?編輯
(From MS)Vista: 啟動配置數據編輯器(BCD)
什么是 BCD 存儲?
啟動配置數據 (BCD) 存儲包含啟動配置參數,并控制 Microsoft? Windows?Vista? 操作系統和代號為“Longhorn”的 Microsoft? Windows?Server? 操作系統的啟動方式。這些參數以前位于 Boot.ini 文件(在基于 BIOS 的操作系統中)或穩定 RAM (NVRAM) 條目中(在基于可擴展固件接口的操作系統中)。您可以使用 Bcdedit.exe 命令行工具在 BCD 存儲中添加、刪除、編輯和追加條目,以影響在預操作系統環境中運行的 Windows? 代碼。Bcdedit.exe 位于 Windows?Vista 分區的 "Windows"System32 目錄下。
??注意雖然本文檔主要介紹 Windows?Vista 相關內容,但此信息同樣適用于 Windows?Server“Longhorn”。
??注意要在命令提示符下獲得詳細的命令和選項信息,請鍵入 bdedit.exe /?命令。例如,鍵入 bcdedit.exe /?CREATESTORE。
為什么從 Boot.ini 改為 BCD?
創建 BCD 的目的是為描述啟動配置數據提供一種改進機制。隨著新固件模型的不斷涌現(例如,可擴展固件接口 (EFI)),人們需要一種可擴展和可互操作的接口來實現基礎固件的抽象化。這一全新設計為 Windows?Vista 中的各種新功能(例如,啟動修復工具和多用戶安裝快捷方式)提供了支持基礎。
BCD 文件位于注冊表中的哪一位置?
- 基于 BIOS 的操作系統。BCD 注冊表文件位于活動分區的 "Boot"Bcd 目錄下。
- 基于 EFI 的操作系統。BCD 注冊表文件位于 EFI 系統分區中。
是否任何用戶都可以修改 BCD?
否。您需要提供管理憑據才能修改 BCD。
可以通過哪些方式修改 BCD?
根據您要更改的內容,可以使用下列工具修改 BCD:
- 啟動和故障恢復。如果您的計算機上安裝了多個操作系統,則通過“啟動和故障恢復”。
- 系統配置實用程序 (Msconfig.exe)。Msconfig.exe 是一款更高級的工具,其功能包含下列選項:/debug、/safeboot、/bootlog、/noguiboot、/basevideo 和 /numproc。
- BCD WMI 提供程序。BCD Windows Management Instrumentation (WMI) 提供程序是一個管理接口,可用于編寫修改 BCD 的實用程序腳本。這是唯一可用于 BCD 的編程接口。有關詳細信息,請參閱 Microsoft 網站上的“啟動配置數據 (BCD)”(http://go.microsoft.com/fwlink/?LinkId=56792)。
- BCDEdit.exe。BCDEdit.exe 是 Windows?Vista 中取代 Bootcfg.exe 的命令行實用程序。有關詳細信息,請參閱使用 Bcdedit.exe 可執行哪些操作?。
為什么在 EFI 啟動管理器中看不到任何 Windows 條目?為什么有兩個啟動管理器?
所有 Windows 條目都存儲在 BCD 存儲中。在基于 EFI 的操作系統中,EFI 固件啟動管理器中只有一個名為“Windows 啟動管理器”的條目。此文件位于 "EFI"Microsoft"Boot"Bootmgfw.efi。如果使用 EFI 啟動管理器啟動 Windows 啟動管理器,則基于 EFI 的操作系統和基于 PC/AT 的操作系統將提供相同的外觀和用戶體驗。例如,高級啟動選項菜單均可供使用。EFI 啟動管理器的默認超時值為 2 秒,以便能夠在 Windows Server?2003 (Service Pack?1) 與 Windows?Vista 之間更輕松地進行啟動切換。
返回頁首多重引導環境
是否可以在已經包含某個操作系統的計算機上安裝 Windows Vista?
可以。您可以將 Windows?Vista 安裝在另一個分區上。最好在安裝舊版操作系統之后安裝 Windows?Vista。舊版操作系統將繼續使用 Boot.ini 來進行啟動配置。
在 Windows Vista 中,是否可以將過去使用 Boot.ini 的代碼替換為現在使用 BCD?
不可以。您需要將代碼改為針對舊版操作系統使用 Boot.ini,而針對 Windows?Vista 使用 BCD。
在多重引導環境中,在 Windows Vista 之前的操作系統上修改 BCD 是否會修改啟動配置?
不會。您需要修改 BCD 以更改 Windows?Vista 的啟動配置。但要更改舊版操作系統的啟動配置,則還需要修改 Boot.ini(如果是基于 BIOS 的操作系統)或 NVRAM(如果是基于 EFI 的操作系統)。
如果不引導到 Windows Vista,是否可以完全禁用 BCD?
不可以。因為首先會運行 Windows?Vista 的啟動管理器以確定要啟動哪個操作系統。因此,如果希望引導到舊版操作系統,則必須在 BCD 存儲中將默認順序設置為舊版操作系統。有關詳細信息,請參閱如何更改默認操作系統條目。
返回頁首BCDedit.exe
什么是 Bcdedit.exe?
您可以使用 Bcdedit.exe 在 BCD 存儲中添加、刪除、編輯和追加條目,以修改在預操作系統環境中運行的 Windows 代碼。Bcdedit.exe 位于 Windows?Vista 分區的 "Windows"System32 目錄下。
使用 Bcdedit.exe 可以執行哪些操作?
Bcdedit.exe 目前使您能夠執行下列操作:
- 為稍后安裝 Windows?Server“Longhorn”創建一個 BCD 存儲。
- 向現有 BCD 存儲中添加條目。
- 修改 BCD 存儲中的現有條目。
- 刪除 BCD 存儲中的條目。
- 將條目導出到 BCD 存儲。
- 導入來自 BCD 存儲的條目。
- 列出當前處于活動狀態的設置。
- 查詢特定類型的條目。
- (向所有條目)應用全局更改。
- 更改默認超時值。
當運行 bcdedit /enum 時,為什么會得到一個 Windows 啟動管理器條目、若干 Windows 啟動加載器條目和一個舊條目?
啟動環境分為兩個類別:Windows 啟動管理器和在啟動環境中運行的各種啟動應用程序。Windows 啟動管理器實質上是一個微型操作系統,可控制您的啟動體驗并使您能夠選擇要運行的啟動應用程序。啟動應用程序有很多種(例如 Windows 啟動加載器),并且每種啟動應用程序所執行的任務都有所不同。例如,Windows 啟動加載器應用程序將加載 Windows。
如果指定 /enum 時,您將獲得以下內容:
- 一個 Windows 啟動管理器條目(因為只有一個啟動管理器)。
- 適用于計算機上安裝的每個 Windows?Vista 操作系統的 Windows 啟動加載器應用程序。例如,如果您在不同分區上安裝了兩個不同版本的 Windows?Vista,就會看到兩個 Windows 啟動加載器條目。
- 一 個舊條目。此條目并不是啟動應用程序,但它使用 NTLDR 和 Boot.ini 引導至 Windows?Vista 之前的操作系統。您可以使用此條目引導至 Windows Server?2003、Windows XP 或其他早期操作系統(如果計算機上安裝了該操作系統)。
Bcdedit.exe 是否具有命令行幫助?
有。要在命令提示符下獲得詳細的命令和選項信息,請鍵入 bdedit.exe /? 和 bdedit.exe /?命令。例如,鍵入 bcdedit.exe /?CREATESTORE。
返回頁首執行熟悉任務的新方式
如何更改全局 zf 設置
在命令提示符下鍵入:
bcdedit /dbgsettingsDebugType[debugport:Port] [baudrate:Baud]
[channel:Channel] [targetname:TargetName]
| ? | |
| 選項 | 說明 |
| DebugType | 指定調試器的類型。DebugType 可以是 SERIAL、1394 或 USB 之一。其余選項 取決于所選的調試器類型。 |
| Port | 用于 SERIAL 調試,指定用作調試端口的串行端口。 |
| Baud | 用于 SERIAL 調試,指定調試使用的波特率。 |
| Channel | 用于 1394 調試,指定調試使用的 1394 通道。 |
| TargetName | 用于通用串行總線 (USB) 調試,指定調試使用的 USB 目標名稱。 |
示例
以下命令將指定條目設為默認
啟動管理器條目:
bcdedit /default {cbd971bf-b7b8-4885-951a-fa03044f5d71}
以下命令將舊 Windows 加載器 (Ntldr) 設為
默認條目:{466f5a88-0af2-4f76-9038-095b170dc21c} 是 Ntldr 的預定義 GUID。
bcdedit /default {466f5a88-0af2-4f76-9038-095b170dc21c}
如何更改下一次重新啟動的啟動順序
在命令提示符下鍵入:
bcdedit /bootsequence {ID} {ID} {ID} …
| ? | |
| 選項 | 說明 |
| ID | 指定構成下一次重新啟動的啟動順序的 GUID。 在此一次性啟動過后,它將還原為默認啟動順序。 |
示例
以下命令在啟動管理器顯示順序中設置三個操作系統條目:
Bcdedit.exe /displayorder {c84b751a-ff09-11d9-9e6e-0030482375e6} {c74b751a-ff09-11d9-9e6e-0030482375e4} {c34b751a-ff09-11d9-9e6e-0030482375e7}
以下命令在啟動管理器顯示順序中設置兩個操作系統條目和舊 Windows 加載器:
bcdedit /displayorder {802d5e32-0784-11da-bd33-000476eba25f}
以下命令在啟動菜單顯示順序的最后添加由 GUID 表示的條目:
bcdedit.exe /displayorder {c84b751a-ff09-11d9-9e6e-0030482375e6}-addlast
如何刪除啟動項目
在命令提示符下鍵入:
bcdedit /delete ID [/f]
| ? | |
| 選項 | 說明 |
| ID | 指定要刪除的啟動項目的 GUID。如果不指定 ID,則刪除當前啟動項目 ID。 如果指定一個已知 GUID,則必須通過指定 /f 強制進行刪除。例如: bcdedit /delete {default} /f |
示例
以下命令將列出所有操作系統加載器啟動項目:
bcdedit /enum osloader
以下命令將列出所有啟動管理器條目:
bcdedit /enum bootmgr
在運行 Windows Vista 的計算機上安裝舊版 Windows 時如何修改 BCD
要在運行 Windows?Vista 的計算機上安裝舊版 Windows 操作系統,請使用以下過程。
在運行 Windows Vista 的計算機上安裝舊版 Windows
Bcdedit /create {legacy} /d “Description”
Bcdedit /set {legacy} device boot
Bcdedit /set {legacy} path "ntldr
Bcdedit /displayorder {legacy} /addlast
如何創建一個可從硬盤啟動 WIM 映像的條目
要創建一個可啟動 Windows 映像格式 (WIM) 映像的條目,您需要創建一個 OSloader 類型的條目,并帶有指向啟動分區的 RAMDISK 選項。為此,請使用以下過程。在此過程中,arcpath multi(0)disk(0)rdisk(0)partition(1) 是指計算機上的 C: 驅動器,Boot.wim 是一個常規 Boot.wim,其中 Winload.exe 位于該 WIM 映像的 System32 文件夾中。
創建一個可從硬盤啟動 WIM 映像的條目
bcdedit /create {ramdiskoptions} /d "Ramdisk options"
bcdedit /set {ramdiskoptions} ramdisksdidevice partition=Drive
bcdedit /set {ramdiskoptions} ramdisksdipath "boot"boot.sdi
bcdedit /create /d "Boot from WIM" /application OSLOADER
bcdedit /set {GUID} device ramdisk=[c:]"sources"boot.wim,{ramdiskoptions}
bcdedit /set {GUID} path "windows"system32"winload.exe
bcdedit /set {GUID} osdevice ramdisk=[c:]"sources"boot.wim,{ramdiskoptions}
bcdedit /set {GUID} systemroot "windows
bcdedit /set {GUID} winpe yes
bcdedit /set {GUID} detecthal yes
bcdedit /displayorder {GUID} /addlast
如何更改特定條目的調試器設置
要覆蓋特定調試器設置的全局條目,請鍵入以下命令之一。
??注意此命令不會為特定啟動項目啟用或禁用調試器。
- 要設置串行調試,請鍵入:
bcdedit /set {GUID} debugtype:serial
bcdedit /set {GUID} baudrate:Baudrate
bcdedit /set {GUID} debugport:Port
- 要設置 USB 調試,請鍵入:
bcdedit /set {GUID} debugtype:usbbcdedit /set {GUID} targetname:debugging
- 要設置 1394 調試,請鍵入:
bcdedit /set {GUID} debugtype:1394bcdedit /set {GUID} targetname:32
示例
以下命令將 c74b751a-ff09-11d9-9e6e-0030482375e4 的調試器設置設為以 115,200 的波特率在 com1 上串行調試:
Bcdedit /set {c74b751a-ff09-11d9-9e6e-0030482375e4} debugtype:serial
Bcdedit /set {c74b751a-ff09-11d9-9e6e-0030482375e4} baudrate:115200
Bcdedit /set {c74b751a-ff09-11d9-9e6e-0030482375e4} debugport:1
posted @ 2008-03-20 08:33 Da Vinci 閱讀(115) | 評論 (0) |?編輯
Windows: "net use" command introduction
1)建立空連接:net use ""IP"ipc$ "" /user:"" (一定要注意:這一行命令中包含了3個空格)
2)建立非空連接:
net use ""IP"ipc$ "密碼" /user:"用戶名" (同樣有3個空格)
3)映射默認共享:
net use z: ""IP"c$ "密碼" /user:"用戶名" (即可將對方的c盤映射為自己的z盤,其他盤類推)
如果已經和目標建立了ipc$,則可以直接用IP+盤符+$訪問,具體命令 net use z: ""IP"c$
4)刪除一個ipc$連接
net use ""IP"ipc$ /del
5)刪除共享映射
net use c: /del 刪除映射的c盤,其他盤類推
net use * /del 刪除全部,會有提示要求按y確認
3 查看遠程主機的共享資源(但看不到默認共享)
net view ""IP
4 查看本地主機的共享資源(可以看到本地的默認共享)
net share
5 得到遠程主機的用戶名列表
nbtstat -A IP
6 得到本地主機的用戶列表
net user
7 查看遠程主機的當前時間
net time ""IP
8 顯示本地主機當前服務
net start
9 啟動/關閉本地服務
net start 服務名 /y
net stop 服務名 /y
10 映射遠程共享:
net use z: ""IP"baby
此命令將共享名為baby的共享資源映射到z盤
11 刪除共享映射
net use c: /del 刪除映射的c盤,其他盤類推
net use * /del /y刪除全部
12 向遠程主機復制文件
copy "路徑"srv.exe ""IP"共享目錄名,如:
copy ccbirds.exe ""*.*.*.*"c 即將當前目錄下的文件復制到對方c盤內
13 遠程添加計劃任務
at ""ip 時間 程序名,如:
at ""127.0.0.0 11:00 love.exe
注意:時間盡量使用24小時制;在系統默認搜索路徑(比如system32/)下不用加路徑,否則必須加全路徑
14 開啟遠程主機的telnet
這里要用到一個小程序:opentelnet.exe,各大下載站點都有,而且還需要滿足四個要求:
1)目標開啟了ipc$共享
2)你要擁有管理員密碼和帳號
3)目標開啟RemoteRegistry服務,用戶就該ntlm認證
4)對WIN2K/XP有效,NT未經測試
命令格式:OpenTelnet.exe ""server account psw NTLM認證方式 port
試例如下:c:">OpenTelnet.exe ""*.*.*.* administrator "" 1 90
15 激活用戶/加入管理員組
1 net uesr account /active:yes
2 net localgroup administrators account /add
16 關閉遠程主機的telnet
同樣需要一個小程序:ResumeTelnet.exe
命令格式:ResumeTelnet.exe ""server account psw
試例如下:c:">ResumeTelnet.exe ""*.*.*.* administrator ""
17 刪除一個已建立的ipc$連接
net use ""IP"ipc$ /del
posted @ 2008-03-20 08:27 Da Vinci 閱讀(27) | 評論 (0) |?編輯
2008年3月19日
.NET下午茶之一: CLR/CTS/CLS
1. CTS Common Type System:公共類型系統。《Practical .NET2.0 and C#2.0》的解釋是:CTS是.NET平臺的一組類型。這組類型獨立于編寫它們的源代碼語言。CTS是一種規范,它描述了每一個能被CLR識別的類型的特征。CTS是定義公共語言運行庫在聲明、使用和管理類型時所遵循的規則的模型。CTS有值類型、引用類型和指針類型組成。2. CLS Common Language Specification:公共語言規范。一組可以以編程方式驗證的規則,這組規范控制用不同編程語言編寫的類型的交互操作。.NET程序員利用CLS 來保證可從多種編程語言調用他們的 API。CLS是CLR/CTS的子集,即某些.NET編程語言可以存在滿足CLS定義的部分,也可以包含不滿足CLS定義的部分。例如C#語言的有符號整型包含在CLS中,但無符號整型卻不是CLS的部分。
3. CLR Common Language Runtime:公共語言運行時。CLR是整個.NET平臺架構的中心元素,是管理所有.NET程序的軟件層。實際上CLR是運行時駐留在內存中的一段代碼,負責IL代碼編譯為機器語言、異常管理、垃圾回收、加載程序集、解析類型等操作。托管就是由它來托管。它類似于Java中的JVM(虛擬機)。
posted @ 2008-03-19 21:39 Da Vinci 閱讀(13) | 評論 (0) |?編輯
(轉載)C++字符串完全指引之一 —— Win32 字符編碼
C++字符串完全指引之一 —— Win32 字符編碼
原著:Michael Du
原文出處:CodeProject:The Complete Guide to C++ Strings, Part I
引言
毫無疑問,我們都看到過像 TCHAR, std::string, BSTR 等各種各樣的字符串類型,還有那些以 _tcs 開頭的奇怪的宏。你也許正在盯著顯示器發愁。本指引將總結引進各種字符類型的目的,展示一些簡單的用法,并告訴您在必要時,如何實現各種字符串類型之間的轉換。
在第一部分,我們將介紹3種字符編碼類型。了解各種編碼模式的工作方式是很重要的事情。即使你已經知道一個字符串是一個字符數組,你也應該閱讀本部分。一旦你了解了這些,你將對各種字符串類型之間的關系有一個清楚地了解。
在第二部分,我們將單獨講述string類,怎樣使用它及實現他們相互之間的轉換。
字符基礎 -- ASCII, DBCS, Unicode
所有的 string 類都是以C-style字符串為基礎的。C-style 字符串是字符數組。所以我們先介紹字符類型。這里有3種編碼模式對應3種字符類型。第一種編碼類型是單子節字符集(single-byte character set or SBCS)。在這種編碼模式下,所有的字符都只用一個字節表示。ASCII是SBCS。一個字節表示的0用來標志SBCS字符串的結束。
第二種編碼模式是多字節字符集(multi-byte character set or MBCS)。一個MBCS編碼包含一些一個字節長的字符,而另一些字符大于一個字節的長度。用在Windows里的MBCS包含兩種字符類型,單字節字符(single-byte characters)和雙字節字符(double-byte characters)。由于Windows里使用的多字節字符絕大部分是兩個字節長,所以MBCS常被用DBCS代替。
在DBCS編碼模式中,一些特定的值被保留用來表明他們是雙字節字符的一部分。例如,在Shift-JIS編碼中(一個常用的日文編碼模式),0x81-0x9f之間和 0xe0-oxfc之間的值表示"這是一個雙字節字符,下一個子節是這個字符的一部分。"這樣的值被稱作"leading bytes",他們都大于0x7f。跟隨在一個leading byte子節后面的字節被稱作"trail byte"。在DBCS中,trail byte可以是任意非0值。像SBCS一樣,DBCS字符串的結束標志也是一個單字節表示的0。
第三種編碼模式是Unicode。Unicode是一種所有的字符都使用兩個字節編碼的編碼模式。Unicode字符有時也被稱作寬字符,因為它比單子節字符寬(使用了更多的存儲空間)。注意,Unicode不能被看作MBCS。MBCS的獨特之處在于它的字符使用不同長度的字節編碼。Unicode 字符串使用兩個字節表示的0作為它的結束標志。
單字節字符包含拉丁文字母表,accented characters及ASCII標準和DOS操作系統定義的圖形字符。雙字節字符被用來表示東亞及中東的語言。Unicode被用在COM及Windows NT操作系統內部。
你一定已經很熟悉單字節字符。當你使用char時,你處理的是單字節字符。雙字節字符也用char類型來進行操作(這是我們將會看到的關于雙子節字符的很多奇怪的地方之一)。Unicode字符用wchar_t來表示。Unicode字符和字符串常量用前綴L來表示。例如:
wchar_t* wsz = L"Hello"; // 12 bytes, 6 wide characters
字符在內存中是怎樣存儲的
單字節字符串:每個字符占一個字節按順序依次存儲,最后以單字節表示的0結束。例如。"Bob"的存貯形式如下:
| 42 | 6F | 62 | 00 |
| B | o | b | BOS |
Unicode的存儲形式,L"Bob"
| 42 00 | 6F 00 | 62 00 | 00 00 |
| B | o | b | BOS |
使用兩個字節表示的0來做結束標志。
一眼看上去,DBCS 字符串很像 SBCS 字符串,但是我們一會兒將看到 DBCS 字符串的微妙之處,它使得使用字符串操作函數和永字符指針遍歷一個字符串時會產生預料之外的結果。字符串" " ("nihongo")在內存中的存儲形式如下(LB和TB分別用來表示 leading byte 和 trail byte)
| 93 FA | 96 7B | 8C EA | 00 |
| LB TB | LB TB | LB TB | EOS |
| EOS |
值得注意的是,"ni"的值不能被解釋成WORD型值0xfa93,而應該看作兩個值93和fa以這種順序被作為"ni"的編碼。
使用字符串處理函數
我們都已經見過C語言中的字符串函數,strcpy(), sprintf(), atoll()等。這些字符串只應該用來處理單字節字符字符串。標準庫也提供了僅適用于Unicode類型字符串的函數,比如wcscpy(), swprintf(), wtol()等。
微軟還在它的CRT(C runtime library)中增加了操作DBCS字符串的版本。Str***()函數都有對應名字的DBCS版本_mbs***()。如果你料到可能會遇到DBCS 字符串(如果你的軟件會被安裝在使用DBCS編碼的國家,如中國,日本等,你就可能會),你應該使用_mbs***()函數,因為他們也可以處理SBCS 字符串。(一個DBCS字符串也可能含有單字節字符,這就是為什么_mbs***()函數也能處理SBCS字符串的原因)
讓我們來看一個典型的字符串來闡明為什么需要不同版本的字符串處理函數。我們還是使用前面的Unicode字符串 L"Bob":
| 42 00 | 6F 00 | 62 00 | 00 00 |
| B | o | b | BOS |
因為x86CPU是little-endian,值0x0042在內存中的存儲形式是42 00。你能看出如果這個字符串被傳給strlen()函數會出現什么問題嗎?它將先看到第一個字節42,然后是00,而00是字符串結束的標志,于是 strlen()將會返回1。如果把"Bob"傳給wcslen(),將會得出更壞的結果。wcslen()將會先看到0x6f42,然后是 0x0062,然后一直讀到你的緩沖區的末尾,直到發現00 00結束標志或者引起了GPF。
到目前為止,我們已經討論了str***()和wcs***()的用法及它們之間的區別。Str***()和_mbs**()之間的有區別區別呢?明白他們之間的區別,對于采用正確的方法來遍歷DBCS字符串是很重要的。下面,我們將先介紹字符串的遍歷,然后回到str***()與_mbs***() 之間的區別這個問題上來。
正確的遍歷和索引字符串
因為我們中大多數人都是用著SBCS字符串成長的,所以我們在遍歷字符串時,常常使用指針的++-和-操作。我們也使用數組下標的表示形式來操作字符串中的字符。這兩種方式是用于SBCS和Unicode字符串,因為它們中的字符有著相同的寬度,編譯器能正確的返回我們需要的字符。
然而,當碰到DBCS字符串時,我們必須拋棄這些習慣。這里有使用指針遍歷DBCS字符串時的兩條規則。違背了這兩條規則,你的程序就會存在DBCS有關的bugs。
我們先來闡述規則2,因為找到一個違背它的真實的實例代碼是很容易的。假設你有一個程序在你自己的目錄里保存了一個設置文件,你把安裝目錄保存在注冊表中。在運行時,你從注冊表中讀取安裝目錄,然后合成配置文件名,接著讀取該文件。假設,你的安裝目錄是C:"Program Files"MyCoolApp,那么你合成的文件名應該是C:"Program Files"MyCoolApp"config.bin。當你進行測試時,你發現程序運行正常。
現在,想象你合成文件名的代碼可能是這樣的:
{
char szConfigFilename[MAX_PATH];
// Read install dir from registry... we''ll assume it succeeds.
// Add on a backslash if it wasn''t present in the registry value.
// First, get a pointer to the terminating zero.
char* pLastChar = strchr ( szConfigFilename, ''"0'' );
// Now move it back one character.
pLastChar--;
if ( *pLastChar != ''""'' )
strcat ( szConfigFilename, """" );
// Add on the name of the config file.
strcat ( szConfigFilename, "config.bin" );
// If the caller''s buffer is big enough, return the filename.
if ( strlen ( szConfigFilename ) >= nBuffSize )
return false;
else
{
strcpy ( pszName, szConfigFilename );
return true;
}
} 這是一段很健壯的代碼,然而在遇到 DBCS 字符時它將會出錯。讓我們來看看為什么。假設一個日本用戶使用了你的程序,把它安裝在 C:"。下面是這個名字在內存中的存儲形式:
| 43 | 3A | 5C | 83 88 | 83 45 | 83 52 | 83 5C | 00 |
| LB TB | LB TB | LB TB | LB TB | ||||
| C | : | " | EOS |
當使用 GetConfigFileName() 檢查尾部的''""''時,它尋找安裝目錄名中最后的非0字節,看它是等于''""''的,所以沒有重新增加一個''""''。結果是代碼返回了錯誤的文件名。
哪里出錯了呢?看看上面兩個被用藍色高量顯示的字節。斜杠''""''的值是0x5c。'' ''的值是83 5c。上面的代碼錯誤的讀取了一個 trail byte,把它當作了一個字符。
正確的后向遍歷方法是使用能夠識別DBCS字符的函數,使指針移動正確的字節數。下面是正確的代碼。(指針移動的地方用紅色標明)
{
char szConfigFilename[MAX_PATH];
// Read install dir from registry... we''ll assume it succeeds.
// Add on a backslash if it wasn''t present in the registry value.
// First, get a pointer to the terminating zero.
char* pLastChar = _mbschr ( szConfigFilename, ''"0'' );
// Now move it back one double-byte character.
pLastChar = CharPrev ( szConfigFilename, pLastChar );
if ( *pLastChar != ''""'' )
_mbscat ( szConfigFilename, """" );
// Add on the name of the config file.
_mbscat ( szConfigFilename, "config.bin" );
// If the caller''s buffer is big enough, return the filename.
if ( _mbslen ( szInstallDir ) >= nBuffSize )
return false;
else
{
_mbscpy ( pszName, szConfigFilename );
return true;
}
}
上面的函數使用CharPrev() API使pLastChar向后移動一個字符,這個字符可能是兩個字節長。在這個版本里,if條件正常工作,因為lead byte永遠不會等于0x5c。
讓我們來想象一個違背規則1的場合。例如,你可能要檢測一個用戶輸入的文件名是否多次出現了'':''。如果,你使用++操作來遍歷字符串,而不是使用CharNext(),你可能會發出不正確的錯誤警告如果恰巧有一個trail byte它的值的等于'':''的值。
與規則2相關的關于字符串索引的規則: 2a. 永遠不要使用減法去得到一個字符串的索引。
違背這條規則的代碼和違背規則2的代碼很相似。例如,
char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];這和向后移動一個指針是同樣的效果。
回到關于str***()和_mbs***()的區別
現在,我們應該很清楚為什么_mbs***()函數是必需的。Str***()函數根本不考慮DBCS字符,而_mbs***()考慮。如果,你調用strrchr("C:"" ", ''""''),返回結果可能是錯誤的,然而_mbsrchr()將會認出最后的雙字節字符,返回一個指向真的''""''的指針。
關于字符串函數的最后一點:str***()和_mbs***()函數認為字符串的長度都是以char來計算的。所以,如果一個字符串包含3個雙字節字符,_mbslen()將會返回6。Unicode函數返回的長度是按wchar_t來計算的。例如,wcslen(L"Bob")返回3。
Win32 API中的MBCS和Unicode
兩組 APIs:
盡管你也許從來沒有注意過,Win32中的每個與字符串相關的API和message都有兩個版本。一個版本接受MBCS字符串,另一個接受 Unicode字符串。例如,根本沒有SetWindowText()這個API,相反,有SetWindowTextA()和 SetWindowTextW()。后綴A表明這是MBCS函數,后綴W表示這是Unicode版本的函數。
當你 build 一個 Windows 程序,你可以選擇是用 MBCS 或者 Unicode APIs。如果,你曾經用過VC向導并且沒有改過預處理的設置,那表明你用的是MBCS版本。那么,既然沒有 SetWindowText() API,我們為什么可以使用它呢?winuser.h頭文件包含了一些宏,例如:
BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString );
#ifdef UNICODE
#define SetWindowText SetWindowTextW
#else
#define SetWindowText SetWindowTextA
#endif 當使用MBCS APIs來build程序時,UNICODE沒有被定義,所以預處理器看到: #define SetWindowText SetWindowTextA
這個宏定義把所有對SetWindowText的調用都轉換成真正的API函數SetWindowTextA。(當然,你可以直接調用SetWindowTextA() 或者 SetWindowTextW(),雖然你不必那么做。)
所以,如果你想把默認使用的API函數變成Unicode版的,你可以在預處理器設置中,把_MBCS從預定義的宏列表中刪除,然后添加UNICODE和_UNICODE。(你需要兩個都定義,因為不同的頭文件可能使用不同的宏。) 然而,如果你用char來定義你的字符串,你將會陷入一個尷尬的境地。考慮下面的代碼:
char szNewText[] = "we love Bob!";
SetWindowText ( hwnd, szNewText );
在預處理器把SetWindowText用SetWindowTextW來替換后,代碼變成:
HWND hwnd = GetSomeWindowHandle();char szNewText[] = "we love Bob!";
SetWindowTextW ( hwnd, szNewText );
看到問題了嗎?我們把單字節字符串傳給了一個以Unicode字符串做參數的函數。解決這個問題的第一個方案是使用 #ifdef 來包含字符串變量的定義:
HWND hwnd = GetSomeWindowHandle();#ifdef UNICODE
wchar_t szNewText[] = L"we love Bob!";
#else
char szNewText[] = "we love Bob!";
#endif
SetWindowText ( hwnd, szNewText );
你可能已經感受到了這樣做將會使你多么的頭疼。完美的解決方案是使用TCHAR.
使用TCHAR
TCHAR是一種字符串類型,它讓你在以MBCS和UNNICODE來build程序時可以使用同樣的代碼,不需要使用繁瑣的宏定義來包含你的代碼。TCHAR的定義如下:
typedef wchar_t TCHAR;
#else
typedef char TCHAR;
#endif
所以用MBCS來build時,TCHAR是char,使用UNICODE時,TCHAR是wchar_t。還有一個宏來處理定義Unicode字符串常量時所需的L前綴。
#ifdef UNICODE#define _T(x) L##x
#else
#define _T(x) x
#endif
##是一個預處理操作符,它可以把兩個參數連在一起。如果你的代碼中需要字符串常量,在它前面加上_T宏。如果你使用Unicode來build,它會在字符串常量前加上L前綴。
TCHAR szNewText[] = _T("we love Bob!");像是用宏來隱藏SetWindowTextA/W的細節一樣,還有很多可以供你使用的宏來實現str***()和_mbs***()等字符串函數。例如,你可以使用_tcsrchr宏來替換strrchr()、_mbsrchr()和wcsrchr()。_tcsrchr根據你預定義的宏是_MBCS 還是UNICODE來擴展成正確的函數,就像SetWindowText所作的一樣。
不僅str***()函數有TCHAR宏。其他的函數如, _stprintf(代替sprinft()和swprintf()),_tfopen(代替fopen()和_wfopen())。 MSDN中"Generic-Text Routine Mappings."標題下有完整的宏列表。
字符串和TCHAR typedefs
由于Win32 API文檔的函數列表使用函數的常用名字(例如,"SetWindowText"),所有的字符串都是用TCHAR來定義的。(除了XP中引入的只適用于 Unicode的API)。下面列出一些常用的typedefs,你可以在msdn中看到他們。
| type | Meaning in MBCS builds | Meaning in Unicode builds |
| WCHAR | wchar_t | wchar_t |
| LPSTR | zero-terminated string of char (char*) | zero-terminated string of char (char*) |
| LPCSTR | constant zero-terminated string of char (const char*) | constant zero-terminated string of char (const char*) |
| LPWSTR | zero-terminated Unicode string (wchar_t*) | zero-terminated Unicode string (wchar_t*) |
| LPCWSTR | constant zero-terminated Unicode string (const wchar_t*) | constant zero-terminated Unicode string (const wchar_t*) |
| TCHAR | char | wchar_t |
| LPTSTR | zero-terminated string of TCHAR (TCHAR*) | zero-terminated string of TCHAR (TCHAR*) |
| LPCTSTR | constant zero-terminated string of TCHAR (const TCHAR*) | constant zero-terminated string of TCHAR (const TCHAR*) |
何時使用 TCHAR 和 Unicode
到現在,你可能會問,我們為什么要使用Unicode。我已經用了很多年的char。下列3種情況下,使用Unicode將會使你受益:
只要你使用Unicode API,NT系統允許使用非常長的文件名(突破了MAX_PATH的限制,MAX_PATH=260)。使用Unicode API的另一個優點是你的程序會自動處理用戶輸入的各種語言。所以一個用戶可以輸入英文,中文或者日文,而你不需要額外編寫代碼去處理它們。
最后,隨著windows 9x產品的淡出,微軟似乎正在拋棄MBCS APIs。例如,包含兩個字符串參數的SetWindowTheme() API只有Unicode版本的。使用Unicode來build你的程序將會簡化字符串的處理,你不必在MBCS和Unicdoe之間相互轉換。
即使你現在不使用Unicode來build你的程序,你也應該使用TCHAR及其相關的宏。這樣做不僅可以的代碼可以很好地處理DBCS,而且如果將來你想用Unicode來build你的程序,你只需要改變一下預處理器中的設置就可以實現了。 ? http://www.cnblogs.com/Sonic2007/
總結
以上是生活随笔為你收集整理的Windbg内核调试(大杂烩)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何用邮件客户端收发电子邮件,如何配置邮
- 下一篇: 照片估计明星身高matlab,娱乐圈部分