Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

565 lines
18 KiB

  1. //===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. // $Workfile: $
  6. // $NoKeywords: $
  7. //===========================================================================//
  8. #include "pch_tier0.h"
  9. #include "tier0/stackstats.h"
  10. #include "tier0/threadtools.h"
  11. #include "tier0/icommandline.h"
  12. #include "tier0/valve_off.h"
  13. #if defined( PLATFORM_WINDOWS_PC )
  14. #define WIN32_LEAN_AND_MEAN
  15. #include <windows.h>
  16. #include <dbghelp.h>
  17. #endif
  18. #if defined( PLATFORM_X360 )
  19. #include <xbdm.h>
  20. #include "xbox/xbox_console.h"
  21. #include "xbox/xbox_vxconsole.h"
  22. #include <map>
  23. #include <set>
  24. #endif
  25. #include "tier0/valve_on.h"
  26. #include "tier0/memdbgon.h"
  27. #if !defined( ENABLE_RUNTIME_STACK_TRANSLATION )
  28. bool _CCallStackStatsGatherer_Internal_DumpStatsToFile( const char *szFileName, const CCallStackStatsGatherer_Standardized_t &StatsGatherer, bool bAllowMemoryAllocations )
  29. {
  30. return false;
  31. }
  32. #else //#if !defined( ENABLE_RUNTIME_STACK_TRANSLATION )
  33. static void BufferedFwrite( FILE *pFile, uint8 *pBuffer, size_t iBufferSize, const void *pData, size_t iDataSize, size_t &iWriteMarker )
  34. {
  35. //write to buffer until it's full, then write to file
  36. if( (iWriteMarker + iDataSize) > iBufferSize )
  37. {
  38. //too big for the buffer, flush the buffer and try again
  39. if( iWriteMarker != 0 )
  40. {
  41. fwrite( pBuffer, 1, iWriteMarker, pFile );
  42. iWriteMarker = 0;
  43. }
  44. if( iDataSize > iBufferSize )
  45. {
  46. //too big to ever hold in the buffer, write it now
  47. fwrite( pData, iDataSize, 1, pFile );
  48. return;
  49. }
  50. }
  51. memcpy( &pBuffer[iWriteMarker], pData, iDataSize );
  52. iWriteMarker += iDataSize;
  53. }
  54. struct CCallStackStatsGatherer_DumpHelperVars_t
  55. {
  56. uint8 *pWriteBuffer;
  57. size_t iWriteBufferSize;
  58. size_t *iWriteMarker;
  59. FILE *pFile;
  60. bool bAllowMemoryAllocations;
  61. };
  62. bool _CCallStackStatsGatherer_Internal_DumpSubTree( const CCallStackStatsGatherer_Standardized_t &StatsGatherer, void *pDumpHelpers )
  63. {
  64. size_t CapturedCallStackLength = 0;
  65. size_t iEntrySizeWithCallStack = 0;
  66. void *pEntries = NULL;
  67. size_t iEntryCount = 0;
  68. CCallStackStatsGatherer_Standardized_t *pSubTrees = NULL;
  69. size_t iSubTreeCount = 0;
  70. const char *szStructName = "";
  71. StatsGatherer.pFunctionTable->pfn_GetDumpInfo( StatsGatherer.pGatherer, szStructName, CapturedCallStackLength, iEntrySizeWithCallStack, pEntries, iEntryCount, pSubTrees, iSubTreeCount );
  72. if( iEntryCount == 0 )
  73. return false; //nothing to write
  74. CCallStackStatsGatherer_DumpHelperVars_t *pHelpers = (CCallStackStatsGatherer_DumpHelperVars_t *)pDumpHelpers;
  75. uint8 *pWriteBuffer = pHelpers->pWriteBuffer;
  76. size_t iWriteBufferSize = pHelpers->iWriteBufferSize;
  77. size_t &iWriteMarker = *pHelpers->iWriteMarker;
  78. FILE *pFile = pHelpers->pFile;
  79. //struct header
  80. {
  81. size_t iStructNameLength = strlen( szStructName ) + 1;
  82. BufferedFwrite( pFile, pWriteBuffer, iWriteBufferSize, szStructName, iStructNameLength, iWriteMarker );
  83. //number of sub-trees
  84. uint32 iSubTreeCount32 = (uint32)iSubTreeCount;
  85. BufferedFwrite( pFile, pWriteBuffer, iWriteBufferSize, &iSubTreeCount32, sizeof( uint32 ), iWriteMarker );
  86. //sub-tree translation addresses
  87. for( size_t i = 0; i != iSubTreeCount; ++i )
  88. {
  89. //sub-trees will be written out immediately after we finish dumping this gatherer, depth first recursion
  90. BufferedFwrite( pFile, pWriteBuffer, iWriteBufferSize, &pSubTrees[i].pGatherer, sizeof( void * ), iWriteMarker );
  91. }
  92. //size of call stack array
  93. BufferedFwrite( pFile, pWriteBuffer, iWriteBufferSize, &CapturedCallStackLength, sizeof( size_t ), iWriteMarker );
  94. //size of each entry, including call stack
  95. BufferedFwrite( pFile, pWriteBuffer, iWriteBufferSize, &iEntrySizeWithCallStack, sizeof( size_t ), iWriteMarker );
  96. //flush the write buffer
  97. if( iWriteMarker != 0 )
  98. {
  99. fwrite( pWriteBuffer, 1, iWriteMarker, pFile );
  100. iWriteMarker = 0;
  101. }
  102. //reserve space for writing out the description blob size since we don't know how big it is until after we write it.
  103. size_t iStructDescSizeWriteMarker = iWriteMarker;
  104. iWriteMarker += sizeof( uint32 );
  105. //describe how the structure should be interpreted on the receiving end
  106. iWriteMarker += StatsGatherer.pFunctionTable->pfn_DescribeCallStackStatStruct( pWriteBuffer + iWriteMarker, iWriteBufferSize - iWriteMarker );
  107. //write out the description size now
  108. *(uint32 *)(pWriteBuffer + iStructDescSizeWriteMarker) = (uint32)(iWriteMarker - (iStructDescSizeWriteMarker + sizeof( uint32 )));
  109. }
  110. size_t iInfoPos;
  111. //stats entries
  112. {
  113. //number of entries
  114. BufferedFwrite( pFile, pWriteBuffer, iWriteBufferSize, &iEntryCount, sizeof( size_t ), iWriteMarker );
  115. iInfoPos = iWriteMarker + ftell( pFile ); //where in the file we wrote out the stats entries. Need this in case we need to repurpose the stats memory for sorting later
  116. //write them all out
  117. BufferedFwrite( pFile, pWriteBuffer, iWriteBufferSize, pEntries, iEntrySizeWithCallStack * iEntryCount, iWriteMarker );
  118. }
  119. //flush any remnants of the write buffer. We're going to repurpose it for a bit
  120. if( iWriteMarker != 0 )
  121. {
  122. fwrite( pWriteBuffer, 1, iWriteMarker, pFile );
  123. iWriteMarker = 0;
  124. }
  125. //now sort the individual addresses, throwing away duplicates. We need memory for this operation, but we might be in a state where we can't allocate any...
  126. //So we'll start with the stack memory used for buffered writing first, then overflow into a memory allocation that should be able to hold it all if we're allowed,
  127. //but if that fails we're going to repurpose our stat tracking memory we already own and just wrote to file. Then read in what we overwrote after we're done.
  128. int iUniqueAddresses = 0;
  129. int iSortSize = (int)iWriteBufferSize / sizeof( void * );
  130. void **pSortedAddresses = (void **)pWriteBuffer;
  131. {
  132. uint8 *pEntryRead = (uint8 *)pEntries;
  133. for( uint32 i = 0; i != iEntryCount; ++i )
  134. {
  135. for( size_t j = 0; j != CapturedCallStackLength; ++j )
  136. {
  137. void *pInsertAddress = ((void **)pEntryRead)[j];
  138. if( pInsertAddress == NULL )
  139. break;
  140. //binary search
  141. int iHigh = iUniqueAddresses - 1;
  142. int iLow = 0;
  143. while( iHigh >= iLow )
  144. {
  145. int iMid = (iHigh + iLow) >> 1;
  146. if( pSortedAddresses[iMid] > pInsertAddress )
  147. {
  148. iHigh = iMid - 1;
  149. }
  150. else if( pSortedAddresses[iMid] < pInsertAddress )
  151. {
  152. iLow = iMid + 1;
  153. }
  154. else
  155. {
  156. //same address
  157. iLow = iMid;
  158. //iHigh = iLow - 1;
  159. break;
  160. }
  161. }
  162. if( iLow > iUniqueAddresses )
  163. {
  164. //tack it onto the end
  165. if( iUniqueAddresses >= iSortSize )
  166. {
  167. size_t maxSize = sizeof( void * ) * CapturedCallStackLength * iEntryCount;
  168. //crap, grew past the temp stack buffer, use real memory...
  169. void **pTemp = pHelpers->bAllowMemoryAllocations ? new void * [maxSize] : NULL;
  170. if( pTemp == NULL )
  171. {
  172. //double crap, memory wasn't available, overwrite our stat tracking data and read it back from file later...
  173. pTemp = (void **)pEntries;
  174. }
  175. memcpy( pTemp, pSortedAddresses, iUniqueAddresses * sizeof( void * ) );
  176. iSortSize = (int)maxSize;
  177. pSortedAddresses = pTemp;
  178. }
  179. pSortedAddresses[iLow] = pInsertAddress;
  180. ++iUniqueAddresses;
  181. }
  182. else if( pSortedAddresses[iLow] != pInsertAddress )
  183. {
  184. //insert it here
  185. if( iUniqueAddresses >= iSortSize )
  186. {
  187. size_t maxSize = sizeof( void * ) * CapturedCallStackLength * iEntryCount;
  188. //crap, grew past the temp stack buffer, use real memory...
  189. void **pTemp = pHelpers->bAllowMemoryAllocations ? new void * [maxSize] : NULL;
  190. if( pTemp == NULL )
  191. {
  192. //double crap, memory wasn't available, overwrite our stat tracking data and read it back from file later...
  193. pTemp = (void **)pEntries;
  194. }
  195. memcpy( pTemp, pSortedAddresses, iUniqueAddresses * sizeof( void * ) );
  196. iSortSize = (int)maxSize;
  197. pSortedAddresses = pTemp;
  198. }
  199. for( int k = iUniqueAddresses; --k >= iLow; )
  200. {
  201. pSortedAddresses[k + 1] = pSortedAddresses[k];
  202. }
  203. ++iUniqueAddresses;
  204. pSortedAddresses[iLow] = pInsertAddress;
  205. }
  206. //else do nothing, it's a duplicate
  207. #ifdef DBGFLAG_ASSERT
  208. else
  209. {
  210. Assert( pSortedAddresses[iLow] == pInsertAddress );
  211. }
  212. #endif
  213. }
  214. pEntryRead += iEntrySizeWithCallStack;
  215. }
  216. }
  217. //now that we have them sorted, see if we need to fudge with the memory we own a bit...
  218. if( (uint8 *)pSortedAddresses == pWriteBuffer )
  219. {
  220. size_t iUniqueAddressSpace = iUniqueAddresses * sizeof( void * );
  221. pWriteBuffer += iUniqueAddressSpace;
  222. iWriteBufferSize -= iUniqueAddressSpace;
  223. }
  224. //write the address to file/symbol/line translation table
  225. {
  226. Assert( iUniqueAddresses > 0 );
  227. BufferedFwrite( pFile, pWriteBuffer, iWriteBufferSize, &iUniqueAddresses, sizeof( int ), iWriteMarker );
  228. //now that they're sorted, we can assume that we have a basic grouping by symbol. And on top of that we can assume that a unique symbol is contained
  229. //in one file. But we're not going to use that information just yet...
  230. //for first version, we'll store all the info for every address. It's excessive but stable. A future file protocol revision should try recognize file/symbol duplicates. The problem is finding them without using mallocs()
  231. for( int i = 0; i != iUniqueAddresses; ++i )
  232. {
  233. BufferedFwrite( pFile, pWriteBuffer, iWriteBufferSize, &pSortedAddresses[i], sizeof( void * ), iWriteMarker );
  234. char szBuff[1024];
  235. if( !GetModuleNameFromAddress( pSortedAddresses[i], szBuff, sizeof( szBuff ) ) )
  236. {
  237. szBuff[0] = '\0';
  238. }
  239. BufferedFwrite( pFile, pWriteBuffer, iWriteBufferSize, szBuff, strlen( szBuff ) + 1, iWriteMarker );
  240. if( !GetSymbolNameFromAddress( pSortedAddresses[i], szBuff, sizeof( szBuff ) ) )
  241. {
  242. szBuff[0] = '\0';
  243. }
  244. BufferedFwrite( pFile, pWriteBuffer, iWriteBufferSize, szBuff, strlen( szBuff ) + 1, iWriteMarker );
  245. uint32 iLine;
  246. if( !GetFileAndLineFromAddress( pSortedAddresses[i], szBuff, sizeof( szBuff ), iLine ) )
  247. {
  248. szBuff[0] = '\0';
  249. iLine = 0;
  250. }
  251. BufferedFwrite( pFile, pWriteBuffer, iWriteBufferSize, szBuff, strlen( szBuff ) + 1, iWriteMarker );
  252. BufferedFwrite( pFile, pWriteBuffer, iWriteBufferSize, &iLine, sizeof( uint32 ), iWriteMarker );
  253. }
  254. }
  255. uint32 iSubToolDataSize = 0; //there's generally a lot of data to process in this file, save some space for any tools that want to save data back to the file in a lump format
  256. BufferedFwrite( pFile, pWriteBuffer, iWriteBufferSize, &iSubToolDataSize, sizeof( uint32 ), iWriteMarker );
  257. //flush any remnants of the write buffer
  258. if( iWriteMarker != 0 )
  259. {
  260. fwrite( pWriteBuffer, 1, iWriteMarker, pFile );
  261. iWriteMarker = 0;
  262. }
  263. pWriteBuffer = pHelpers->pWriteBuffer;
  264. iWriteBufferSize = pHelpers->iWriteBufferSize;
  265. if( pSortedAddresses == (void **)pEntries )
  266. {
  267. // We trashed the data after we wrote it. We needed the memory for sorting unique addresses. Fix it by reading what we overwrote back into place
  268. fseek( pFile, (long)iInfoPos, SEEK_SET );
  269. fread( pEntries, sizeof( void * ) * iUniqueAddresses, 1, pFile ); //we overwrote iUniqueAddresses worth of void pointers, so we just need to fix that chunk
  270. fseek( pFile, 0, SEEK_END );
  271. }
  272. else if( (uint8 *)pSortedAddresses != pWriteBuffer )
  273. {
  274. //we overflowed the temp buffer, but got away with an allocation. Free it
  275. delete []pSortedAddresses;
  276. }
  277. //dump sub-trees, depth first
  278. for( size_t i = 0; i != iSubTreeCount; ++i )
  279. {
  280. bool bRet = _CCallStackStatsGatherer_Internal_DumpSubTree( pSubTrees[i], pDumpHelpers );
  281. if( bRet == false )
  282. {
  283. return false;
  284. }
  285. }
  286. return true;
  287. }
  288. size_t _CCallStackStatsGatherer_Write_FieldDescriptions( CallStackStatStructDescFuncs *pFieldDescriptions, uint8 *pWriteBuffer, size_t iWriteBufferSize )
  289. {
  290. size_t iWriteMarker = 0;
  291. //lump ID
  292. *(uint32 *)(pWriteBuffer + iWriteMarker) = (uint32)SSDLID_FIELDDESC;
  293. iWriteMarker += sizeof( uint32 );
  294. //lump size, currently unknown, so just save a spot to write it later
  295. size_t iLumpSizeMarker = iWriteMarker;
  296. iWriteMarker += sizeof( uint32 );
  297. //number of fields described, currently unknown, so just save a spot to write it later
  298. size_t iNumFieldsWriteMarker = iWriteMarker;
  299. iWriteMarker += sizeof( uint32 );
  300. uint32 iNumFields = 0;
  301. //describe the structure in the file
  302. CallStackStatStructDescFuncs *pDesc = pFieldDescriptions;
  303. while( pDesc != NULL )
  304. {
  305. size_t iFieldWrote = pDesc->DescribeField( pWriteBuffer + iWriteMarker, iWriteBufferSize - iWriteMarker );
  306. if( iFieldWrote != 0 )
  307. {
  308. ++iNumFields;
  309. }
  310. iWriteMarker += iFieldWrote;
  311. pDesc = pDesc->m_pNext;
  312. }
  313. *(uint32 *)(pWriteBuffer + iNumFieldsWriteMarker) = iNumFields;
  314. *(uint32 *)(pWriteBuffer + iLumpSizeMarker) = (uint32)(iWriteMarker - (iLumpSizeMarker + sizeof( uint32 )));
  315. return iWriteMarker;
  316. }
  317. #if 0 //embedded script handling not ready yet
  318. PLATFORM_INTERFACE size_t _CCallStackStatsGatherer_Write_FieldMergeScript( CallStackStatStructDescFuncs *pFieldDescriptions, CallStackStatStructDescFuncs::MergeScript_Language scriptMergeLanguage, uint8 *pWriteBuffer, size_t iWriteBufferSize )
  319. {
  320. Assert( scriptMergeLanguage == SSMSL_Squirrel ); //all we support so far
  321. size_t iWriteMarker = 0;
  322. *(uint32 *)(pWriteBuffer + iWriteMarker) = (uint32)SSDLID_EMBEDDEDSCRIPT; //at some point this should move to a more global location, for now we only support exporting the merge function and no other script code
  323. iWriteMarker += sizeof( uint32 );
  324. //lump size, currently unknown, so just save a spot to write it later
  325. size_t iLumpSizeMarker = iWriteMarker;
  326. iWriteMarker += sizeof( uint32 );
  327. *(uint8 *)(pWriteBuffer + iWriteMarker) = (uint8)scriptMergeLanguage;
  328. iWriteMarker += sizeof( uint8 );
  329. //write out squirrel script
  330. iWriteMarker += _snprintf( (char *)pWriteBuffer + iWriteMarker, iWriteBufferSize - iWriteMarker, "function MergeStructs( mergeTo, mergeFrom )\n{\n" );
  331. CallStackStatStructDescFuncs *pDesc = pFieldDescriptions;
  332. while( pDesc != NULL )
  333. {
  334. iWriteMarker += pDesc->DescribeMergeOperation( scriptMergeLanguage, pWriteBuffer + iWriteMarker, iWriteBufferSize - iWriteMarker );
  335. if( iWriteMarker < (iWriteBufferSize - 2) )
  336. {
  337. pWriteBuffer[iWriteMarker] = '\n';
  338. ++iWriteMarker;
  339. pWriteBuffer[iWriteMarker] = '\0';
  340. }
  341. pDesc = pDesc->m_pNext;
  342. }
  343. iWriteMarker += _snprintf( (char *)pWriteBuffer + iWriteMarker, iWriteBufferSize - iWriteMarker, "}\n" ) + 1;
  344. *(uint32 *)(pWriteBuffer + iLumpSizeMarker) = (uint32)(iWriteMarker - (iLumpSizeMarker + sizeof( uint32 )));
  345. return iWriteMarker;
  346. }
  347. #endif
  348. #if 0 //embedded script handling not ready yet
  349. size_t BasicStatStructFieldDesc::DescribeMergeOperation( MergeScript_Language scriptLanguage, uint8 *pDescribeWriteBuffer, size_t iDescribeMaxLength )
  350. {
  351. Assert( scriptMergeLanguage == SSMSL_Squirrel ); //all we support so far
  352. switch( m_Combine )
  353. {
  354. case BSSFCM_ADD:
  355. {
  356. return _snprintf( (char *)pDescribeWriteBuffer, iDescribeMaxLength, "mergeTo.%s += mergeFrom.%s\n", m_szFieldName, m_szFieldName );
  357. break;
  358. }
  359. case BSSFCM_MAX:
  360. {
  361. return _snprintf( (char *)pDescribeWriteBuffer, iDescribeMaxLength, "if( mergeTo.%s < mergeFrom.%s ) mergeTo.%s = mergeFrom.%s\n", m_szFieldName, m_szFieldName, m_szFieldName, m_szFieldName );
  362. break;
  363. }
  364. case BSSFCM_MIN:
  365. {
  366. return _snprintf( (char *)pDescribeWriteBuffer, iDescribeMaxLength, "if( mergeTo.%s > mergeFrom.%s ) mergeTo.%s = mergeFrom.%s\n", m_szFieldName, m_szFieldName, m_szFieldName, m_szFieldName );
  367. break;
  368. }
  369. /*case BSSFCM_AND:
  370. {
  371. break;
  372. }
  373. case BSSFCM_OR:
  374. {
  375. break;
  376. }
  377. case BSSFCM_XOR:
  378. {
  379. break;
  380. }
  381. case BSSFCM_LIST: //add the values
  382. {
  383. Error( "BSSFCM_LIST is currently unsupported" );
  384. break;
  385. }*/
  386. };
  387. return 0;
  388. }
  389. #endif
  390. bool _CCallStackStatsGatherer_Internal_DumpStatsToFile( const char *szFileName, const CCallStackStatsGatherer_Standardized_t &StatsGatherer, bool bAllowMemoryAllocations )
  391. {
  392. if( !StatsGatherer.pGatherer )
  393. return false;
  394. FILE *pFile = fopen(szFileName, "wb");
  395. if (!pFile)
  396. return false;
  397. uint8 tempBuffer[256 * 1024]; //256kb
  398. size_t iWriteMarker = 0;
  399. //File Header
  400. {
  401. //file format version
  402. uint8 version = 3;
  403. BufferedFwrite( pFile, tempBuffer, sizeof( tempBuffer ), &version, sizeof( uint8 ), iWriteMarker );
  404. uint32 iEndian = 0x12345678;
  405. BufferedFwrite( pFile, tempBuffer, sizeof( tempBuffer ), &iEndian, sizeof( uint32 ), iWriteMarker );
  406. }
  407. CCallStackStatsGatherer_DumpHelperVars_t helperVars;
  408. helperVars.pWriteBuffer = tempBuffer;
  409. helperVars.iWriteBufferSize = sizeof( tempBuffer );
  410. helperVars.iWriteMarker = &iWriteMarker;
  411. helperVars.pFile = pFile;
  412. helperVars.bAllowMemoryAllocations = bAllowMemoryAllocations;
  413. bool bRetVal = _CCallStackStatsGatherer_Internal_DumpSubTree( StatsGatherer, &helperVars );
  414. //flush any remnants of the write buffer
  415. if( iWriteMarker != 0 )
  416. {
  417. fwrite( tempBuffer, 1, iWriteMarker, pFile );
  418. iWriteMarker = 0;
  419. }
  420. fclose( pFile );
  421. return bRetVal;
  422. }
  423. #endif //#if !defined( ENABLE_RUNTIME_STACK_TRANSLATION )
  424. size_t BasicStatStructFieldDesc::DescribeField( uint8 *pDescribeWriteBuffer, size_t iDescribeMaxLength )
  425. {
  426. #if defined( ENABLE_RUNTIME_STACK_TRANSLATION )
  427. //description file format
  428. //1 byte version
  429. //1 byte field type
  430. //1 byte combine method
  431. //4 byte field offset
  432. //null terminated string field name
  433. size_t iFieldNameStrLen = strlen( m_szFieldName ) + 1;
  434. const size_t iOutputLength = sizeof( uint8 ) + sizeof( uint8 ) + sizeof( uint8 ) + sizeof( uint32 ) + iFieldNameStrLen;
  435. if( iDescribeMaxLength < iOutputLength )
  436. return 0;
  437. size_t iWriteOffset = 0;
  438. //entry version
  439. *reinterpret_cast<uint8 *>(pDescribeWriteBuffer + iWriteOffset) = static_cast<uint8>(DFV_BasicStatStructFieldTypes_t);
  440. iWriteOffset += sizeof( uint8 );
  441. //field type
  442. *reinterpret_cast<uint8 *>(pDescribeWriteBuffer + iWriteOffset) = static_cast<uint8>(m_Type);
  443. iWriteOffset += sizeof( uint8 );
  444. //combine method
  445. *reinterpret_cast<uint8 *>(pDescribeWriteBuffer + iWriteOffset) = static_cast<uint8>(m_Combine);
  446. iWriteOffset += sizeof( uint8 );
  447. //field offset
  448. *reinterpret_cast<uint32 *>(pDescribeWriteBuffer + iWriteOffset) = static_cast<uint32>(m_iFieldOffset);
  449. iWriteOffset += sizeof( uint32 );
  450. //field name
  451. memcpy( pDescribeWriteBuffer + iWriteOffset, m_szFieldName, iFieldNameStrLen );
  452. iWriteOffset += iFieldNameStrLen;
  453. Assert( iWriteOffset == iOutputLength );
  454. return iWriteOffset;
  455. #else
  456. return 0;
  457. #endif
  458. }