Source code of Windows XP (NT5)
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.

1013 lines
24 KiB

  1. /*++
  2. Copyright (c) 1998 Microsoft Corporation
  3. Module Name:
  4. ohcmp.cpp
  5. Abstract:
  6. This module reports the differences between two oh output files.
  7. Author:
  8. Matt Bandy (t-mattba) 23-Jul-1998
  9. Revision History:
  10. 24-Jul-1998 t-mattba
  11. Modified module to conform to coding standards.
  12. 11-Jun-2001 silviuc
  13. Deal with handles that are recreated with a different value
  14. and other simple output improvements (sorted output etc.).
  15. --*/
  16. #include <windows.h>
  17. #include <common.ver>
  18. #include <stdio.h>
  19. #include <string.h>
  20. #include <stdlib.h>
  21. #include <tchar.h>
  22. #include "MAPSTRINGINT.h"
  23. LPTSTR HelpText =
  24. TEXT("ohcmp - Display difference between two OH output files --") BUILD_MACHINE_TAG TEXT("\n")
  25. VER_LEGALCOPYRIGHT_STR TEXT("\n")
  26. TEXT(" \n")
  27. TEXT("ohcmp [OPTION ...] BEFORE_OH_FILE AFTER_OH_FILE \n")
  28. TEXT(" \n")
  29. TEXT("/h Print most interesting increases in a separate initial section. \n")
  30. TEXT("/t Do not add TRACE id to the names if files contain traces. \n")
  31. TEXT("/all Report decreases as well as increases. \n")
  32. TEXT(" \n")
  33. TEXT("If the OH files have been created with -h option (they contain traces) \n")
  34. TEXT("then ohcmp will print Names having this syntax: (TRACEID) NAME. \n")
  35. TEXT("In case of a potential leak just search for the TRACEID in the original\n")
  36. TEXT("OH file to find the stack trace. \n")
  37. TEXT(" \n");
  38. LPTSTR
  39. SearchStackTrace (
  40. LPTSTR FileName,
  41. LPTSTR TraceId
  42. );
  43. PSTRINGTOINTASSOCIATION
  44. MAPSTRINGTOINT::GetStartPosition(
  45. VOID
  46. )
  47. /*++
  48. Routine Description:
  49. This routine retrieves the first association in the list for iteration with the
  50. MAPSTRINGTOINT::GetNextAssociation function.
  51. Arguments:
  52. None.
  53. Return value:
  54. The first association in the list, or NULL if the map is empty.
  55. --*/
  56. {
  57. return Associations;
  58. }
  59. VOID
  60. MAPSTRINGTOINT::GetNextAssociation(
  61. IN OUT PSTRINGTOINTASSOCIATION & Position,
  62. OUT LPTSTR & Key,
  63. OUT LONG & Value)
  64. /*++
  65. Routine Description:
  66. This routine retrieves the data for the current association and sets Position to
  67. point to the next association (or NULL if this is the last association.)
  68. Arguments:
  69. Position - Supplies the current association and returns the next association.
  70. Key - Returns the key for the current association.
  71. Value - Returns the value for the current association.
  72. Return value:
  73. None.
  74. --*/
  75. {
  76. Key = Position->Key;
  77. Value = Position->Value;
  78. Position = Position->Next;
  79. }
  80. MAPSTRINGTOINT::MAPSTRINGTOINT(
  81. )
  82. /*++
  83. Routine Description:
  84. This routine initializes a MAPSTRINGTOINT to be empty.
  85. Arguments:
  86. None.
  87. Return value:
  88. None.
  89. --*/
  90. {
  91. Associations = NULL;
  92. }
  93. MAPSTRINGTOINT::~MAPSTRINGTOINT(
  94. )
  95. /*++
  96. Routine Description:
  97. This routine cleans up memory used by a MAPSTRINGTOINT.
  98. Arguments:
  99. None.
  100. Return value:
  101. None.
  102. --*/
  103. {
  104. PSTRINGTOINTASSOCIATION Deleting;
  105. // clean up associations
  106. while (Associations != NULL) {
  107. // save pointer to first association
  108. Deleting = Associations;
  109. // remove first association from list
  110. Associations = Deleting->Next;
  111. // free removed association
  112. free (Deleting->Key);
  113. delete Deleting;
  114. }
  115. }
  116. LONG &
  117. MAPSTRINGTOINT::operator [] (
  118. IN LPTSTR Key
  119. )
  120. /*++
  121. Routine Description:
  122. This routine retrieves an l-value for the value associated with a given key.
  123. Arguments:
  124. Key - The key for which the value is to be retrieved.
  125. Return value:
  126. A reference to the value associated with the provided key.
  127. --*/
  128. {
  129. PSTRINGTOINTASSOCIATION CurrentAssociation = Associations;
  130. // search for key
  131. while(CurrentAssociation != NULL) {
  132. if(!_tcscmp(CurrentAssociation->Key, Key)) {
  133. // found key, return value
  134. return CurrentAssociation->Value;
  135. }
  136. CurrentAssociation = CurrentAssociation->Next;
  137. }
  138. // not found, create new association
  139. CurrentAssociation = new STRINGTOINTASSOCIATION;
  140. if (CurrentAssociation == NULL) {
  141. _tprintf(_T("Memory allocation failure\n"));
  142. exit (0);
  143. }
  144. if (Key == NULL) {
  145. _tprintf(_T("Null object name\n"));
  146. exit (0);
  147. }
  148. else if (_tcscmp (Key, "") == 0) {
  149. _tprintf(_T("Invalid object name `%s'\n"), Key);
  150. exit (0);
  151. }
  152. CurrentAssociation->Key = _tcsdup(Key);
  153. if (CurrentAssociation->Key == NULL) {
  154. _tprintf(_T("Memory string allocation failure\n"));
  155. exit (0);
  156. }
  157. // add association to front of list
  158. CurrentAssociation->Next = Associations;
  159. Associations = CurrentAssociation;
  160. // return value for new association
  161. return CurrentAssociation->Value;
  162. }
  163. BOOLEAN
  164. MAPSTRINGTOINT::Lookup(
  165. IN LPTSTR Key,
  166. OUT LONG & Value
  167. )
  168. /*++
  169. Routine Description:
  170. This routine retrieves an r-value for the value association with a given key.
  171. Arguments:
  172. Key - The key for which the associated value is to be retrieved.
  173. Value - Returns the value associated with Key if Key is present in the map.
  174. Return value:
  175. TRUE if the key is present in the map, FALSE otherwise.
  176. --*/
  177. {
  178. PSTRINGTOINTASSOCIATION CurrentAssociation = Associations;
  179. // search for key
  180. while (CurrentAssociation != NULL) {
  181. if(!_tcscmp(CurrentAssociation->Key , Key)) {
  182. // found key, return it
  183. Value = CurrentAssociation->Value;
  184. return TRUE;
  185. }
  186. CurrentAssociation = CurrentAssociation->Next;
  187. }
  188. // didn't find it
  189. return FALSE;
  190. }
  191. BOOLEAN
  192. PopulateMapsFromFile(
  193. IN LPTSTR FileName,
  194. OUT MAPSTRINGTOINT & TypeMap,
  195. OUT MAPSTRINGTOINT & NameMap,
  196. BOOLEAN FileWithTraces
  197. )
  198. /*++
  199. Routine Description:
  200. This routine parses an OH output file and fills two maps with the number of handles of
  201. each type and the number of handles to each named object.
  202. Arguments:
  203. FileName - OH output file to parse.
  204. TypeMap - Map to fill with handle type information.
  205. NameMap - Map to fill with named object information.
  206. Return value:
  207. TRUE if the file was successfully parsed, FALSE otherwise.
  208. --*/
  209. {
  210. LONG HowMany;
  211. LPTSTR Name, Type, Process, Pid;
  212. LPTSTR NewLine;
  213. TCHAR LineBuffer[512];
  214. TCHAR ObjectName[512];
  215. TCHAR TypeName[512];
  216. FILE *InputFile;
  217. ULONG LineNumber;
  218. BOOLEAN rc;
  219. LineNumber = 0;
  220. // open file
  221. InputFile = _tfopen(FileName, _T("rt"));
  222. if (InputFile == NULL) {
  223. _ftprintf(stderr, _T("Error opening oh file %s.\n"), FileName);
  224. return FALSE;
  225. }
  226. rc = TRUE;
  227. // loop through lines in oh output
  228. while (_fgetts(LineBuffer, sizeof(LineBuffer), InputFile)
  229. && !( feof(InputFile) || ferror(InputFile) ) ) {
  230. LineNumber += 1;
  231. // trim off newline
  232. if((NewLine = _tcschr(LineBuffer, _T('\n'))) != NULL) {
  233. *NewLine = _T('\0');
  234. }
  235. // ignore lines that start with white space or are empty.
  236. if (LineBuffer[0] == _T('\0') ||
  237. LineBuffer[0] == _T('\t') ||
  238. LineBuffer[0] == _T(' ')) {
  239. continue;
  240. }
  241. // ignore lines that start with a comment
  242. if( LineBuffer[0] == _T('/') && LineBuffer[1] == _T('/') ) {
  243. continue;
  244. }
  245. // skip pid
  246. if((Pid = _tcstok(LineBuffer, _T(" \t"))) == NULL) {
  247. rc = FALSE;
  248. break;
  249. }
  250. // skip process name
  251. if((Process = _tcstok(NULL, _T(" \t"))) == NULL) {
  252. rc = FALSE;
  253. break;
  254. }
  255. // Type points to the type of handle
  256. if ((Type = _tcstok(NULL, _T(" \t"))) == NULL) {
  257. rc = FALSE;
  258. break;
  259. }
  260. // HowMany = number of previous handles with this type
  261. _stprintf (TypeName,
  262. TEXT("<%s/%s/%s>"),
  263. Process,
  264. Pid,
  265. Type);
  266. if (TypeMap.Lookup(TypeName, HowMany) == FALSE) {
  267. HowMany = 0;
  268. }
  269. // add another handle of this type
  270. TypeMap[TypeName] = (HowMany + 1);
  271. //
  272. // Name points to the name. These are magic numbers based on the way
  273. // OH formats output. The output is a little bit different if the
  274. // `-h' option of OH was used (this dumps stack traces too).
  275. //
  276. Name = LineBuffer + 39 + 5;
  277. if (FileWithTraces) {
  278. Name += 7;
  279. }
  280. if (_tcscmp (Name, "") == 0) {
  281. _stprintf (ObjectName,
  282. TEXT("<%s/%s/%s>::<<noname>>"),
  283. Process,
  284. Pid,
  285. Type);
  286. }
  287. else {
  288. _stprintf (ObjectName,
  289. TEXT("<%s/%s/%s>::%s"),
  290. Process,
  291. Pid,
  292. Type,
  293. Name);
  294. }
  295. // HowMany = number of previous handles with this name
  296. // printf("name --> `%s' \n", ObjectName);
  297. if (NameMap.Lookup(ObjectName, HowMany) == FALSE) {
  298. HowMany = 0;
  299. }
  300. // add another handle with this name and read the next line
  301. // note -- NameMap[] is a class operator, not an array.
  302. NameMap[ObjectName] = (HowMany + 1);
  303. }
  304. // done, close file
  305. fclose(InputFile);
  306. return rc;
  307. }
  308. int
  309. __cdecl
  310. KeyCompareAssociation (
  311. const void * Left,
  312. const void * Right
  313. )
  314. {
  315. PSTRINGTOINTASSOCIATION X;
  316. PSTRINGTOINTASSOCIATION Y;
  317. X = (PSTRINGTOINTASSOCIATION)Left;
  318. Y = (PSTRINGTOINTASSOCIATION)Right;
  319. return _tcscmp (X->Key, Y->Key);
  320. }
  321. int
  322. __cdecl
  323. ValueCompareAssociation (
  324. const void * Left,
  325. const void * Right
  326. )
  327. {
  328. PSTRINGTOINTASSOCIATION X;
  329. PSTRINGTOINTASSOCIATION Y;
  330. X = (PSTRINGTOINTASSOCIATION)Left;
  331. Y = (PSTRINGTOINTASSOCIATION)Right;
  332. return Y->Value - X->Value;
  333. }
  334. VOID
  335. PrintIncreases(
  336. IN MAPSTRINGTOINT & BeforeMap,
  337. IN MAPSTRINGTOINT & AfterMap,
  338. IN BOOLEAN ReportIncreasesOnly,
  339. IN BOOLEAN PrintHighlights,
  340. IN LPTSTR AfterLogName
  341. )
  342. /*++
  343. Routine Description:
  344. This routine compares two maps and prints out the differences between them.
  345. Arguments:
  346. BeforeMap - First map to compare.
  347. AfterMap - Second map to compare.
  348. ReportIncreasesOnly - TRUE for report only increases from BeforeMap to AfterMap,
  349. FALSE for report all differences.
  350. Return value:
  351. None.
  352. --*/
  353. {
  354. PSTRINGTOINTASSOCIATION Association = NULL;
  355. LONG HowManyBefore = 0;
  356. LONG HowManyAfter = 0;
  357. LPTSTR Key = NULL;
  358. PSTRINGTOINTASSOCIATION SortBuffer;
  359. ULONG SortBufferSize;
  360. ULONG SortBufferIndex;
  361. //
  362. // Loop through associations in map and figure out how many output lines
  363. // we will have.
  364. //
  365. SortBufferSize = 0;
  366. for (Association = AfterMap.GetStartPosition(),
  367. AfterMap.GetNextAssociation(Association, Key, HowManyAfter);
  368. Association != NULL;
  369. AfterMap.GetNextAssociation(Association, Key, HowManyAfter)) {
  370. // look up value for this key in BeforeMap
  371. if(BeforeMap.Lookup(Key, HowManyBefore) == FALSE) {
  372. HowManyBefore = 0;
  373. }
  374. // should we report this?
  375. if((HowManyAfter > HowManyBefore) ||
  376. ((!ReportIncreasesOnly) && (HowManyAfter != HowManyBefore))) {
  377. SortBufferSize += 1;
  378. }
  379. }
  380. //
  381. // Loop through associations in map again this time filling the output buffer.
  382. //
  383. SortBufferIndex = 0;
  384. SortBuffer = new STRINGTOINTASSOCIATION[SortBufferSize];
  385. if (SortBuffer == NULL) {
  386. _ftprintf(stderr, _T("Failed to allocate internal buffer of %u bytes.\n"),
  387. SortBufferSize);
  388. return;
  389. }
  390. for (Association = AfterMap.GetStartPosition(),
  391. AfterMap.GetNextAssociation(Association, Key, HowManyAfter);
  392. Association != NULL;
  393. AfterMap.GetNextAssociation(Association, Key, HowManyAfter)) {
  394. // look up value for this key in BeforeMap
  395. if(BeforeMap.Lookup(Key, HowManyBefore) == FALSE) {
  396. HowManyBefore = 0;
  397. }
  398. // should we report this?
  399. if((HowManyAfter > HowManyBefore) ||
  400. ((!ReportIncreasesOnly) && (HowManyAfter != HowManyBefore))) {
  401. ZeroMemory (&(SortBuffer[SortBufferIndex]),
  402. sizeof (STRINGTOINTASSOCIATION));
  403. SortBuffer[SortBufferIndex].Key = Key;
  404. SortBuffer[SortBufferIndex].Value = HowManyAfter - HowManyBefore;
  405. SortBufferIndex += 1;
  406. }
  407. }
  408. //
  409. // Sort the output buffer using the Key.
  410. //
  411. if (PrintHighlights) {
  412. qsort (SortBuffer,
  413. SortBufferSize,
  414. sizeof (STRINGTOINTASSOCIATION),
  415. ValueCompareAssociation);
  416. }
  417. else {
  418. qsort (SortBuffer,
  419. SortBufferSize,
  420. sizeof (STRINGTOINTASSOCIATION),
  421. KeyCompareAssociation);
  422. }
  423. //
  424. // Dump the buffer.
  425. //
  426. for (SortBufferIndex = 0; SortBufferIndex < SortBufferSize; SortBufferIndex += 1) {
  427. if (PrintHighlights) {
  428. if (SortBuffer[SortBufferIndex].Value >= 1) {
  429. TCHAR TraceId[7];
  430. LPTSTR Start;
  431. _tprintf(_T("%d\t%s\n"),
  432. SortBuffer[SortBufferIndex].Value,
  433. SortBuffer[SortBufferIndex].Key);
  434. Start = _tcsstr (SortBuffer[SortBufferIndex].Key, "(");
  435. if (Start == NULL) {
  436. TraceId[0] = 0;
  437. }
  438. else {
  439. _tcsncpy (TraceId,
  440. Start,
  441. 6);
  442. TraceId[6] = 0;
  443. }
  444. _tprintf (_T("%s"), SearchStackTrace (AfterLogName, TraceId));
  445. }
  446. }
  447. else {
  448. _tprintf(_T("%d\t%s\n"),
  449. SortBuffer[SortBufferIndex].Value,
  450. SortBuffer[SortBufferIndex].Key);
  451. }
  452. }
  453. //
  454. // Clean up memory.
  455. //
  456. if (SortBuffer) {
  457. delete[] SortBuffer;
  458. }
  459. }
  460. VOID
  461. PrintUsage(
  462. VOID
  463. )
  464. /*++
  465. Routine Description:
  466. This routine prints out a message describing the proper usage of OHCMP.
  467. Arguments:
  468. None.
  469. Return value:
  470. None.
  471. --*/
  472. {
  473. _ftprintf (stderr, HelpText);
  474. }
  475. LONG _cdecl
  476. _tmain(
  477. IN LONG argc,
  478. IN LPTSTR argv[]
  479. )
  480. /*++
  481. Routine Description:
  482. This routine parses program arguments, reads the two input files, and prints out the
  483. differences.
  484. Arguments:
  485. argc - Number of command-line arguments.
  486. argv - Command-line arguments.
  487. Return value:
  488. 0 if comparison is successful, 1 otherwise.
  489. --*/
  490. {
  491. try {
  492. MAPSTRINGTOINT TypeMapBefore, TypeMapAfter;
  493. MAPSTRINGTOINT NameMapBefore, NameMapAfter;
  494. LPTSTR BeforeFileName=NULL;
  495. LPTSTR AfterFileName=NULL;
  496. BOOLEAN ReportIncreasesOnly = TRUE;
  497. BOOLEAN Interpreted = FALSE;
  498. BOOLEAN Result;
  499. BOOLEAN FileWithTraces;
  500. BOOLEAN PrintHighlights;
  501. // parse arguments
  502. FileWithTraces = FALSE;
  503. PrintHighlights = FALSE;
  504. for (LONG n = 1; n < argc; n++) {
  505. Interpreted = FALSE;
  506. switch(argv[n][0]) {
  507. case _T('-'):
  508. case _T('/'):
  509. // the argument is a switch
  510. if(_tcsicmp(argv[n]+1, _T("all")) == 0) {
  511. ReportIncreasesOnly = FALSE;
  512. Interpreted = TRUE;
  513. }
  514. else if (_tcsicmp(argv[n]+1, _T("t")) == 0) {
  515. FileWithTraces = TRUE;
  516. Interpreted = TRUE;
  517. }
  518. else if (_tcsicmp(argv[n]+1, _T("h")) == 0) {
  519. PrintHighlights = TRUE;
  520. Interpreted = TRUE;
  521. }
  522. break;
  523. default:
  524. // the argument is a file name
  525. if(BeforeFileName == NULL) {
  526. BeforeFileName = argv[n];
  527. Interpreted = TRUE;
  528. } else {
  529. if(AfterFileName == NULL) {
  530. AfterFileName = argv[n];
  531. Interpreted = TRUE;
  532. } else {
  533. // too many file arguments
  534. PrintUsage();
  535. return 1;
  536. }
  537. }
  538. break;
  539. }
  540. if(!Interpreted) {
  541. // user specified a bad argument
  542. PrintUsage();
  543. return 1;
  544. }
  545. }
  546. // did user specify required arguments?
  547. if((BeforeFileName == NULL) || (AfterFileName == NULL))
  548. {
  549. PrintUsage();
  550. return 1;
  551. }
  552. // read oh1 file
  553. Result = PopulateMapsFromFile (BeforeFileName,
  554. TypeMapBefore,
  555. NameMapBefore,
  556. FileWithTraces);
  557. if(Result == FALSE) {
  558. _ftprintf(stderr, _T("Failed to read first OH output file.\n"));
  559. return 1;
  560. }
  561. // read oh2 file
  562. Result = PopulateMapsFromFile (AfterFileName,
  563. TypeMapAfter,
  564. NameMapAfter,
  565. FileWithTraces);
  566. if(Result == FALSE) {
  567. _ftprintf(stderr, _T("Failed to read second OH output file.\n"));
  568. return 1;
  569. }
  570. // print out increases by handle name
  571. if (PrintHighlights) {
  572. _putts (TEXT ("\n")
  573. TEXT("// \n")
  574. TEXT("// Possible leaks (DELTA <PROCESS/PID/TYPE>::NAME): \n")
  575. TEXT("// \n")
  576. TEXT("// Note that the NAME can appear as `(TRACEID) NAME' if output \n")
  577. TEXT("// is generated by comparing OH files containing traces. In this case \n")
  578. TEXT("// just search in the `AFTER' OH log file for the trace id to \n")
  579. TEXT("// find the stack trace creating the handle possibly leaked. \n")
  580. TEXT("// \n\n"));
  581. PrintIncreases (NameMapBefore,
  582. NameMapAfter,
  583. ReportIncreasesOnly,
  584. TRUE,
  585. AfterFileName);
  586. }
  587. // print out increases by handle type
  588. _putts (TEXT ("\n")
  589. TEXT("// \n")
  590. TEXT("// Handle types (DELTA <PROCESS/PID/TYPE>): \n")
  591. TEXT("// \n")
  592. TEXT("// DELTA is the additional number of handles found in the `AFTER' log. \n")
  593. TEXT("// PROCESS is the process name having a handle increase. \n")
  594. TEXT("// PID is the process PID having a handle increase. \n")
  595. TEXT("// TYPE is the type of the handle \n")
  596. TEXT("// \n\n"));
  597. PrintIncreases (TypeMapBefore,
  598. TypeMapAfter,
  599. ReportIncreasesOnly,
  600. FALSE,
  601. NULL);
  602. // print out increases by handle name
  603. _putts (TEXT ("\n")
  604. TEXT("// \n")
  605. TEXT("// Objects (named and anonymous) (DELTA <PROCESS/PID/TYPE>::NAME): \n")
  606. TEXT("// \n")
  607. TEXT("// DELTA is the additional number of handles found in the `AFTER' log. \n")
  608. TEXT("// PROCESS is the process name having a handle increase. \n")
  609. TEXT("// PID is the process PID having a handle increase. \n")
  610. TEXT("// TYPE is the type of the handle \n")
  611. TEXT("// NAME is the name of the handle. Anonymous handles appear with name <<noname>>.\n")
  612. TEXT("// \n")
  613. TEXT("// Note that the NAME can appear as `(TRACEID) NAME' if output \n")
  614. TEXT("// is generated by comparing OH files containing traces. In this case \n")
  615. TEXT("// just search in the `AFTER' OH log file for the trace id to \n")
  616. TEXT("// find the stack trace creating the handle possibly leaked. \n")
  617. TEXT("// \n\n"));
  618. PrintIncreases (NameMapBefore,
  619. NameMapAfter,
  620. ReportIncreasesOnly,
  621. FALSE,
  622. NULL);
  623. return 0;
  624. } catch (...) {
  625. // this is mostly intended to catch out of memory conditions
  626. _tprintf(_T("\nAn exception has been detected. OHCMP aborted.\n"));
  627. return 1;
  628. }
  629. }
  630. /////////////////////////////////////////////////////////////////////
  631. /////////////////////////////////////////////////////////////////////
  632. /////////////////////////////////////////////////////////////////////
  633. TCHAR StackTraceBuffer [0x10000];
  634. LPTSTR
  635. SearchStackTrace (
  636. LPTSTR FileName,
  637. LPTSTR TraceId
  638. )
  639. {
  640. TCHAR LineBuffer[512];
  641. FILE *InputFile;
  642. StackTraceBuffer[0] = 0;
  643. //
  644. // Open file.
  645. //
  646. InputFile = _tfopen(FileName, _T("rt"));
  647. if (InputFile == NULL) {
  648. _ftprintf(stderr, _T("Error opening oh file %s.\n"), FileName);
  649. return NULL;
  650. }
  651. //
  652. // Loop through lines in oh output.
  653. //
  654. while (_fgetts(LineBuffer, sizeof(LineBuffer), InputFile)
  655. && !( feof(InputFile) || ferror(InputFile) ) ) {
  656. //
  657. // Skip line if it does not contain trace ID.
  658. //
  659. if (_tcsstr (LineBuffer, TraceId) == NULL) {
  660. continue;
  661. }
  662. //
  663. // We have got a trace ID. We need now to copy everything
  664. // to a trace buffer until we get a line containing a character
  665. // in column zero.
  666. //
  667. while (_fgetts(LineBuffer, sizeof(LineBuffer), InputFile)
  668. && !( feof(InputFile) || ferror(InputFile) ) ) {
  669. if (LineBuffer[0] == _T(' ') ||
  670. LineBuffer[0] == _T('\0') ||
  671. LineBuffer[0] == _T('\n') ||
  672. LineBuffer[0] == _T('\t')) {
  673. _tcscat (StackTraceBuffer, LineBuffer);
  674. }
  675. else {
  676. break;
  677. }
  678. }
  679. break;
  680. }
  681. //
  682. // Close file.
  683. fclose(InputFile);
  684. return StackTraceBuffer;
  685. }