830 lines
17 KiB
C
830 lines
17 KiB
C
/*++
|
|
|
|
Copyright (c) 2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
dumpsym.cxx
|
|
|
|
Abstract:
|
|
|
|
This is the command line tool to dump symbols from an image.
|
|
|
|
Author:
|
|
|
|
David Fields - Feb 23, 2000
|
|
Silviu Calinoiu - Feb 28, 2000
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <tchar.h>
|
|
#include <windows.h>
|
|
#include <imagehlp.h>
|
|
#include <common.ver>
|
|
|
|
//
|
|
// Section information
|
|
//
|
|
|
|
typedef struct {
|
|
|
|
CHAR Name [9];
|
|
DWORD64 Start;
|
|
ULONG Size;
|
|
|
|
} IMG_SECTION_INFO, * PIMG_SECTION_INFO;
|
|
|
|
#define MAX_NUMBER_OF_SECTIONS 1024
|
|
IMG_SECTION_INFO Section [MAX_NUMBER_OF_SECTIONS];
|
|
ULONG SectionWriteIndex = 0;
|
|
|
|
|
|
typedef struct {
|
|
|
|
HANDLE File;
|
|
HANDLE Section;
|
|
LPBYTE ImageBase;
|
|
|
|
PIMAGE_DOS_HEADER DosHeader;
|
|
PIMAGE_FILE_HEADER FileHeader;
|
|
PIMAGE_OPTIONAL_HEADER OptionalHeader;
|
|
PIMAGE_SECTION_HEADER SectionHeader;
|
|
|
|
DWORD FileSignature;
|
|
|
|
PIMAGE_DATA_DIRECTORY ImportDirectory;
|
|
PIMAGE_SECTION_HEADER ImportSection;
|
|
PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor;
|
|
DWORD_PTR AddressCorrection;
|
|
|
|
} IMAGE_BROWSE_INFO, *PIMAGE_BROWSE_INFO;
|
|
|
|
|
|
BOOL
|
|
ImgInitializeBrowseInfo (
|
|
|
|
LPCTSTR FilePath,
|
|
PIMAGE_BROWSE_INFO Info);
|
|
|
|
|
|
BOOL
|
|
ImgDeleteBrowseInfo (
|
|
|
|
PIMAGE_BROWSE_INFO Info);
|
|
|
|
PCHAR
|
|
ImgSearchSectionForAddress (
|
|
DWORD64 Address
|
|
);
|
|
|
|
BOOL
|
|
ShouldExcludeSymbol (
|
|
LPSTR Name
|
|
);
|
|
|
|
BOOL
|
|
OpenExcludeFile (
|
|
LPSTR FilePath
|
|
);
|
|
|
|
//
|
|
// Symbol information
|
|
//
|
|
|
|
typedef struct {
|
|
|
|
LPSTR Name;
|
|
DWORD64 Address;
|
|
ULONG Size;
|
|
BOOL Exclude;
|
|
|
|
} SYMBOL, *PSYMBOL;
|
|
|
|
PSYMBOL Symbols;
|
|
DWORD SymbolCount;
|
|
DWORD TotalNumberOfSymbols;
|
|
|
|
VOID
|
|
DumpSymbols(
|
|
char *,
|
|
BOOL All,
|
|
BOOL SortBySize);
|
|
|
|
VOID
|
|
PrintUsage(
|
|
);
|
|
|
|
VOID
|
|
Error (
|
|
char * Fmt,
|
|
...
|
|
);
|
|
|
|
int __cdecl
|
|
SymbolCompareBySize(
|
|
const void * Arg1,
|
|
const void * Arg2
|
|
);
|
|
|
|
int __cdecl
|
|
SymbolCompareByAddress(
|
|
const void * Arg1,
|
|
const void * Arg2
|
|
);
|
|
|
|
BOOL
|
|
CALLBACK
|
|
SymbolEnumerationCallback(
|
|
LPSTR SymbolName,
|
|
DWORD64 SymbolAddress,
|
|
ULONG SymbolSize,
|
|
PVOID UserContext
|
|
);
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
VOID
|
|
Help (
|
|
)
|
|
{
|
|
printf("dumpsym BINARY-PATH [OPTIONS] \n");
|
|
printf("%s \n", VER_LEGALCOPYRIGHT_STR);
|
|
printf(" \n");
|
|
printf("OPTIONS: \n");
|
|
printf("/notpaged Print all symbols that are not pageable \n");
|
|
printf("/all Print all symbols (default) \n");
|
|
printf("/address Sort by address in increasing order \n");
|
|
printf("/size Sort by size in decreasing order (default) \n");
|
|
printf("/exclude PATH File with symbols that should be ignored \n");
|
|
printf("/symbols PATH Symbols path. If not specified symbols must be \n");
|
|
printf(" in the directory containing the binary. \n");
|
|
printf(" \n");
|
|
printf("Ex. dumpsym c:\\binaries.x86fre\\ntoskrnl.exe \n");
|
|
printf(" /symbols c:\\binaries.x86fre\\Symbols.pri\\retail \n");
|
|
printf(" \n");
|
|
printf("This tool can be used to determine what symbols are not paged \n");
|
|
printf("and then manually analyze if any of the functions or variables \n");
|
|
printf("can be moved into a PAGEXXXX section (become pageable). When \n");
|
|
printf("analyzing this data please take into account that the size for \n");
|
|
printf("some symbols includes padding/alignment zones and therefore \n");
|
|
printf("appears to be bigger than it really is. \n");
|
|
printf(" \n");
|
|
printf("Ex. dumpsym \\\\robsvbl1\\latest\\ntfs.sys \n");
|
|
printf(" /symbols \\\\robsvbl1\\latest\\Symbols.pri\\retail \n");
|
|
printf(" /notpaged /size \n");
|
|
printf(" \n");
|
|
printf(" \n");
|
|
exit(-1);
|
|
}
|
|
|
|
|
|
VOID
|
|
Error (
|
|
char * Fmt,
|
|
...
|
|
)
|
|
{
|
|
va_list Prms;
|
|
|
|
va_start (Prms, Fmt);
|
|
fprintf (stderr, "Dumpsym error: ");
|
|
vfprintf (stderr, Fmt, Prms);
|
|
fprintf (stderr, "\n");
|
|
fflush (stderr);
|
|
exit (1);
|
|
}
|
|
|
|
PCHAR *
|
|
SearchOption (
|
|
PCHAR * Args,
|
|
PCHAR Option
|
|
)
|
|
{
|
|
for ( ; Args && *Args; Args++) {
|
|
if (_stricmp (*Args, Option) == 0) {
|
|
return Args;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// main
|
|
//
|
|
|
|
VOID __cdecl
|
|
main (
|
|
int argc,
|
|
char *argv[]
|
|
)
|
|
{
|
|
IMAGE_BROWSE_INFO Info;
|
|
PCHAR ExeName;
|
|
PCHAR LongName;
|
|
BOOL OptionAll;
|
|
BOOL OptionSortBySize;
|
|
PCHAR * OptionString;
|
|
|
|
if (argc == 1 || SearchOption (argv, "/?")) {
|
|
Help ();
|
|
}
|
|
|
|
SymInitialize(GetCurrentProcess(), NULL, FALSE);
|
|
SymSetOptions(SYMOPT_UNDNAME);
|
|
|
|
//
|
|
// /exclude EXCLUDE-FILE-PATH
|
|
//
|
|
|
|
if ((OptionString = SearchOption (argv, "/exclude"))) {
|
|
OpenExcludeFile (*(OptionString + 1));
|
|
}
|
|
|
|
//
|
|
// dumpsym PATH-TO-BINARY
|
|
//
|
|
|
|
if ((OptionString = SearchOption (argv, argv[0]))) {
|
|
|
|
ImgInitializeBrowseInfo (*(OptionString + 1), &Info);
|
|
LongName = *(OptionString + 1);
|
|
}
|
|
else {
|
|
Help ();
|
|
}
|
|
|
|
//
|
|
// /symbols SYMBOL-PATH
|
|
//
|
|
|
|
if ((OptionString = SearchOption (argv, "/symbols"))) {
|
|
SetCurrentDirectory (*(OptionString + 1));
|
|
}
|
|
|
|
//
|
|
// Dump options.
|
|
//
|
|
|
|
OptionAll = TRUE;
|
|
OptionSortBySize = TRUE;
|
|
|
|
if (SearchOption (argv, "/notpaged")) {
|
|
OptionAll = FALSE;
|
|
}
|
|
|
|
if (SearchOption (argv, "/all")) {
|
|
OptionAll = TRUE;
|
|
}
|
|
|
|
if (SearchOption (argv, "/address")) {
|
|
OptionSortBySize = FALSE;
|
|
}
|
|
|
|
if (SearchOption (argv, "/size")) {
|
|
OptionSortBySize = TRUE;
|
|
}
|
|
|
|
//
|
|
// Dump stuff.
|
|
//
|
|
|
|
DumpSymbols (LongName, OptionAll, OptionSortBySize);
|
|
}
|
|
|
|
LPSTR
|
|
CopyString (
|
|
LPSTR Source
|
|
)
|
|
{
|
|
LPSTR Target;
|
|
|
|
Target = (LPSTR) malloc (strlen(Source) + 1);
|
|
|
|
if (Target) {
|
|
strcpy (Target, Source);
|
|
}
|
|
|
|
return Target;
|
|
}
|
|
|
|
|
|
BOOL
|
|
CALLBACK
|
|
SymbolEnumerationCallback(
|
|
LPSTR SymbolName,
|
|
DWORD64 SymbolAddress,
|
|
ULONG SymbolSize,
|
|
PVOID UserContext
|
|
)
|
|
{
|
|
if (PtrToUlong(UserContext) == 1) {
|
|
|
|
if (SymbolName == NULL) {
|
|
Error ("Ooops");
|
|
}
|
|
|
|
if (SymbolCount >= TotalNumberOfSymbols) {
|
|
Error ("enumerated more symbols on second pass");
|
|
}
|
|
|
|
Symbols[SymbolCount].Name = CopyString (SymbolName);
|
|
Symbols[SymbolCount].Address = SymbolAddress;
|
|
Symbols[SymbolCount].Size = SymbolSize;
|
|
|
|
if (Symbols[SymbolCount].Name == NULL) {
|
|
Symbols[SymbolCount].Name = "*error*";
|
|
}
|
|
}
|
|
|
|
SymbolCount += 1;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
int __cdecl
|
|
SymbolCompareBySize(
|
|
const void * Arg1,
|
|
const void * Arg2
|
|
)
|
|
{
|
|
PSYMBOL Sym1 = (PSYMBOL) Arg1;
|
|
PSYMBOL Sym2 = (PSYMBOL) Arg2;
|
|
|
|
// decreasing order by size
|
|
return (Sym2->Size - Sym1->Size);
|
|
}
|
|
|
|
|
|
int __cdecl
|
|
SymbolCompareByAddress(
|
|
const void * Arg1,
|
|
const void * Arg2
|
|
)
|
|
{
|
|
PSYMBOL Sym1 = (PSYMBOL) Arg1;
|
|
PSYMBOL Sym2 = (PSYMBOL) Arg2;
|
|
INT64 Delta;
|
|
|
|
// increasing order by address
|
|
Delta = (INT64)(Sym1->Address - Sym2->Address);
|
|
|
|
if (Delta > 0) {
|
|
return 1;
|
|
}
|
|
else if (Delta == 0) {
|
|
return 0;
|
|
}
|
|
else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
DumpSymbols(
|
|
LPTSTR ImageName,
|
|
BOOL All,
|
|
BOOL SortBySize)
|
|
{
|
|
DWORD64 BaseOfDll;
|
|
PCHAR SectionName;
|
|
DWORD I, J;
|
|
BOOL FoundOne;
|
|
|
|
//
|
|
// Load symbols
|
|
//
|
|
|
|
BaseOfDll = SymLoadModule64(
|
|
GetCurrentProcess (),
|
|
NULL,
|
|
ImageName,
|
|
NULL,
|
|
0,
|
|
0);
|
|
|
|
if (BaseOfDll == 0) {
|
|
Error ("cannot load symbols for %s \n", ImageName);
|
|
}
|
|
|
|
//
|
|
// Number the symbols
|
|
//
|
|
|
|
SymbolCount = 0;
|
|
|
|
SymEnumerateSymbols64(
|
|
GetCurrentProcess(),
|
|
BaseOfDll,
|
|
SymbolEnumerationCallback,
|
|
0); // Count them
|
|
|
|
TotalNumberOfSymbols = SymbolCount;
|
|
printf("Detected %u symbols in %s \n\n", TotalNumberOfSymbols, ImageName);
|
|
|
|
//
|
|
// Read all symbols.
|
|
//
|
|
|
|
SymbolCount = 0;
|
|
Symbols = malloc(TotalNumberOfSymbols * sizeof(SYMBOL));
|
|
|
|
if (Symbols == NULL) {
|
|
Error ("out of memory (failed to allocate %u bytes)", TotalNumberOfSymbols * sizeof(SYMBOL));
|
|
}
|
|
|
|
SymEnumerateSymbols64(
|
|
GetCurrentProcess(),
|
|
BaseOfDll,
|
|
SymbolEnumerationCallback,
|
|
(PVOID)1);
|
|
|
|
//
|
|
// Sort symbols
|
|
//
|
|
|
|
qsort(
|
|
Symbols,
|
|
TotalNumberOfSymbols,
|
|
sizeof(SYMBOL),
|
|
(SortBySize ? SymbolCompareBySize : SymbolCompareByAddress));
|
|
|
|
//
|
|
// Figure out symbols that should not be printed.
|
|
//
|
|
|
|
for (J = 0; J < TotalNumberOfSymbols; J++) {
|
|
|
|
if (ShouldExcludeSymbol (Symbols[J].Name)) {
|
|
Symbols[J].Exclude = TRUE;
|
|
}
|
|
else {
|
|
Symbols[J].Exclude = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Print symbols
|
|
//
|
|
|
|
printf("%-8s %-16s %-8s %s \n", "Section", "Address", "Size", "Symbol");
|
|
printf("-------------------------------------------------------------\n");
|
|
|
|
for (I = 0; I < SectionWriteIndex; I++) {
|
|
|
|
for (J = 0, FoundOne = FALSE; J < TotalNumberOfSymbols; J++) {
|
|
|
|
if (Symbols[J].Exclude) {
|
|
continue;
|
|
}
|
|
|
|
SectionName = ImgSearchSectionForAddress (
|
|
Symbols[J].Address - BaseOfDll);
|
|
|
|
if (strcmp (SectionName, Section[I].Name) == 0) {
|
|
if (All || strstr (SectionName,"PAGE") == NULL) {
|
|
|
|
if (Symbols[J].Name == NULL) {
|
|
printf(".\n");
|
|
continue;
|
|
}
|
|
printf("%-8s %016I64X %08X %s \n",
|
|
SectionName,
|
|
Symbols[J].Address - BaseOfDll,
|
|
Symbols[J].Size,
|
|
Symbols[J].Name);
|
|
|
|
FoundOne = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FoundOne) {
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
//
|
|
// Unload symbols
|
|
//
|
|
|
|
if (SymUnloadModule64(GetCurrentProcess(), BaseOfDll) == FALSE) {
|
|
Error ("cannot unload symbols");
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////// Section manipulation routines
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Function:
|
|
//
|
|
// ImgInitializeBrowseInfo
|
|
//
|
|
// Description:
|
|
//
|
|
// This functions fills oout the `Info' structure with
|
|
// various pointers to PE data from the mapped image file.
|
|
//
|
|
// Note. Even if the function returned false the destructor
|
|
// `ImgDeleteBrowseInfo' should be called because it does some
|
|
// cleanup.
|
|
//
|
|
// Return:
|
|
//
|
|
// True if all the PE data pointers have been obtained.
|
|
//
|
|
|
|
BOOL
|
|
ImgInitializeBrowseInfo (
|
|
|
|
LPCTSTR FilePath,
|
|
PIMAGE_BROWSE_INFO Info)
|
|
{
|
|
DWORD Index, I;
|
|
|
|
if (Info == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
ZeroMemory (Info, sizeof *Info);
|
|
|
|
Info->File = CreateFile (
|
|
|
|
FilePath,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
0,
|
|
NULL);
|
|
|
|
if (Info->File == INVALID_HANDLE_VALUE) {
|
|
|
|
Error ("create file %s (error %u)", FilePath, GetLastError());
|
|
return FALSE;
|
|
}
|
|
|
|
Info->Section = CreateFileMapping (
|
|
|
|
Info->File,
|
|
NULL,
|
|
PAGE_READONLY,
|
|
0,
|
|
0,
|
|
NULL);
|
|
|
|
if (Info->Section == NULL) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
Info->ImageBase = (LPBYTE) MapViewOfFile (
|
|
|
|
Info->Section,
|
|
FILE_MAP_READ,
|
|
0,
|
|
0,
|
|
0);
|
|
|
|
if (Info->ImageBase == NULL) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check the signature
|
|
//
|
|
|
|
Info->DosHeader = (PIMAGE_DOS_HEADER)Info->ImageBase;
|
|
|
|
if (Info->DosHeader->e_magic != 'ZM') {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
Info->FileHeader = (PIMAGE_FILE_HEADER)
|
|
(Info->ImageBase + Info->DosHeader->e_lfanew + sizeof(DWORD));
|
|
|
|
Info->FileSignature = *((DWORD *)Info->FileHeader - 1);
|
|
|
|
if (Info->FileSignature != IMAGE_NT_SIGNATURE) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
Info->OptionalHeader = (PIMAGE_OPTIONAL_HEADER)(Info->FileHeader + 1);
|
|
Info->ImportDirectory = & (Info->OptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]);
|
|
Info->SectionHeader = (PIMAGE_SECTION_HEADER)(Info->OptionalHeader + 1);
|
|
Info->ImportSection = NULL;
|
|
|
|
//
|
|
// Find the section containing the import table
|
|
//
|
|
|
|
printf("Sections in %s \n\n", FilePath);
|
|
|
|
for (Index = 0; Index < Info->FileHeader->NumberOfSections; Index++) {
|
|
|
|
//
|
|
// SilviuC: I wonder if there is a way to get a 64 bit value for VirtualAddress.
|
|
// Apparently it is stored as a ULONG in PE format.
|
|
//
|
|
|
|
Section[SectionWriteIndex].Start = (DWORD64)((Info->SectionHeader + Index)->VirtualAddress);
|
|
Section[SectionWriteIndex].Size = (Info->SectionHeader + Index)->SizeOfRawData;
|
|
|
|
for (I = 0; I < 8; I++) {
|
|
Section[SectionWriteIndex].Name[I] = ((Info->SectionHeader + Index)->Name)[I];
|
|
}
|
|
|
|
Section[SectionWriteIndex].Name[I] = 0;
|
|
|
|
printf("%-8s %08X %08X \n",
|
|
Section[SectionWriteIndex].Name,
|
|
Section[SectionWriteIndex].Start,
|
|
Section[SectionWriteIndex].Size);
|
|
|
|
SectionWriteIndex += 1;
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
//
|
|
// Find the address of import data in the section body.
|
|
//
|
|
|
|
#if 0
|
|
Info->AddressCorrection = (DWORD_PTR)Info->ImageBase
|
|
+ Info->ImportSection->PointerToRawData
|
|
- Info->ImportSection->VirtualAddress;
|
|
|
|
Info->ImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(Info->AddressCorrection
|
|
+ Info->ImportDirectory->VirtualAddress);
|
|
#endif
|
|
|
|
//
|
|
// Finish
|
|
//
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// Function:
|
|
//
|
|
// ImgDeleteBrowseInfo
|
|
//
|
|
// Description:
|
|
//
|
|
// This function cleans up the `Info' structure, unmaps views,
|
|
// closes handles, etc.
|
|
//
|
|
|
|
BOOL
|
|
ImgDeleteBrowseInfo (
|
|
|
|
PIMAGE_BROWSE_INFO Info)
|
|
{
|
|
if (Info == NULL)
|
|
return FALSE;
|
|
|
|
UnmapViewOfFile (Info->ImageBase);
|
|
CloseHandle (Info->Section);
|
|
CloseHandle (Info->File);
|
|
|
|
ZeroMemory (Info, sizeof *Info);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
PCHAR
|
|
ImgSearchSectionForAddress (
|
|
DWORD64 Address
|
|
)
|
|
{
|
|
DWORD I;
|
|
|
|
for (I = 0; I < SectionWriteIndex; I++) {
|
|
if (Section[I].Start <= Address && Address < Section[I].Start + Section[I].Size) {
|
|
return Section[I].Name;
|
|
}
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
//
|
|
// Exclude file logic
|
|
//
|
|
|
|
PCHAR *ExcludeStrings;
|
|
DWORD NumberOfExcludeStrings;
|
|
|
|
BOOL
|
|
ShouldExcludeSymbol (
|
|
LPSTR Name
|
|
)
|
|
{
|
|
DWORD I;
|
|
|
|
if (ExcludeStrings == NULL) {
|
|
return FALSE;
|
|
}
|
|
for (I = 0; I <NumberOfExcludeStrings; I += 1) {
|
|
|
|
if (_stricmp (Name, ExcludeStrings[I]) == 0) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
OpenExcludeFile (
|
|
LPSTR FilePath
|
|
)
|
|
{
|
|
FILE * File;
|
|
CHAR String[1024];
|
|
DWORD StringCount = 0;
|
|
|
|
File = fopen (FilePath, "r");
|
|
|
|
if (File == NULL) {
|
|
Error ("cannot open exclude file %s", FilePath);
|
|
}
|
|
|
|
while (fgets (String, 1024, File)) {
|
|
StringCount += 1;
|
|
}
|
|
|
|
fclose (File);
|
|
|
|
ExcludeStrings = (PCHAR *)malloc (StringCount * sizeof(PVOID));
|
|
|
|
if (ExcludeStrings == NULL) {
|
|
Error ("cannot allocate exclude strings buffer");
|
|
}
|
|
|
|
NumberOfExcludeStrings = StringCount;
|
|
|
|
printf("Excluding %u symbols from %s \n",
|
|
NumberOfExcludeStrings,
|
|
FilePath);
|
|
|
|
File = fopen (FilePath, "r");
|
|
if (!File) {
|
|
Error ("cannot open file");
|
|
}
|
|
|
|
StringCount = 0;
|
|
|
|
while (fgets (String, 1024, File)) {
|
|
|
|
PCHAR Start, Current;
|
|
|
|
Current = String;
|
|
|
|
while (*Current == ' ' || *Current == '\t') {
|
|
Current += 1;
|
|
}
|
|
|
|
Start = Current;
|
|
|
|
while (*Current && *Current != ' ' && *Current != '\t' && *Current != '\n') {
|
|
Current += 1;
|
|
}
|
|
|
|
*Current = '\0';
|
|
|
|
if (StringCount < NumberOfExcludeStrings) {
|
|
ExcludeStrings[StringCount] = CopyString (Start);
|
|
|
|
// printf("Exclude %s \n", ExcludeStrings[StringCount]);
|
|
}
|
|
|
|
StringCount += 1;
|
|
}
|
|
|
|
fclose (File);
|
|
|
|
return TRUE;
|
|
}
|