功峰's profile旋动家族PhotosBlogListsMore Tools Help

Blog


    September 18

    【翻译文章】如何升级基于STL的应用来支持Unicode

                                         

    翻译作者:dozb,Nicole

    译注:注意,本文仅仅适合于MSVC环境中STL库,对于STLPort有问题

    原作者:Taka Muraoka

    原出处:http://www.codeproject.com/vcpp/stl/upgradingstlappstounicode.asp

    介绍

    我最近升级一个想当大的程序,目的是用Unicode代替single-byte 字符。除了少数遗留下来的模块,我忠实地使用t-functions并且用_T()宏包裹我的字符串和字符常量,众所周知这能安全的转换成Unicode,我要做的事情是定义UNICODE 和 _UNICODE,我祈祷所有事情将如我所愿的工作。

    天啊,我是多么地错误:((

    因此,我写这篇文章是为了治疗两周工作之痛,并且希望解除其他人的痛苦,这痛苦是我已经经受的。唉...

    基础

    理论上,写出用single- 或 double-字节字符能被编译的代码是直接的。我曾经想在这里写一节,但是Chris Maunder 已经写了 done it. 他描述的技术是广为人知的,因此对理解这篇文章的内容非常有帮助。

    Wide 文件 I/O

    这里是stream类的wide版本,它容易地定义t-风格的宏去管理他们:

    你将像这样用它们:

    tofstream testFile( "test.txt" ) ; 
    testFile << _T("ABC") ;

    现在,你期待的结果是,当用single-byte 字符编译的时候,执行代码将生成3字节的文件,当用double-byte 字符编译的时候,执行代码将生成6字节的文件。但是你错了,都是3字节的文件。
    到底怎么啦?

    这渊源是标准C++的规定,wide流当写到 file。必须转换double-byte 到single-byte 。如上例,宽字符串L"ABC"(有6个字节长),当写到文件前,被转换成窄字符串(3字节)。更坏的情况,如何转换由库的实现来决定的( implementation-dependent)。

    我不能找出一个确切的解释,为什么事情会弄成这样子。我猜测,文件被定义为考虑作为字符(single-byte)流。若允许同时写2字节的字符将无法提取。不管对还是错,这都导致严重的问题。例如,你不能写二进制数据到wofstream,因为这个类试图在输出前先窄字符化它。

    这对我是明显的问题,因为我有大量的函数像这样写:

    void outputStuff( tostream& os )
    {
        // output stuff to the stream
        os << ....
    }

    假如你传递的是tstringstream 对象将没有问题(例如,它流出宽字符),但是假如你传递的是tofstream 将得到怪异的结果(因为所有内容都被窄化了)。

    Wide 文件 I/O: 解决方案

    用调试器单步跟踪STL,结果发现wofstream 在写输出到文件以前,调用std::codecvt 对象来窄化输出的数据。std::codecvt对象是造成字符串从一种字符集到另一种字符集转换的原因。C++要求作为标准提供:1、转换chars 到 chars(例如,费力地什么也不做),2、转换wchar_ts 到chars。后一种就是引起我们这么多伤心事的原因。

    解决方案:写一个新的继承自codecvt的类,用来转换wchar_ts 到 wchar_ts(什么也不做),绑定到wofstream 对象中。当wofstream 试图转换它所输出的数据时,它将调用我们新的codecvt 对象,实际上什么也不做,不改变地写输出数据。

    在google groups浏览找一些P. J. Plauger写的代码 code (是MSVC环境中STL库的作者),但是用 Stlport 4.5.3 编译还是有问题。 这是最后敲定的版本:

    #include 
    
    // nb: MSVC6+Stlport can't handle "std::"
    // appearing in the NullCodecvtBase typedef.
    using std::codecvt ; 
    typedef codecvt < wchar_t , char , mbstate_t > NullCodecvtBase ;
    
    class NullCodecvt
        : public NullCodecvtBase
    {
    
    public:
        typedef wchar_t _E ;
        typedef char _To ;
        typedef mbstate_t _St ;
    
        explicit NullCodecvt( size_t _R=0 ) : NullCodecvtBase(_R) { }
    
    protected:
        virtual result do_in( _St& _State ,
                       const _To* _F1 , const _To* _L1 , const _To*& _Mid1 ,
                       _E* F2 , _E* _L2 , _E*& _Mid2
                       ) const
        {
            return noconv ;
        }
        virtual result do_out( _St& _State ,
                       const _E* _F1 , const _E* _L1 , const _E*& _Mid1 ,
                       _To* F2, _E* _L2 , _To*& _Mid2
                       ) const
        {
            return noconv ;
        }
        virtual result do_unshift( _St& _State , 
                _To* _F2 , _To* _L2 , _To*& _Mid2 ) const
        {
            return noconv ;
         }
        virtual int do_length( _St& _State , const _To* _F1 , 
               const _To* _L1 , size_t _N2 ) const _THROW0()
        {
            return (_N2 < (size_t)(_L1 - _F1)) ? _N2 : _L1 - _F1 ;
        }
        virtual bool do_always_noconv() const _THROW0()
        {
            return true ;
        }
        virtual int do_max_length() const _THROW0()
        {
            return 2 ;
        }
        virtual int do_encoding() const _THROW0()
        {
            return 2 ;
        }
    } ;

    你能看得出这些函数都是空架子,实际上什么也不做,仅仅返回noconv 指示而已。

    剩下要做的仅仅是把其实例化,并连接到wofstream 对象中。用MSVC,假定你用_ADDFAC() 宏(非标准的)来imbue一个locale到对象。可是它不能和我的新的NullCodecvt类工作,因此我绕过这个宏,写一个新的来代替:

    #define IMBUE_NULL_CODECVT( outputFile ) \
    { \
        NullCodecvt* pNullCodecvt = new NullCodecvt ; \
        locale loc = locale::classic() ; \
        loc._Addfac( pNullCodecvt , NullCodecvt::id, NullCodecvt::_Getcat() ) ; \
        (outputFile).imbue( loc ) ; \
    }

    好,上面给出的不能好好工作的例子代码,现在能这样写:

    tofstream testFile ;
    IMBUE_NULL_CODECVT( testFile ) ;
    testFile.open( "test.txt" , ios::out | ios::binary ) ; 
    testFile << _T("ABC") ;

    重要的是必须是在打开文件前,文件流对象要用新的codecvt对象imbue。文件也必须用binary模式打开。假如不是这种模式,每次文件看一个宽字符的高位或低位是10的时候,它将进行既定的CR/LF翻译,结果不是你想要的。假如你真的想要CR/LF序列,你可以明确地插入"\r\n"来代替std::endl。

    wchar_t 问题

    wchar_t 是宽字符的类型,其定义如下:

    typedef unsigned short wchar_t ;

    不幸的是,因为它用typedef 代替真正的C++类型,这样定义有一个棘手的缺点:你不能重载它。看下面的代码:

    TCHAR ch = _T('A') ;
    tcout << ch << endl ;

    用窄字符串,正如你期望的:打印出字符A。用宽字符,它打印出65。编译器决定出,你正在流出一个unsigned short 并且把它作为数字值来代替宽字符来打印它。哈哈!!!找出在你流出特别的字符的地方并修正它,比起贯串你整个代码的基础,这不是办法。我写了一个小函数,使得情况好一些:

    #ifdef _UNICODE
        // NOTE: Can't stream out wchar_t's - convert to a string first!
        inline std::wstring toStreamTchar( wchar_t ch ) 
                { return std::wstring(&ch,1) ; }
    #else 
        // NOTE: It's safe to stream out narrow char's directly.
        inline char toStreamTchar( char ch ) { return ch ; }
    #endif // _UNICODE    
    
    TCHAR ch = _T('A') ;
    tcout << toStreamTchar(ch) << endl ;

    Wide 异常类

    多数C++程序用异常来捕获错误的发生。不幸地,std::exception 被定义成这个样子:

    class std::exception
    {
        // ...
        virtual const char *what() const throw() ;
    } ;

    仅仅能捕获窄字符的错误信息。我曾经throw自己定义的或std::runtime_error的异常,因此我写了一个std::runtime_error 的版本如下:

    class wruntime_error
        : public std::runtime_error
    {
    
    public:                 // --- PUBLIC INTERFACE ---
    
    // constructors:
                            wruntime_error( const std::wstring& errorMsg ) ;
    // copy/assignment:
                            wruntime_error( const wruntime_error& rhs ) ;
        wruntime_error&     operator=( const wruntime_error& rhs ) ;
    // destructor:
        virtual             ~wruntime_error() ;
    
    // exception methods:
        const std::wstring& errorMsg() const ;
    
    private:                // --- DATA MEMBERS ---
    
    // data members:
        std::wstring        mErrorMsg ; ///< Exception error message.
        
    } ;
    
    #ifdef _UNICODE
        #define truntime_error wruntime_error
    #else 
        #define truntime_error runtime_error
    #endif // _UNICODE
    
    /* -------------------------------------------------------------------- */
    
    wruntime_error::wruntime_error( const wstring& errorMsg )
        : runtime_error( toNarrowString(errorMsg) )
        , mErrorMsg(errorMsg)
    {
        // NOTE: We give the runtime_error base the narrow version of the 
        //  error message. This is what will get shown if what() is called.
        //  The wruntime_error inserter or errorMsg() should be used to get 
        //  the wide version.
    }
    
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
    
    wruntime_error::wruntime_error( const wruntime_error& rhs )
        : runtime_error( toNarrowString(rhs.errorMsg()) )
        , mErrorMsg(rhs.errorMsg())
    {
    }
    
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
    
    wruntime_error&
    wruntime_error::operator=( const wruntime_error& rhs )
    {
        // copy the wruntime_error
        runtime_error::operator=( rhs ) ; 
        mErrorMsg = rhs.mErrorMsg ; 
    
        return *this ; 
    }
    
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
    
    wruntime_error::~wruntime_error()
    {
    }
    
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
    
    const wstring& wruntime_error::errorMsg() const { return mErrorMsg ; }

    (toNarrowString() 是一个小函数用来转换宽字符到窄字符,下面会给出). wruntime_error 简单地保存宽错误信息自身的一个拷贝,并且为适应有人调用what(),给出一个基于std::exception的窄版本。 我定义的异常类,如下:

    class MyExceptionClass : public std::truntime_error
    {
    public:
        MyExceptionClass( const std::tstring& errorMsg ) : 
                                std::truntime_error(errorMsg) { } 
    } ;

    最后的问题是我有大量的代码看起来如下: 

    try
    {
        // do something...
    }
    catch( exception& xcptn )
    {
        tstringstream buf ;
        buf << _T("An error has occurred: ") << xcptn ; 
        AfxMessageBox( buf.str().c_str() ) ;
    }

    我已经定义了一个std::exception的插入者,如下:

    tostream&
    operator<<( tostream& os , const exception& xcptn )
    {
        // insert the exception
        // NOTE: toTstring() converts a string to a tstring - defined below
        os << toTstring( xcptn.what() ) ;
    
        return os ;
    }

    问题是我的插入者调用what(),其仅仅返回窄版本的错误信息。但是假如错误信息包含外国字符,我想看他们在错误对话框。因此我重写了插入者如下:

    tostream&
    operator<<( tostream& os , const exception& xcptn )
    {
        // insert the exception
        if ( const wruntime_error* p = 
                dynamic_cast<const wruntime_error*>(&xcptn) )
            os << p->errorMsg() ; 
        else 
            os << toTstring( xcptn.what() ) ;
    
        return os ;
    }

    现在,它检测是否给的是一个宽异常类,假如是,流出宽错误信息。否则它用标准的窄错误信息取回。即使我可以专门用truntime_error起源的类在我的应用中,后面的情况仍然是重要的,因为STL或其他第三方库可以throw 来自std::exception的错误。

    其他各种问题

    • Q100639: 假如你在MFC中使用Unicode,你需要指定wWinMainCRTStartup 作为你的进入点(在你的Project Options中的Link页面里)。
    • 许多windows函数接受一个buffer来在里面返回其结果。buffer大小通常以字符多少指定,非字节。因此下面的代码用single-byte 编译的时候工作良好:
      // get our EXE name 
      TCHAR buf[ _MAX_PATH+1 ] ; 
      GetModuleFileName( NULL , buf , sizeof(buf) ) ;

      double-byte 字符的时候将发生错误。调用GetModuleFileName()需要这么写:

      GetModuleFileName( NULL , buf , sizeof(buf)/sizeof(TCHAR) ) ;
    • 假如你一个一个字节地处理文件的时候,你需要测试WEOF, 而不是 EOF。
    • 在发送前,HttpSendRequest() 接收一个字符串,用来指定附加的头绑定到HTTP请求。ANSI建造接收一个长度为-1的字符串意味着头字符是以NULL结束的。Unicode 建造需要字符串的长度必须明确提供。不要问我为什么。

    各种有用的东东

    最后,假如你做类似工作,一些小函数对你来说可能有用:

    extern std::wstring toWideString( const char* pStr , int len=-1 ) ; 
    inline std::wstring toWideString( const std::string& str )
    {
        return toWideString(str.c_str(),str.length()) ;
    }
    inline std::wstring toWideString( const wchar_t* pStr , int len=-1 )
    {
        return (len < 0) ? pStr : std::wstring(pStr,len) ;
    }
    inline std::wstring toWideString( const std::wstring& str )
    {
        return str ;
    }
    extern std::string toNarrowString( const wchar_t* pStr , int len=-1 ) ; 
    inline std::string toNarrowString( const std::wstring& str )
    {
        return toNarrowString(str.c_str(),str.length()) ;
    }
    inline std::string toNarrowString( const char* pStr , int len=-1 )
    {
        return (len < 0) ? pStr : std::string(pStr,len) ;
    }
    inline std::string toNarrowString( const std::string& str )
    {
        return str ;
    }
    
    #ifdef _UNICODE
        inline TCHAR toTchar( char ch )
        {
            return (wchar_t)ch ;
        }
        inline TCHAR toTchar( wchar_t ch )
        {
            return ch ;
        }
        inline std::tstring toTstring( const std::string& s )
        {
            return toWideString(s) ;
        }
        inline std::tstring toTstring( const char* p , int len=-1 )
        {
            return toWideString(p,len) ;
        }
        inline std::tstring toTstring( const std::wstring& s )
        {
            return s ;
        }
        inline std::tstring toTstring( const wchar_t* p , int len=-1 )
        {
            return (len < 0) ? p : std::wstring(p,len) ;
        }
    #else 
        inline TCHAR toTchar( char ch )
        {
            return ch ;
        }
        inline TCHAR toTchar( wchar_t ch )
        {
            return (ch >= 0 && ch <= 0xFF) ? (char)ch : '?' ;
        } 
        inline std::tstring toTstring( const std::string& s )
        {
            return s ;
        }
        inline std::tstring toTstring( const char* p , int len=-1 )
        {
            return (len < 0) ? p : std::string(p,len) ;
        }
        inline std::tstring toTstring( const std::wstring& s )
        {
            return toNarrowString(s) ;
        }
        inline std::tstring toTstring( const wchar_t* p , int len=-1 )
        {
            return toNarrowString(p,len) ;
        }
    #endif // _UNICODE
    
    /* -------------------------------------------------------------------- */
    
    wstring 
    toWideString( const char* pStr , int len )
    {
        ASSERT_PTR( pStr ) ; 
        ASSERT( len >= 0 || len == -1 , _T("Invalid string length: ") << len ) ; 
    
        // figure out how many wide characters we are going to get 
        int nChars = MultiByteToWideChar( CP_ACP , 0 , pStr , len , NULL , 0 ) ; 
        if ( len == -1 )
            -- nChars ; 
        if ( nChars == 0 )
            return L"" ;
    
        // convert the narrow string to a wide string 
        // nb: slightly naughty to write directly into the string like this
        wstring buf ;
        buf.resize( nChars ) ; 
        MultiByteToWideChar( CP_ACP , 0 , pStr , len , 
            const_cast(buf.c_str()) , nChars ) ; 
    
        return buf ;
    }
    
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
    
    string 
    toNarrowString( const wchar_t* pStr , int len )
    {
        ASSERT_PTR( pStr ) ; 
        ASSERT( len >= 0 || len == -1 , _T("Invalid string length: ") << len ) ; 
    
        // figure out how many narrow characters we are going to get 
        int nChars = WideCharToMultiByte( CP_ACP , 0 , 
                 pStr , len , NULL , 0 , NULL , NULL ) ; 
        if ( len == -1 )
            -- nChars ; 
        if ( nChars == 0 )
            return "" ;
    
        // convert the wide string to a narrow string
        // nb: slightly naughty to write directly into the string like this
        string buf ;
        buf.resize( nChars ) ;
        WideCharToMultiByte( CP_ACP , 0 , pStr , len , 
              const_cast<char*>(buf.c_str()) , nChars , NULL , NULL ) ; 
    
        return buf ; 
    }

    September 11

    Windbg 使用指南 转

    debug常用命令解析:
    1 !address eax查看对应内存页的属性
    2 vertarget 显示当前进程的大致信息
    3 !peb 显示process Environment Block
    4 lmvm 可以查看任意一个dll的详细信息
     例如:0:026 lmvm msvcrt (deferred)表示察看msvcrt.dll的信息,但是没有加载
    symbol可以通过.reload命令来加载
     
    5.reload /!sym 加载符号文件
     
    6 lmf 列出当前进程中加载的所有dll文件和对应的路径
    0:018> lmf
     
    7 r 命令显示和修改寄存器上的值
    r命令显示和修改寄存器上的值
    0:018> r     显示寄存器的值
    0:018> r eax=0 修改了寄存器,把eax的值修改为0x0
     
    8 d命令显示esp寄存器指向的内存
    如下
    0:018>d esp
     
    用dd命令直接指定054efc14地址
    0:018>dd 054efc14 
    注意:第二个d表示DWORD格式,此外还有db(byte),du(Unicode),dc(char)等等。
    数据查看指令 d{a|b|c|d|D|f|p|q|u|w|W}
    d{b|c|d|D|f|p|q}
    分别是显示:
    byte&ASCII, double-word&ASCII,double-word,double-precision,float,pointer-sized,quad-word
    数据;
    DA
    用于显示ASCIIDU用于显示UNICODE
    BYB
    BYD,显示binaryBytebinaryDWORD
    补充一个DV,用于查看本地变量用的
    9 e命令可以用来修改内存地址
    跟d命令一样,e命令后面也可以跟类型后缀,比如ed命
    令表示用DWORD的方式修改。下面的命令把054efc14地址上的值修改为11112222。
    0:018>ed 054efc14 11112222
    修改后可以用dd命令来查看内存。
    0:018>dd 0543fc14 L4 L4参数指定内存区间的长度为4个DWORD,这样输出只有1行,
    而不是8行了。
     
    10s 命令用来搜索内存具体见help文档
    11!runaway 可以显示每一个线程的cpu消耗
    0:018> !runaway 结果如下:
     0:83c       0 days 0:00:00.406
     13:bd4       0 days 0:00:00.046
     10:ac8       0 days 0:00:00.046
     24:4f4       0 days 0:00:00.031
     上面输出的第一列是线程的编号和线程ID,后一列对应的是该线程在用户态模式中的
    总的繁忙时间。
     在该命令加上f参数,还可以看到内核态的繁忙时间,当进程内存占用率比较高的时候
    ,通过该命令可以方便的找到对应的繁忙线程。
     
    12 ~ 命令是用来切换目标线程
    0:018> ~ 可以显示线程的信息
    0:018> ~0s把当前的线程切换到0号线程,也就是主线程,切换后提示符会变为0:000.
    13 ~* 命令列出当前进程中的所有线程的详细信息
    14~*kb命令列出所有线程的堆栈
    15 k 命令用来显示当前线程的堆栈,如下
    0:018> k
    跟d命令一样,k后面也可以跟很多后缀,比如kb kp,kn,kv,kl等,这些后缀控制了
    显示的格式和信息。
    栈指令k[b|p|P|v]
    这四条指令显示的内容类似,但是每个指令都有特色,KB显示三个参数Kp显示所有的参数,但需要Full SymbolsPrivate PDBSymbols支持。KPKp相似,只是KP将参数换行显示了Kv用于显示FPO和调用约定KD,用于显示StackDump,在跟踪栈时比较有用。
    这些指令区分大小。

    16 u命令把指定地址上的代码翻译成汇编输出
    0:018> u 7739d023
    USER32!NtUserWaitMessage:
    7739d023 b84a120000       mov     eax,0x124a
    7739d028 ba0003fe7f       mov     edx,0x7ffe0300
    7739d02d ff12             call    dword ptr [edx]
    7739d02f c3               ret
    如果符号文件加载正确,可以用uf命令直接反汇编整个函数,比如uf USER32! NtUserWaitMessage
     
    17 x 查找符号的二进制地址如下
    0:018> x msvcr!printf
    77bd27c2 msvcrt!printf = <no type information>
    上面的命令找到了printf函数的入口地址在77bd27c2
     
    0:001> x ntdll!GlobalCounter
    7c99f72c ntdll!GlobalCounter = <no type information>
    上面的命令表示ntdll!GlobalCounter这个变量保存的地址是7c99f72c。
    注意:符号对应的是变量和变量所在的地址,不是变量的值,上面只是找到GlobalCounter这个变量的值是7c99f72,要找到变量的值,需要用d命令读取内存地址来获取。
     
    X命令还支持通配符,比如x ntdll !*命令列出ntdll模块中的所有的符号,以及对应的二进制地址。
     
    18 dds 打印内存地址上的二进制值
    同时自动搜索二进制值对应的符号。
    比如要看看当前**中保存了那些函数地址,就可以检查ebp指向的内存
    0:018>dds ebp
    0013ed98  0013ee24
    0013ed9c  75ecb30f BROWSEUI!BrowserProtectedThreadProc+0x44
    0013eda0  00163820
    0013eda4  0013ee50
    0013eda8  00163820
    0013edac  00000000
    0013edb0  0013ee10
    0013edb4  75ece83a BROWSEUI!__delayLoadHelper2+0x23a
    0013edb8  00000005
    0013edbc  0013edcc
    0013edc0  0013ee50
    0013edc4  00163820
    0013edc8  00000000
    0013edcc  00000024
    0013edd0  75f36d2c BROWSEUI!_DELAY_IMPORT_DESCRIPTOR_SHELL32
    0013edd4  75f3a184 BROWSEUI!_imp__SHGetInstanceExplorer
    0013edd8  75f36e80 BROWSEUI!_sz_SHELL32
    0013eddc  00000001
    0013ede0  75f3726a BROWSEUI!urlmon_NULL_THUNK_DATA_DLN+0x116
    0013ede4  7c8d0000 SHELL32!_imp__RegCloseKey <PERF> (SHELL32+0x0)
    0013ede8  7c925b34 SHELL32!SHGetInstanceExplorer
     
    这里dds命令从ebp指向的内存地址0013ed98开始打印,第一列是内存地址的值,第二列是地址上对应的二进制数据,第三列是二进制对应的符号。上面的命令自动找到了75ecb390f对应的符号是BROWSEUI!BrowserProtectedThreadProc +0x44.
     
    Com interface 和c++ vtable里面的成员函数都是顺序排列的。所以,dds命令可以方便的找到虚函数表中的具体的函数地址,比如用下面的命令可以找到OpaqueDatinfo类型中虚函数的实际函数地址。
    首先通过x命令找到OpaqueDataInfo虚函数地址
    0:000> x ole32!OpaqueDataInfo::vftable’
    7768265c ole32!OpaqueDataInfo::`vftable' = <no type information>
    77682680 ole32!OpaqueDataInfo::`vftable' = <no type information>
    接下来dds命令可以打印出虚函数表中的函数名字
    0:000> dds 7768265c
    19 .frame 命令在栈中切换以便检查局部变量
    要查看局部变量的需要如下:
    1查看线程的callstack
    0:018>knl
    00 0012f7a0 7c821c94 ntdll!KiFastSystemCallRet
    01 0012f7a4 7c836066 ntdll!NtRequestWaitReplyPort+0xc
    02 0012f7c4 77eaaba3 ntdll!CsrClientCallServer+0x8c
    03 0012f8bc 77eaacb8 kernel32!ReadConsoleInternal+0x1b8
    04 0012f944 77e41990 kernel32!ReadConsoleA+0x3b
     
    第一列的号称为Frame num,通过.frame命令就可以切换到对应的函数中检查局部变量,比如我们检查kernel32!ReadConsoleA,这个函数的frame num是4,于是,我们如下
    2 iframe切换到指定行号的函数中
    0:018> .frame 4
    3然后调用x显示当前frame的局部变量,比如这个函数中有两个局部变量pcls和rawptr
    0:018> x
    0012fced pcls = 0x0039ba80
    0012fcd8 rawptr = 0x0039ba80
     
    20 dt 格式化显示资料
    Dt命令格式化显示变量的资料和结构
    0:000> dt pcls
    Local var @ 0x12fce4 Type MyCls*
    0x0039ba80
       +0x000 str              : 0x00416648  "abcd"
       +0x004 inobj            : inner
    上面的命令打印出pcls的类型是MyCls指针,指向的地址是0x0039ba80,其中的两个class成员的偏移分别在+0和+4,对应的值在第2列显示。加上-b -r参数可以显示inner class和数组的信息:
    0:000> dt pcls -b -r
    Local var @ 0x12fce4 Type MyCls*
    0x0039ba80
       +0x000 str              : 0x00416648  "abcd"
       +0x004 inobj            : innner
          +0x000 arr              :  "abcd"
           [00] 97 'a'
           [01] 98 'b'
           [02] 99 'c'
           [03] 100 'd'
           [04] 0 ''
           [05] 0 ''
           [06] 0 ''
           [07] 0 ''
           [08] 0 ''
           [09] 0 ''
    对于任意的地址,也可以手动指定符号类型来格式化显示。比如把0x0039ba80地址上的数据用MyCls类型来显示:
    0:000> dt 0x0039ba80 MyCls
       +0x000 str              : 0x00416648  "abcd"
       +0x004 inobj            : innner
    21bp设定调试断点
     比如可以这样写:0:018>bp notepad!WinMain 在notepade的winmain函数处下断点
    断点的位置可以用符号来表示,如上,也可以直接用地址以及windbg的Pseudo_Register(虚拟寄存器)。比如,我们用$exentry表示进程的入口,那么可以用bp @$exentry在进程的入口设置断点,如果notepade的winmain的入口地址为01006420,那么断点也可以这么写
    Bp 01006420
    bp mysource.cpp:143` "j (poi(MyVar)”0n20) ''; 'g' "
    意思就是:当myvar的值等于0x20时,g命令继续执行
    下面一个设置条件断点
    0:001> bp exceptioninject!foo3 “k; .echo ‘breaks’ ; g”
    在exceptioninject!foo3上设置断点后,每次断下来后,先用k显示callstack,然后用.echo命令输出简单的字符串‘breaks’,最后g命令继续执行。
    下面看一个更复杂的设置条件断点的例子:
    ba w4 execptioninject!i ”j(poi(exceptioninject!i)<0n40) ‘.printf\”exceptioninject!i value is :%d\”,poi(exceptioninject!i); g’ ; ‘.echo stop!’ ”
    首先ba w4 exceptioninject!i 表示在修改exceptioninject!i这个全局变量的时候,停下来,
    j(judge)命令的作用就是对后面的表达式作条件判断如果为true,执行第一个单引号里面的命令,否则执行第2个单引号里面的命令。
    条件表达式是(poi(exceptioninject!i)<0n40),在windbg中excepioninject!i符号表示符号所在的内存地址,而不是符号的数值,相当于c语言的&操作符的作用,poi命令就是取这个地址上的值,相当于c语言的*操作符。所以这个条件判断的意思就是判断exceptioninject!i的值,是否小于十进制的40。如果为真,就执行第一个单引号,‘.printf\”exceptioninject!i value is :%d\”,poi(exceptioninject!i); g’,如果为假,就执行第二个单引号‘.echo stop!’
    第一个单引号里有三个命令,.printf .echo g。这里的printfc语言的printf函数语法一样,不过由于这个printf命令本身是在ba命令的双引号里面,所以需要用\来转义print中的引号。第一个引号的作用是:打印出当前exceptioninject!i的值,.echo命令换行 g命令继续执行
    第二个引号的作用就是显示stop,由于后面没有g命令,所以windbg会停下。
    22 bm 使用模式匹配设置断点
    这个功能需要符号表的支持,bm可以通过模式一次设置多个断点,比如
    bm mydriver!FastIO* 可以将所有与FastIO*模式匹配的函数下设置断点,比如FastIoRead ,FastIoWriter等函数都会被设置上断点。需要注意的是,bm命令需要full or export symbols支持。
    23 ba 对内存访问设置断点 break on access
    就是对于内存访问设置断点,对于在多核处理或者多核处理器调试的时候很有用,对于调试多线程也很有用,比如说,我们可以对一个全局变量设置断点,
    ba mydriver!gMonitoreedDevices , 如果你认为这个变量的值被莫名的修改了,相信通过ba设置的断点,你可以很快找到是谁修改的。
    也可以这样
    ba w4 0x4000000 "kb;g" 当0x4000000地址有写操作时,进入断点 。w表示类型为写 4表示长度为4个字节
    24 bl 列出所有的断点 break list
    25 bc 清除断点       break clear
    26 be 开启断点      break enable
    27 bd禁用断点       break disable
    以上提到的断点指令通过和j指令很容易形成条件断点,比如
    bp USER32!GetMessageW "r $t1=poi(esp+4);r $t2=poi(@$t1+4); j(@$t2 = 0x102 ) 'du @$t1+8 L2;gc';'gc'"
    这个条件断点,截取WM_CHAR消息,并将字符(包括中文)显示出来。
    条件断点的最简形式:bp Address "j (Condition) 'OptionalCommands'; 'gc' "
    Address是指令的地址,Condition是一个条件表达式,如果@eax=1,'OptionalCommands'是在断点被击中并且表达式成立时要执行的指令;gc指定是从一个条件断点返回,是不可少的一部分。
    28跟踪指令T,TA,TB,TC,WT,P,PA,PC
      T指令单步执行,在源码调试状态下,可指源码的一行,根据不同的选项也可以为一行ASM指令;
    TA
    单步跟踪到指定地址,如果没有参数将运行到断点处。
    TB
    执行到分支指令,分支指令包括calls, returns, jumps, counted loops, and while loops
    TC
    执行到Call指令
    WT Trace and Watch Data
    ,一条强大指令,对执行流程做Profile,执行一下看看结果吧
    P
    PAPC相信不用多做解释,大家也都明白了
    29源代码操作指令.lsflsclsllsp
    .指令打一个源文件,可以打开一个全路径的文件,也可以通过函数地址来打开并定位到源文件中函数的位置,如. –a myapp!main. j:\mydriver\mydriver.c
    lsf
    指定一个源文件为当前源文件,使用lsc可显示当前指定的源文件ls可显示源文件的代码。Lsf可以使用全路径,如果源路径已经设置,也可以直接指定源文件名称。如lsf mydriver.clsf j:\mydriver\mydriver.c
    lsc
    显示当前源文件
    ls
    显示当前源文件的代码,如ls 200显示第200
    l
    用于设置源文件选项
    lsp
    设置源文件行在调试时显示范围比如,显示当前行的前50,后50lsp 100
    但通常使用Windbg时,可以直接用Ctrl+O来打开并查看源文件
    30 查询符号
    kd> x nt!KeServiceDescriptorTable*
    8046e100 nt!KeServiceDescriptorTableShadow = <no type information>
    8046e0c0 nt!KeServiceDescriptorTable = <no type information>
    kd> ln 8046e100
    (8046e100)     nt!KeServiceDescriptorTableShadow     | (8046e140)     nt!MmSectionExtendResource
    Exact matches:
    nt!KeServiceDescriptorTableShadow = <no type information>
    31!gle 查看LastError
    32指定进制的形式0x/0n/0t/y 分别表示 16/10/8/2进制
    ? 0x12345678+0n10
    Evaluate expression: 305419906 = 12345682
    33!sym noice/quiet symbol prompts开关
    34.srcpath 设置源代码的路径
    35dv查看本地变量
    36!teb 显示当前线程的执行块(execution block
    37!peb 显示当前进程的执行块(execution block
    38ln[Address] 显示当前地址上的对象类型
    39!locks 显示死锁
    40!handle可以获取整个进程或者某一个handle的详细信息
    首先运行以下!handle,可以看到当前进程的每个一个handle的类型,以及统计信息
    0:002>!handle
    Handle 4
     Type      key
    Handle c
       Type     keyEvent
    …….
    然后找到一个key,查看详细信息
    0:001>!handle 4 f
    就会列出这个handle的详细信息。
    41!htrace命令检查操作句柄的历史记录
    !htrace命令可以打印出指定的handle的最近几次调用堆栈
    0:001>!htrace 384
    42!cs列出CriticalSection的详细信息
    43!threadpool能看到完成端口,线城池工作线程和timer回调占线程池的情况
    44.time 可以看到进程跑了多长时间
    45 !dso 查看当前线程中有哪些对象,分析泄露时用到
    46.dump保存进程的dump文件
    Dump文件是进程的内存镜像,可当在调试器中打开dump文件时,使用上面的命令检查,看到的结果跟用调试检查进程看到的一样
    .dump /ma c:\testdump.dmp
    这个命令把当前进程的镜像保存为c:\testdump.dmp,其中/ms参数表示dump的文件应该包含进程的完整信息。
    在windbg中,通过file—open---open Crash dump菜单打开dump文件进行分析。打开文件后,运行调试命令看到的信息和状态就是dump文件保存时进程的状态。通过dump文件能够方便的保存发生问题时进程的状态,方便事后分析。