topameng's profileQuake3 启示录PhotosBlogListsMore Tools Help

Blog


    November 22

    软件Release 版本 Crash 堆栈信息收集

    比较详细的地址是:http://www.codeproject.com/debug/XCrashReportPt3.asp

    不过作者考虑了没有vc等情况。
    其实如果有vc使用ms 的 MiniDumpWriteDump 将会非常简单. 如下面100行左右搞定,理解、转换为unicode 编码都简单很多.只需要装入相应的头文件即可记录crash dump 文件。用vc打开dump 文件。按F5即可运行到crash地点
    vc Release 工程必须设置:
    链接器 ->调试-> 生成调试信息 是(/DEBUG)
    链接器->优化->引用->消除未引用数据(/OPT:REF)

    头文件 minidump.h

    #pragma once
    #include <windows.h>
    #include <tchar.h>
    #if _MSC_VER < 1300
    #define DECLSPEC_DEPRECATED
    // VC6: change this path to your Platform SDK headers
    #include "c:\\dev7\\vs\\devtools\\common\\win32sdk\\include\\dbghelp.h"            // must be XP version of file
    #else
    // VC7: ships with updated headers
    #include "dbghelp.h"
    #endif

    // based on dbghelp.h

    class MiniDumper
    {
        typedef BOOL (WINAPI* MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType,
                                                CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
                                                CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
                                                CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
    private:
        static LPCTSTR m_szAppName;
        static LONG WINAPI TopLevelFilter(struct _EXCEPTION_POINTERS *pExceptionInfo);
    public:
        MiniDumper(LPCTSTR szAppName);
        ~MiniDumper();
    };


    //源文件 minidump.cpp

    #include "minidump.h"
    #include <assert.h>
    #include <stdio.h>
    #include <direct.h>

    LPCTSTR MiniDumper::m_szAppName;
    #ifndef _DEBUG
    MiniDumper dumper(_T("xyj2007"));
    #endif

    MiniDumper::MiniDumper(LPCTSTR szAppName)
    {
        // if this assert fires then you have two instances of MiniDumper which is not allowed
        assert( m_szAppName==NULL );            //确认只创建一个 minidumper
        m_szAppName =_tcsdup(szAppName);
        ::SetUnhandledExceptionFilter(TopLevelFilter);
    }

    MiniDumper::~MiniDumper()
    {
        if(m_szAppName)
        {
            free( (void *) m_szAppName);
            m_szAppName = NULL;
        }
    }

    LONG MiniDumper::TopLevelFilter( struct _EXCEPTION_POINTERS *pExceptionInfo )
    {
        LONG retval = EXCEPTION_CONTINUE_SEARCH;
        HWND hParent = NULL;                        // find a better value for your app

        // firstly see if dbghelp.dll is around and has the function we need
        // look next to the EXE first, as the one in System32 might be old
        // (e.g. Windows 2000)
        HMODULE hDll = NULL;
        TCHAR szDbgHelpPath[_MAX_PATH];

        if (GetModuleFileName( NULL, szDbgHelpPath, _MAX_PATH ))
        {
            TCHAR *pSlash = _tcsrchr( szDbgHelpPath, _T('\\') );
            if (pSlash)
            {
                _tcscpy(pSlash+1, _T("DBGHELP.DLL"));
                hDll = ::LoadLibrary( szDbgHelpPath );
            }
        }

        if (hDll==NULL)
        {
            // load any version we can
            hDll = ::LoadLibrary(_T("DBGHELP.DLL"));
        }

        LPCTSTR szResult = NULL;

        if (hDll)
        {
            MINIDUMPWRITEDUMP pDump = (MINIDUMPWRITEDUMP)::GetProcAddress(hDll,"MiniDumpWriteDump");
            if (pDump)
            {
                TCHAR szDumpPath[_MAX_PATH];
                TCHAR szScratch [_MAX_PATH];

                // work out a good place for the dump file
                _tgetcwd(szDumpPath,_MAX_PATH);
                _tcscat( szDumpPath, _T("\\"));

                _tcscat( szDumpPath, m_szAppName );
                _tcscat( szDumpPath, _T(".dmp"));

                // ask the user if they want to save a dump file
                if (::MessageBox(NULL,_T("程序发生意外,是否保存一个文件用于诊断?"),m_szAppName,MB_YESNO)==IDYES)
                {
                    // create the file
                    HANDLE hFile = ::CreateFile( szDumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
                                                FILE_ATTRIBUTE_NORMAL, NULL );

                    if (hFile!=INVALID_HANDLE_VALUE)
                    {
                        _MINIDUMP_EXCEPTION_INFORMATION ExInfo;
                        ExInfo.ThreadId = ::GetCurrentThreadId();
                        ExInfo.ExceptionPointers = pExceptionInfo;
                        ExInfo.ClientPointers = NULL;

                        // write the dump
                        BOOL bOK = pDump( GetCurrentProcess(),GetCurrentProcessId(),hFile,MiniDumpNormal,&ExInfo,NULL,NULL);
                        if (bOK)
                        {
                            _stprintf( szScratch, _T("保存文件到:'%s'"), szDumpPath );
                            szResult = szScratch;
                            retval = EXCEPTION_EXECUTE_HANDLER;
                        }
                        else
                        {
                            _stprintf( szScratch, _T("保存文件到 '%s'失败,(错误号: %d)"), szDumpPath, GetLastError() );
                            szResult = szScratch;
                        }
                        ::CloseHandle(hFile);
                    }
                    else
                    {
                        _stprintf( szScratch, _T("在'%s'创建 dump 文件失败,(错误号 %d)"), szDumpPath, GetLastError() );
                        szResult = szScratch;
                    }
                }
            }
            else
            {
                szResult = _T("dbghelp.dll 文件太旧,不能支持MiniDumpWriteDump函数");
            }
        }
        else
        {
            szResult = _T("dbghelp.dll 文件不存在");
        }

        if (szResult)
        {
            ::MessageBox( NULL, szResult, m_szAppName, MB_OK );
        }

        return retval;
    }

    November 15

    winsock 10053

    这是个比较烦人的错误。不好遇到不好调试。而往往又不是软件的问题。
    当浏览 quake3 网络代码时,被他的delta 网络压缩搞得头昏脑胀。里面就有一项拆包的。既udp 包要小于服务器mtu 允许的大小(设置为1400).
    所以当用tcp send 消息时最好不要全部 send 再取发送长度,这样可能因为超过 mtu 允许而造成失败。而有时由于网络一次发送的又少于mtu极限,搞得时而成功时而失败。默认xp操作系统的mtu限制是1500,而有些服务器对adsl 限制是1492.
    其实真正好的做法应该是拆成小的包。如512大小。再加上包头指定包大小。这样更有利于网络传输。
    还有iocp之类的。
    当然如果你 send 的数据 buf 长度小于 len 指定的。一定会发生这个错误。
    或许这只是其中一个原因。先留一笔记住他

    November 14

    Quake 3 Network Protocol

    This document describes the network protocol that quake 3 uses to converse with clients and the outside world (query servers). Currently more of a work in progress.

    Query

    To query a server is very simple. Send a connectionless (UDP  对应服务器接收函数SV_ConnectionlessPacket) packet with 4 OOB header bytes (0xff) and the text string getstatus.(例如:NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" )) There are many sites which contain a thorough description of this so I won't go into details.

    Game Protocol 68 - used by 1.32

    All game packets are connectionless (UDP), but there is still a handshaking process which much occur before you are allowed to join the server.

    The client sends a challenge request (sometimes you need to send multiple requests before the server will respond).
    NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "getchallenge");

    +------------+----------------+
    | Header     | Content        |
    +------------+----------------+
    | 0xffffffff | getchallenge   |
    +------------+----------------+

    If the server is able to accept more connections it will reply with.
    NET_OutOfBandPrint( NS_SERVER, from, "challengeResponse %i", challenge->challenge )

    +------------+------------------------+
    | Header     | Content                |
    +------------+------------------------+
    | 0xffffffff | challengeResponse <ID> |
    +------------+------------------------+

    Once the client has the <ID> it can send a connect request. However, the CS is huffman compressed in protocol 68 and is NOT clear text as indicated below. Protocol 43 uses the plain text version.

    +------------+----------------+
    | Header     | Content        |
    +------------+----------------+
    | 0xffffffff | connect "<CS>" |
    +------------+----------------+
    <CS> represents a connection string containing the player details, e.g.
    \cg_predictItems\1\sex\male\handicap\100\color\3\snaps\40\rate\10000\model\doom/red\name\UnnamaedPlayer\protocol\68\qport\<PORT>\challenge\<ID>
    <PORT> represents the local port used to send this packet

    If the connect is successful the server will reply with the following

    +------------+-----------------+
    | Header     | Content         |
    +------------+-----------------+
    | 0xffffffff | connectResponse |
    +------------+-----------------+

    The server will now place you in the CNCT (connecting) state and start sending you game updates.

    This is where the communication gets substantially more difficult, so I warn you what follows may be incomplete and perhaps incorrect - although I hope not!.

    Client to Server
    clienttoserver 

    Client commands

    0 - clc_bad
    1 - clc_nop
    2 - clc_move
    3 - clc_moveNoDelta
    4 - clc_clientCommand
    5 - clc_EOF
    clientcommands

    Server to Client

     servertoclient

    Server Commands

    0 - svc_bad
    1 - svc_nop
    2 - svc_gamestate
    3 - svc_configstring
    4 - svc_baseline
    5 - svc_serverCommand
    6 - svc_download
    7 - svc_snapshot
    8 - svc_EOF
    Details

    (1) - Huffman compression using a predefined set of nodes to further reduce message length (detailed below).

    int msg_hData[256] = {
    250315,// 0
    41193,// 1
    6292,// 2
    7106,// 3
    3730,// 4
    3750,// 5
    6110,// 6
    23283,// 7
    33317,// 8
    6950,// 9
    7838,// 10
    9714,// 11
    9257,// 12
    17259,// 13
    3949,// 14
    1778,// 15
    8288,// 16
    1604,// 17
    1590,// 18
    1663,// 19
    1100,// 20
    1213,// 21
    1238,// 22
    1134,// 23
    1749,// 24
    1059,// 25
    1246,// 26
    1149,// 27
    1273,// 28
    4486,// 29
    2805,// 30
    3472,// 31
    21819,// 32
    1159,// 33
    1670,// 34
    1066,// 35
    1043,// 36
    1012,// 37
    1053,// 38
    1070,// 39
    1726,// 40
    888,// 41
    1180,// 42
    850,// 43
    960,// 44
    780,// 45
    1752,// 46
    3296,// 47
    10630,// 48
    4514,// 49
    5881,// 50
    2685,// 51
    4650,// 52
    3837,// 53
    2093,// 54
    1867,// 55
    2584,// 56
    1949,// 57
    1972,// 58
    940,// 59
    1134,// 60
    1788,// 61
    1670,// 62
    1206,// 63
    5719,// 64
    6128,// 65
    7222,// 66
    6654,// 67
    3710,// 68
    3795,// 69
    1492,// 70
    1524,// 71
    2215,// 72
    1140,// 73
    1355,// 74
    971,// 75
    2180,// 76
    1248,// 77
    1328,// 78
    1195,// 79
    1770,// 80
    1078,// 81
    1264,// 82
    1266,// 83
    1168,// 84
    965,// 85
    1155,// 86
    1186,// 87
    1347,// 88
    1228,// 89
    1529,// 90
    1600,// 91
    2617,// 92
    2048,// 93
    2546,// 94
    3275,// 95
    2410,// 96
    3585,// 97
    2504,// 98
    2800,// 99
    2675,// 100
    6146,// 101
    3663,// 102
    2840,// 103
    14253,// 104
    3164,// 105
    2221,// 106
    1687,// 107
    3208,// 108
    2739,// 109
    3512,// 110
    4796,// 111
    4091,// 112
    3515,// 113
    5288,// 114
    4016,// 115
    7937,// 116
    6031,// 117
    5360,// 118
    3924,// 119
    4892,// 120
    3743,// 121
    4566,// 122
    4807,// 123
    5852,// 124
    6400,// 125
    6225,// 126
    8291,// 127
    23243,// 128
    7838,// 129
    7073,// 130
    8935,// 131
    5437,// 132
    4483,// 133
    3641,// 134
    5256,// 135
    5312,// 136
    5328,// 137
    5370,// 138
    3492,// 139
    2458,// 140
    1694,// 141
    1821,// 142
    2121,// 143
    1916,// 144
    1149,// 145
    1516,// 146
    1367,// 147
    1236,// 148
    1029,// 149
    1258,// 150
    1104,// 151
    1245,// 152
    1006,// 153
    1149,// 154
    1025,// 155
    1241,// 156
    952,// 157
    1287,// 158
    997,// 159
    1713,// 160
    1009,// 161
    1187,// 162
    879,// 163
    1099,// 164
    929,// 165
    1078,// 166
    951,// 167
    1656,// 168
    930,// 169
    1153,// 170
    1030,// 171
    1262,// 172
    1062,// 173
    1214,// 174
    1060,// 175
    1621,// 176
    930,// 177
    1106,// 178
    912,// 179
    1034,// 180
    892,// 181
    1158,// 182
    990,// 183
    1175,// 184
    850,// 185
    1121,// 186
    903,// 187
    1087,// 188
    920,// 189
    1144,// 190
    1056,// 191
    3462,// 192
    2240,// 193
    4397,// 194
    12136,// 195
    7758,// 196
    1345,// 197
    1307,// 198
    3278,// 199
    1950,// 200
    886,// 201
    1023,// 202
    1112,// 203
    1077,// 204
    1042,// 205
    1061,// 206
    1071,// 207
    1484,// 208
    1001,// 209
    1096,// 210
    915,// 211
    1052,// 212
    995,// 213
    1070,// 214
    876,// 215
    1111,// 216
    851,// 217
    1059,// 218
    805,// 219
    1112,// 220
    923,// 221
    1103,// 222
    817,// 223
    1899,// 224
    1872,// 225
    976,// 226
    841,// 227
    1127,// 228
    956,// 229
    1159,// 230
    950,// 231
    7791,// 232
    954,// 233
    1289,// 234
    933,// 235
    1127,// 236
    3207,// 237
    1020,// 238
    927,// 239
    1355,// 240
    768,// 241
    1040,// 242
    745,// 243
    952,// 244
    805,// 245
    1073,// 246
    740,// 247
    1013,// 248
    805,// 249
    1008,// 250
    796,// 251
    996,// 252
    1057,// 253
    11457,// 254
    13504,// 255
    };

    (2) - XOR algorithm used by the server to decode the message content.

    #define CL_ENCODE_START 12
    byte key, *string;
    int i, index;

    string = (byte *)clc.serverCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ];
    index = 0;
    //
    key = clc.challenge ^ serverId ^ messageAcknowledge;
    for (i = CL_ENCODE_START; i < msg->cursize; i++)
    {
        // modify the key with the last received now acknowledged server command
        if (!string[index])
        {
            index = 0;
        }

        if (string[index] > 127 || string[index] == '%')
        {
            key ^= '.' << (i & 1);
        }
       else
       {
            key ^= string[index] << (i & 1);
      }
      index++;
    // encode the data with this key
    *(msg->data + i) = (*(msg->data + i)) ^ key;
    }

    3) - XOR algorithm used by the client to decode the message content.

    Notes

    data has mixed endianess - is this right?

    Packet fragmentation is worked out using a sequencetNumber & FRAGMENT_BIT (where FRAGMENT_BIT is 1<<31) calculation. If fragmented, flip the bit to 0 to correct the sequence number.

    cl_shownet 1 - MSG_SIZE
    cl_shownet 2|3 - READ_COUNT - CMD
    showpackets 1 - WHO recv MSG_SIZE : s=SEQ_NO (optional fragment info)

    November 01

    使用VLD检测内存泄露

    1 要能够配置vld 即可以使用下面的宏。由于我的是vc2003 。而网上的是vc6和7编译出来的库,不知道是什么版本(可能是多线程调试,单线程调试之类设置不匹配),这些配置宏不起作用. 结果:运行结束后,在调试窗口打印所有泄漏内存中的数据,结束程序时间超长。
    需要设置 VLD_MAX_DATA_DUMP 宏来控制打印数据的多少, 所以必须用源码编译一个。

    VLD_AGGREGATE_DUPLICATES
    重复内存泄露在同一个地方,只显示一次堆栈信息。但包含泄露次数
    VLD_MAX_TRACE_FRAMES
    跟踪的堆栈数目 #define VLD_MAX_TRACE_FRAMES 9 过少的话会发现没有调用函数堆栈显示。9 能显示到 new 的位置
    VLD_MAX_DATA_DUMP
    打印泄露内存数据长度。有些地方泄露内存数据太大,如果全部打印,退出程序时间超长。
    #define VLD_MAX_DATA_DUMP 10

    VLD_SELF_TEST
    测试自己

    VLD_SHOW_USELESS_FRAMES
     

    VLD_START_DISABLED

    2 编译vld 需要配置vc include 路径,加入vc crt目录,例如:C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\crt\src (最好放到所有include目录最后面)

    3 要注意不要把工程放入到中文目录,这样就不能看到泄露的函数堆栈了。那样还不如:
    #include "crtdbg.h"
    _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);

    我在 dll 中使用vld 。当动态调用loadlibrary 来使用dll 时候。vld 不能报告内存泄漏
    使用CrtSetDbgFlag 会在vc output 窗口中报告第多少块内存分配有泄漏然后使用CrtSetBreakAlloc中断即可。这两个函数放入dllmain中即可中断到泄漏的分配地点

    调试窗口内容:
    Dumping objects ->
    {353} normal block at 0x003B9C58, 320 bytes long.
    ~~~ 第353次内存分配发生内存泄漏
     Data: <  ;             > F0 9C 3B 00 CD CD CD CD 00 00 00 00 CD CD CD CD
     _CrtSetBreakAlloc(353);
    BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD reason, LPVOID)
    {
     //可以中断在第353次分配内存的地方
     _CrtSetBreakAlloc(353); 
     _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
     _tsetlocale(LC_ALL,_T("chs"));
     return TRUE;
    }