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日


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进行内核调试, 熟练的运用命令行是必不可少的技能. 但是面对众多繁琐的命令, 实在是不可能全部的了解和掌握. 而了解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进行内核调试,需要一些基本的技巧和设置,在这个系列文章中,我将使用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)编辑


操作文件基本上是每个应用程序都必须做的事情。除了必要的配置信息外,用户的工作最终都要以文件的形式保存到磁盘上。保存和获取这些信息可以使用独立的磁盘文件,也可以使用系统自带的数据库——注册表。

本章首先介绍底层操作文件的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)编辑

使用值类型还是引用类型?结构体(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)编辑

什么是 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
  1. 安装旧版 Windows。
  2. 登录到旧版操作系统,并通过运行以下命令还原最新的启动管理器。Fixntfs.exe 将位于活动分区 fixntfs /lh 的 "boot 目录下。
  3. 通过指定以下内容,为旧版操作系统创建一个 BCD 条目。Bcdedit.exe 位于 Windows Vista 分区的 "Windows"System32 目录下。Description 是对旧版操作系统中新条目的描述。

    Bcdedit /create {legacy} /d “Description

    Bcdedit /set {legacy} device boot

    Bcdedit /set {legacy} path "ntldr

    Bcdedit /displayorder {legacy} /addlast

  4. 重新启动计算机以使更改生效。

如何创建一个可从硬盘启动 WIM 映像的条目

要创建一个可启动 Windows 映像格式 (WIM) 映像的条目,您需要创建一个 OSloader 类型的条目,并带有指向启动分区的 RAMDISK 选项。为此,请使用以下过程。在此过程中,arcpath multi(0)disk(0)rdisk(0)partition(1) 是指计算机上的 C: 驱动器,Boot.wim 是一个常规 Boot.wim,其中 Winload.exe 位于该 WIM 映像的 System32 文件夹中。

创建一个可从硬盘启动 WIM 映像的条目
  1. 通过指定以下内容,在您的 BCD 存储中创建 {ramdisktoptions} 对象。Drive 应是包含该映像的驱动器。

    bcdedit /create {ramdiskoptions} /d "Ramdisk options"

    bcdedit /set {ramdiskoptions} ramdisksdidevice partition=Drive

    bcdedit /set {ramdiskoptions} ramdisksdipath "boot"boot.sdi

  2. 通过指定以下内容,创建新的启动应用程序条目:

    bcdedit /create /d "Boot from WIM" /application OSLOADER

  3. 这将为新创建的条目返回一个标识符 (GUID)。此过程的其他部分将使用 {GUID} 指代该新条目。接下来指定以下内容:

    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

  4. 如果要引导到 Windows 预安装环境 (Windows PE),则还需要指定:

    bcdedit /set {GUID} winpe yes

    bcdedit /set {GUID} detecthal yes

  5. 继续指定以下内容,将新条目添加到显示顺序中:

    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)编辑

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日

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 字符编码

原著: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 wch = L''1''; // 2 bytes, 0x0031
wchar_t* wsz = L"Hello"; // 12 bytes, 6 wide characters

字符在内存中是怎样存储的

单字节字符串:每个字符占一个字节按顺序依次存储,最后以单字节表示的0结束。例如。"Bob"的存贮形式如下:

426F6200
BobBOS

Unicode的存储形式,L"Bob"

42 00 6F 0062 0000 00
BobBOS

使用两个字节表示的0来做结束标志。

一眼看上去,DBCS 字符串很像 SBCS 字符串,但是我们一会儿将看到 DBCS 字符串的微妙之处,它使得使用字符串操作函数和永字符指针遍历一个字符串时会产生预料之外的结果。字符串" " ("nihongo")在内存中的存储形式如下(LB和TB分别用来表示 leading byte 和 trail byte)

93 FA96 7B8C EA00
LB TBLB TBLB TBEOS
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 0062 0000 00
BobBOS

因为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。

  • 1.在前向遍历时,不要使用++操作,除非你每次都检查lead byte;
  • 2.永远不要使用-操作进行后向遍历。
  • 我们先来阐述规则2,因为找到一个违背它的真实的实例代码是很容易的。假设你有一个程序在你自己的目录里保存了一个设置文件,你把安装目录保存在注册表中。在运行时,你从注册表中读取安装目录,然后合成配置文件名,接着读取该文件。假设,你的安装目录是C:"Program Files"MyCoolApp,那么你合成的文件名应该是C:"Program Files"MyCoolApp"config.bin。当你进行测试时,你发现程序运行正常。
    现在,想象你合成文件名的代码可能是这样的:

    bool GetConfigFileName ( char* pszName, size_t nBuffSize )
    {
    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:" 。下面是这个名字在内存中的存储形式:
    433A5C83 8883 4583 5283 5C00
    LB TB LB TB LB TB LB TB
    C:"EOS

      当使用 GetConfigFileName() 检查尾部的''""''时,它寻找安装目录名中最后的非0字节,看它是等于''""''的,所以没有重新增加一个''""''。结果是代码返回了错误的文件名。
    哪里出错了呢?看看上面两个被用蓝色高量显示的字节。斜杠''""''的值是0x5c。'' ''的值是83 5c。上面的代码错误的读取了一个 trail byte,把它当作了一个字符。
    正确的后向遍历方法是使用能够识别DBCS字符的函数,使指针移动正确的字节数。下面是正确的代码。(指针移动的地方用红色标明)

    bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )
    {
    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 SetWindowTextA ( HWND hWnd, LPCSTR lpString );
    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来定义你的字符串,你将会陷入一个尴尬的境地。考虑下面的代码:

    HWND hwnd = GetSomeWindowHandle();
    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的定义如下:

    #ifdef UNICODE
    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
    WCHARwchar_twchar_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*)
    LPWSTRzero-terminated Unicode string (wchar_t*) zero-terminated Unicode string (wchar_t*)
    LPCWSTRconstant zero-terminated Unicode string (const wchar_t*)constant zero-terminated Unicode string (const wchar_t*)
    TCHAR char wchar_t
    LPTSTRzero-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将会使你受益:

  • 1.你的程序只运行在Windows NT系统中。
  • 2. 你的程序需要处理超过MAX_PATH个字符长的文件名。
  • 3. 你的程序需要使用XP中引入的只有Unicode版本的API.
  • Windows 9x 中大多数的 API 没有实现 Unicode 版本。所以,如果你的程序要在windows 9x中运行,你必须使用MBCS APIs。然而,由于NT系统内部都使用Unicode,所以使用Unicode APIs将会加快你的程序的运行速度。每次,你传递一个字符串调用MBCS API,操作系统会把这个字符串转换成Unicode字符串,然后调用对应的Unicode API。如果一个字符串被返回,操作系统还要把它转变回去。尽管这个转换过程被高度优化了,但它对速度造成的损失是无法避免的。
    只要你使用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/
    • 0
      点赞
    • 4
      收藏
      觉得还不错? 一键收藏
    • 0
      评论
    对于内核调试Windbg 是一种常用的工具。它是微软官方提供的调试器,用于调试 Windows 操作系统的内核模式和用户模式程序。以下是一些关于使用 Windbg 进行内核调试的基本步骤: 1. 准备环境:首先需要安装 Windbg 调试工具,并确保目标机器上已经启用了内核调试。 2. 连接目标机器:使用串口、网络或火线等方式将目标机器连接到调试机器上。 3. 配置符号文件路径:在调试机器上设置符号文件路径,以便 Windbg 能够正确解析调试信息。 4. 启动 Windbg:打开 Windbg 工具,并选择内核模式调试。可以通过 File -> Kernel Debug... 或者 Ctrl+K 快捷键来选择。 5. 配置调试连接:在弹出的对话框中,选择调试连接的方式和连接参数,比如选择串口调试、网络调试或火线调试,并输入相应的参数。 6. 开始调试:点击 OK 后,Windbg 会尝试与目标机器建立连接,并开始内核调试。在这个阶段,可以观察到目标机器的启动过程,并在 Windbg 中查看和分析调试信息。 7. 运行调试命令:在 Windbg 中可以使用各种调试命令来控制调试过程,比如设置断点、查看寄存器、内存和堆栈等信息,以及执行单步调试调试器命令等。 8. 分析问题:通过观察调试信息和运行调试命令,可以分析出问题的原因,并进一步进行故障排除和调试。 需要注意的是,内核调试是一项高级调试技术,对于初学者来说可能有一定的难度。在进行内核调试之前,建议先熟悉 Windbg 的基本使用方法,并了解一些常见的内核调试技巧和技术。

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值