1453 lines
30 KiB
C++
1453 lines
30 KiB
C++
//----------------------------------------------------------------------------
|
|
//
|
|
// Source file searching and loading.
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 1997-2001.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "ntsdp.hpp"
|
|
|
|
// #define DBG_SRC
|
|
// #define VERBOSE_SRC
|
|
|
|
ULONG g_SrcOptions;
|
|
|
|
LPSTR g_SrcPath;
|
|
PSRCFILE g_SrcFiles;
|
|
PSRCFILE g_CurSrcFile;
|
|
ULONG g_CurSrcLine;
|
|
|
|
ULONG g_OciSrcBefore;
|
|
ULONG g_OciSrcAfter = 1;
|
|
|
|
PSRCFILE
|
|
LoadSrcFile(
|
|
LPSTR FileName,
|
|
LPSTR RecordFileName
|
|
)
|
|
{
|
|
PathFile* File;
|
|
PSRCFILE SrcFile, Realloc;
|
|
ULONG Avail;
|
|
ULONG BaseLen, Len, Done;
|
|
LPSTR Cur, End;
|
|
ULONG Lines;
|
|
LPSTR *CurLine, LineStart;
|
|
ULONG ReadLen;
|
|
|
|
if (OpenPathFile(FileName, g_SymOptions, &File) != S_OK)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
BaseLen = sizeof(SRCFILE) + strlen(RecordFileName) + 1;
|
|
Len = BaseLen;
|
|
SrcFile = NULL;
|
|
for (;;)
|
|
{
|
|
if (File->QueryDataAvailable(&Avail) != S_OK)
|
|
{
|
|
goto EH_CloseFile;
|
|
}
|
|
if (Avail == 0)
|
|
{
|
|
if (SrcFile == NULL)
|
|
{
|
|
goto EH_CloseFile;
|
|
}
|
|
break;
|
|
}
|
|
|
|
Realloc = (SRCFILE *)realloc(SrcFile, Len + Avail);
|
|
if (Realloc == NULL)
|
|
{
|
|
goto EH_CloseFile;
|
|
}
|
|
SrcFile = Realloc;
|
|
|
|
if (File->Read((LPSTR)SrcFile + Len, Avail, &Done) != S_OK ||
|
|
Done < Avail)
|
|
{
|
|
goto EH_CloseFile;
|
|
}
|
|
|
|
Len += Avail;
|
|
}
|
|
|
|
SrcFile->File = (LPSTR)(SrcFile + 1);
|
|
strcpy(SrcFile->File, RecordFileName);
|
|
SrcFile->RawText = (LPSTR)SrcFile + BaseLen;
|
|
Len -= BaseLen;
|
|
|
|
// Count lines in the source file. Stop before the last character
|
|
// to handle the case where there's a newline at the end of the
|
|
// file in the same way as where there isn't one.
|
|
|
|
Lines = 0;
|
|
Cur = SrcFile->RawText;
|
|
End = SrcFile->RawText + Len;
|
|
while (Cur < End - 1)
|
|
{
|
|
if (*Cur++ == '\n')
|
|
{
|
|
Lines++;
|
|
}
|
|
}
|
|
Lines++;
|
|
|
|
SrcFile->LineText = (char **)malloc(sizeof(LPSTR) * Lines);
|
|
if (SrcFile->LineText == NULL)
|
|
{
|
|
goto EH_CloseFile;
|
|
}
|
|
|
|
SrcFile->Lines = Lines;
|
|
Cur = SrcFile->RawText;
|
|
CurLine = SrcFile->LineText;
|
|
LineStart = Cur;
|
|
while (Cur < End - 1)
|
|
{
|
|
if (*Cur == '\n')
|
|
{
|
|
*CurLine++ = LineStart;
|
|
*Cur = 0;
|
|
LineStart = Cur+1;
|
|
}
|
|
else if (*Cur == '\r')
|
|
{
|
|
*Cur = 0;
|
|
}
|
|
Cur++;
|
|
}
|
|
*CurLine++ = LineStart;
|
|
|
|
delete File;
|
|
|
|
SrcFile->Next = g_SrcFiles;
|
|
g_SrcFiles = SrcFile;
|
|
|
|
#ifdef VERBOSE_SRC
|
|
dprintf("Loaded '%s' '%s' %d lines\n", FileName, RecordFileName, Lines);
|
|
#endif
|
|
|
|
return SrcFile;
|
|
|
|
EH_CloseFile:
|
|
free(SrcFile);
|
|
delete File;
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
DeleteSrcFile(
|
|
PSRCFILE SrcFile
|
|
)
|
|
{
|
|
if (g_CurSrcFile == SrcFile)
|
|
{
|
|
g_CurSrcFile = NULL;
|
|
g_CurSrcLine = 0;
|
|
}
|
|
|
|
free(SrcFile->LineText);
|
|
free(SrcFile);
|
|
}
|
|
|
|
BOOL
|
|
MatchSrcFileName(
|
|
PSRCFILE SrcFile,
|
|
LPSTR File
|
|
)
|
|
{
|
|
LPSTR FileStop, MatchStop;
|
|
|
|
//
|
|
// SRCFILE filenames are saved as the partial path that
|
|
// matched a source path component rather than the full path
|
|
// of the file as loaded. When matching against potentially full
|
|
// path information in debug info it's useful to use the incoming
|
|
// string as the filename and the SRCFILE filename as the match
|
|
// string. A full match indicates that the partial path matches
|
|
// completely and so should be used.
|
|
//
|
|
// This doesn't work so well for human input where the filename is
|
|
// likely to be just a filename with no path. In this case there
|
|
// won't be a full match of the match string, nor is just flipping
|
|
// the order of strings useful because that would allow submatches
|
|
// such as "foo.c" matching "barfoo.c". Instead this code tests
|
|
// two conditions:
|
|
// 1. Full match string match.
|
|
// 2. Full file string match (implies partial match string match)
|
|
// and the mismatch character is a path separator.
|
|
// This forces filenames to match completely.
|
|
//
|
|
|
|
if (SymMatchFileName(File, SrcFile->File, &FileStop, &MatchStop) ||
|
|
(FileStop < File && IS_PATH_DELIM(*MatchStop)))
|
|
{
|
|
#ifdef DBG_SRC
|
|
dprintf("'%s' matches '%s'\n", SrcFile->File, File);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
#ifdef DBG_SRC
|
|
dprintf("'%s' doesn't match '%s'\n", SrcFile->File, File);
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
UnloadSrcFile(
|
|
LPSTR File
|
|
)
|
|
{
|
|
PSRCFILE SrcFile, Prev;
|
|
|
|
Prev = NULL;
|
|
for (SrcFile = g_SrcFiles; SrcFile != NULL; SrcFile = SrcFile->Next)
|
|
{
|
|
if (MatchSrcFileName(SrcFile, File))
|
|
{
|
|
break;
|
|
}
|
|
|
|
Prev = SrcFile;
|
|
}
|
|
|
|
if (SrcFile == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (Prev != NULL)
|
|
{
|
|
Prev->Next = SrcFile->Next;
|
|
}
|
|
else
|
|
{
|
|
g_SrcFiles = SrcFile->Next;
|
|
}
|
|
|
|
DeleteSrcFile(SrcFile);
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
UnloadSrcFiles(
|
|
void
|
|
)
|
|
{
|
|
PSRCFILE Cur, Next;
|
|
|
|
for (Cur = g_SrcFiles; Cur != NULL; Cur = Next)
|
|
{
|
|
Next = Cur->Next;
|
|
|
|
DeleteSrcFile(Cur);
|
|
}
|
|
|
|
g_SrcFiles = NULL;
|
|
}
|
|
|
|
PSRCFILE
|
|
FindLoadedSrcFile(
|
|
LPSTR File
|
|
)
|
|
{
|
|
PSRCFILE SrcFile;
|
|
|
|
for (SrcFile = g_SrcFiles; SrcFile != NULL; SrcFile = SrcFile->Next)
|
|
{
|
|
if (MatchSrcFileName(SrcFile, File))
|
|
{
|
|
#ifdef DBG_SRC
|
|
dprintf("Found loaded file '%s'\n", SrcFile->File);
|
|
#endif
|
|
return SrcFile;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
ConcatPathComponents(
|
|
LPSTR Path,
|
|
LPSTR PathEnd,
|
|
LPSTR* PathOut,
|
|
LPSTR FilePath,
|
|
LPSTR Buffer
|
|
)
|
|
{
|
|
if (PathEnd == NULL)
|
|
{
|
|
PathEnd = strchr(Path, ';');
|
|
if (PathEnd != NULL)
|
|
{
|
|
if (PathOut != NULL)
|
|
{
|
|
*PathOut = PathEnd + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PathEnd = Path + strlen(Path);
|
|
if (PathOut != NULL)
|
|
{
|
|
*PathOut = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (PathEnd > Path)
|
|
{
|
|
memcpy(Buffer, Path, (int)(PathEnd - Path));
|
|
PathEnd = Buffer + (PathEnd - Path - 1);
|
|
|
|
// Attempt to avoid duplicating separators while forcing separation.
|
|
if ((*PathEnd == ':' && *FilePath == ':') ||
|
|
(IS_SLASH(*PathEnd) && IS_SLASH(*FilePath)))
|
|
{
|
|
FilePath++;
|
|
}
|
|
else if (!IS_PATH_DELIM(*PathEnd) && !IS_PATH_DELIM(*FilePath))
|
|
{
|
|
*(++PathEnd) = '\\';
|
|
}
|
|
|
|
strcpy(PathEnd + 1, FilePath);
|
|
}
|
|
else
|
|
{
|
|
strcpy(Buffer, FilePath);
|
|
}
|
|
}
|
|
|
|
void
|
|
EditPathSlashes(
|
|
LPSTR Path
|
|
)
|
|
{
|
|
if (!IsUrlPathComponent(Path))
|
|
{
|
|
return;
|
|
}
|
|
|
|
PSTR Scan = Path;
|
|
|
|
// Flip all backslashes forwards.
|
|
while (*Scan)
|
|
{
|
|
if (*Scan == '\\')
|
|
{
|
|
*Scan = '/';
|
|
}
|
|
|
|
Scan++;
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
SrcFileExists(
|
|
LPSTR Path,
|
|
LPSTR PathEnd,
|
|
LPSTR* PathOut,
|
|
LPSTR FilePath,
|
|
LPSTR File
|
|
)
|
|
{
|
|
char Buffer[MAX_SOURCE_PATH];
|
|
|
|
ConcatPathComponents(Path, PathEnd, PathOut, FilePath, Buffer);
|
|
if (File != NULL)
|
|
{
|
|
ULONG Len = strlen(Buffer);
|
|
Buffer[Len] = '\\';
|
|
strcpy(Buffer + Len + 1, File);
|
|
}
|
|
|
|
EditPathSlashes(Buffer);
|
|
|
|
#ifdef DBG_SRC
|
|
dprintf("Check for existence of '%s'\n", Buffer);
|
|
#endif
|
|
|
|
FILE_IO_TYPE IoType;
|
|
|
|
return PathFileExists(Buffer, g_SymOptions, &IoType);
|
|
}
|
|
|
|
BOOL
|
|
FindSrcFileOnPath(
|
|
ULONG StartElement,
|
|
LPSTR File,
|
|
ULONG Flags,
|
|
PSTR Found,
|
|
PSTR* MatchPart,
|
|
PULONG FoundElement
|
|
)
|
|
{
|
|
LPSTR PathSuff;
|
|
LPSTR Path;
|
|
LPSTR PathStart;
|
|
LPSTR PathSep;
|
|
LPSTR PathCharPtr;
|
|
char PathChar;
|
|
ULONG Elt;
|
|
|
|
// Find the element of the path to start at.
|
|
PathStart = FindPathElement(g_SrcPath, StartElement, &PathSep);
|
|
if (PathStart == NULL)
|
|
{
|
|
goto CheckPlainFile;
|
|
}
|
|
|
|
// Split the given filename into a path prefix and a path
|
|
// suffix. Initially the path prefix is any path components
|
|
// and the path suffix is just the filename. If there
|
|
// are path components attempt to match them against the source
|
|
// path. Keep backing up the path one component at a time
|
|
// until a match is found or the prefix is emptied. At
|
|
// that point just do a plain file search along the source path.
|
|
PathSuff = File + strlen(File);
|
|
|
|
for (;;)
|
|
{
|
|
while (--PathSuff >= File)
|
|
{
|
|
if (IS_SLASH(*PathSuff) ||
|
|
(*PathSuff == ':' && !IS_SLASH(*(PathSuff + 1))))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
PathSuff++;
|
|
|
|
// If we've run out of path prefix we're done with this
|
|
// part of the search.
|
|
if (PathSuff == File)
|
|
{
|
|
break;
|
|
}
|
|
|
|
char Save;
|
|
LPSTR BestPathStart;
|
|
LPSTR BestPathEnd;
|
|
LPSTR BestFile;
|
|
ULONG BestElement;
|
|
LPSTR MatchPath;
|
|
LPSTR MatchFile;
|
|
|
|
Save = *(PathSuff - 1);
|
|
*(PathSuff - 1) = 0;
|
|
|
|
#ifdef DBG_SRC
|
|
dprintf("Check path pre '%s' suff '%s'\n",
|
|
File, PathSuff);
|
|
#endif
|
|
|
|
Path = PathStart;
|
|
Elt = StartElement;
|
|
BestPathStart = NULL;
|
|
BestFile = PathSuff - 2;
|
|
while (*Path != 0)
|
|
{
|
|
PathSep = strchr(Path, ';');
|
|
if (PathSep == NULL)
|
|
{
|
|
PathSep = Path + strlen(Path);
|
|
}
|
|
|
|
// Trim trailing slashes on path components as
|
|
// the file components have them trimmed so
|
|
// leaving them would confuse the matching.
|
|
PathCharPtr = PathSep;
|
|
if (PathCharPtr > Path && IS_SLASH(PathCharPtr[-1]))
|
|
{
|
|
PathCharPtr--;
|
|
}
|
|
|
|
PathChar = *PathCharPtr;
|
|
if (PathChar != 0)
|
|
{
|
|
*PathCharPtr = 0;
|
|
}
|
|
else
|
|
{
|
|
// Back up off the terminator so that PathSep
|
|
// can be advanced the same way for both
|
|
// ';' and end-of-string cases.
|
|
PathSep--;
|
|
}
|
|
|
|
SymMatchFileName(Path, File, &MatchPath, &MatchFile);
|
|
|
|
#ifdef DBG_SRC
|
|
dprintf("Match '%s' against '%s': %d (match '%s')\n",
|
|
Path, File, MatchFile - File, MatchFile + 1);
|
|
#endif
|
|
|
|
*PathCharPtr = PathChar;
|
|
|
|
if (MatchFile < BestFile &&
|
|
SrcFileExists(Path, MatchPath + 1, NULL,
|
|
MatchFile + 1, PathSuff))
|
|
{
|
|
BestPathStart = Path;
|
|
BestPathEnd = MatchPath + 1;
|
|
BestFile = MatchFile + 1;
|
|
BestElement = Elt;
|
|
|
|
// Check for complete match or first-match mode.
|
|
if (MatchPath < Path || MatchFile < File ||
|
|
(Flags & DEBUG_FIND_SOURCE_BEST_MATCH) == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
Path = PathSep + 1;
|
|
Elt++;
|
|
}
|
|
|
|
*(PathSuff - 1) = Save;
|
|
|
|
if (BestPathStart != NULL)
|
|
{
|
|
#ifdef DBG_SRC
|
|
dprintf("Found partial file '%.*s' on path '%.*s'\n",
|
|
PathSuff - BestFile, BestFile,
|
|
BestPathEnd - BestPathStart, BestPathStart);
|
|
#endif
|
|
|
|
// Return the match found.
|
|
ConcatPathComponents(BestPathStart, BestPathEnd, NULL,
|
|
BestFile, Found);
|
|
EditPathSlashes(Found);
|
|
*MatchPart = BestFile;
|
|
*FoundElement = BestElement;
|
|
|
|
#ifdef DBG_SRC
|
|
dprintf("Found partial file '%s' at %d\n",
|
|
Found, *FoundElement);
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Skip past separator.
|
|
PathSuff--;
|
|
}
|
|
|
|
// Traverse all directories in the source path and try them with the
|
|
// filename given. Start with the given filename
|
|
// to make the most restrictive check. If
|
|
// no match is found keep trimming components off and
|
|
// checking again.
|
|
|
|
PathSuff = File;
|
|
|
|
for (;;)
|
|
{
|
|
#ifdef DBG_SRC
|
|
dprintf("Scan all paths for '%s'\n", PathSuff);
|
|
#endif
|
|
|
|
Path = PathStart;
|
|
Elt = StartElement;
|
|
while (Path != NULL && *Path != 0)
|
|
{
|
|
if (SrcFileExists(Path, NULL, &PathSep, PathSuff, NULL))
|
|
{
|
|
// SrcFileExists leaves PathSep set to the
|
|
// path element after the separator so back up
|
|
// one when forming the return path.
|
|
if (PathSep != NULL)
|
|
{
|
|
PathSep--;
|
|
}
|
|
|
|
#ifdef DBG_SRC
|
|
dprintf("Found file suffix '%s' on path '%.*s'\n",
|
|
PathSuff, PathSep != NULL ?
|
|
PathSep - Path : strlen(Path), Path);
|
|
#endif
|
|
|
|
ConcatPathComponents(Path, PathSep, NULL, PathSuff, Found);
|
|
EditPathSlashes(Found);
|
|
*MatchPart = PathSuff;
|
|
*FoundElement = Elt;
|
|
|
|
#ifdef DBG_SRC
|
|
dprintf("Found file suffix '%s' at %d\n",
|
|
Found, *FoundElement);
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
Path = PathSep;
|
|
Elt++;
|
|
}
|
|
|
|
// Trim a component from the front of the path suffix.
|
|
PathSep = PathSuff;
|
|
while (*PathSep != 0 &&
|
|
!IS_SLASH(*PathSep) &&
|
|
(*PathSep != ':' || IS_SLASH(*(PathSep + 1))))
|
|
{
|
|
PathSep++;
|
|
}
|
|
if (*PathSep == 0)
|
|
{
|
|
// Nothing left to trim.
|
|
break;
|
|
}
|
|
|
|
PathSuff = PathSep + 1;
|
|
}
|
|
|
|
CheckPlainFile:
|
|
|
|
#ifdef DBG_SRC
|
|
dprintf("Check plain file '%s'\n", File);
|
|
#endif
|
|
|
|
if (GetFileAttributes(File) != -1)
|
|
{
|
|
strcpy(Found, File);
|
|
*MatchPart = File;
|
|
*FoundElement = -1;
|
|
|
|
#ifdef DBG_SRC
|
|
dprintf("Found plain file '%s' at %d\n", Found, *FoundElement);
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
PSRCFILE
|
|
LoadSrcFileOnPath(
|
|
LPSTR File
|
|
)
|
|
{
|
|
if (g_SrcPath == NULL)
|
|
{
|
|
return LoadSrcFile(File, File);
|
|
}
|
|
|
|
char Found[MAX_SOURCE_PATH];
|
|
PSTR MatchPart;
|
|
ULONG Elt;
|
|
|
|
if (FindSrcFileOnPath(0, File, DEBUG_FIND_SOURCE_BEST_MATCH,
|
|
Found, &MatchPart, &Elt))
|
|
{
|
|
return LoadSrcFile(Found, MatchPart);
|
|
}
|
|
|
|
dprintf("No source found for '%s'\n", File);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
PSRCFILE
|
|
FindSrcFile(
|
|
LPSTR File
|
|
)
|
|
{
|
|
PSRCFILE SrcFile;
|
|
|
|
#ifdef DBG_SRC
|
|
dprintf("Find '%s'\n", File);
|
|
#endif
|
|
|
|
SrcFile = FindLoadedSrcFile(File);
|
|
if (SrcFile == NULL)
|
|
{
|
|
SrcFile = LoadSrcFileOnPath(File);
|
|
}
|
|
return SrcFile;
|
|
}
|
|
|
|
void
|
|
OutputLineAddr(
|
|
ULONG64 Offset
|
|
)
|
|
{
|
|
IMAGEHLP_LINE64 Line;
|
|
ULONG Disp;
|
|
|
|
Line.SizeOfStruct = sizeof(Line);
|
|
if (SymGetLineFromAddr64(g_CurrentProcess->Handle,
|
|
Offset,
|
|
&Disp,
|
|
&Line))
|
|
{
|
|
dprintf("%s(%d)", Line.FileName, Line.LineNumber);
|
|
if (Disp != 0)
|
|
{
|
|
dprintf("+0x%x", Disp);
|
|
}
|
|
dprintf("\n");
|
|
}
|
|
}
|
|
|
|
void
|
|
OutputSrcLines(
|
|
PSRCFILE File,
|
|
ULONG First,
|
|
ULONG Last,
|
|
ULONG Mark
|
|
)
|
|
{
|
|
ULONG i;
|
|
LPSTR *Text;
|
|
|
|
if (First < 1)
|
|
{
|
|
First = 1;
|
|
}
|
|
if (Last > File->Lines)
|
|
{
|
|
Last = File->Lines;
|
|
}
|
|
|
|
Text = &File->LineText[First-1];
|
|
for (i = First; i <= Last; i++)
|
|
{
|
|
if (i == Mark)
|
|
{
|
|
dprintf(">");
|
|
}
|
|
else
|
|
{
|
|
dprintf(" ");
|
|
}
|
|
|
|
dprintf("%5d: %s\n", i, *Text++);
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
OutputSrcLinesAroundAddr(
|
|
ULONG64 Offset,
|
|
ULONG Before,
|
|
ULONG After
|
|
)
|
|
{
|
|
IMAGEHLP_LINE64 Line;
|
|
ULONG Disp;
|
|
PSRCFILE SrcFile;
|
|
|
|
Line.SizeOfStruct = sizeof(Line);
|
|
if (!SymGetLineFromAddr64(g_CurrentProcess->Handle,
|
|
Offset,
|
|
&Disp,
|
|
&Line))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
SrcFile = FindSrcFile(Line.FileName);
|
|
if (SrcFile == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
OutputSrcLines(SrcFile,
|
|
Line.LineNumber-Before, Line.LineNumber+After-1,
|
|
Line.LineNumber);
|
|
return TRUE;
|
|
}
|
|
|
|
ULONG
|
|
GetOffsetFromLine(
|
|
PSTR FileLine,
|
|
PULONG64 Offset
|
|
)
|
|
{
|
|
IMAGEHLP_LINE64 Line;
|
|
LPSTR Mod;
|
|
LPSTR File;
|
|
LPSTR LineStr;
|
|
LPSTR SlashF, SlashB;
|
|
ULONG LineNum;
|
|
ULONG Disp;
|
|
ULONG OldSym;
|
|
ULONG NewSym;
|
|
BOOL AllowDisp;
|
|
BOOL Ret;
|
|
PDEBUG_IMAGE_INFO Image = NULL;
|
|
|
|
if ((g_SymOptions & SYMOPT_LOAD_LINES) == 0)
|
|
{
|
|
WarnOut("WARNING: Line information loading disabled\n");
|
|
}
|
|
|
|
OldSym = g_SymOptions;
|
|
NewSym = g_SymOptions;
|
|
|
|
// Symbol directives can prefix the source expression.
|
|
// These can be given by sufficiently knowledgeable users
|
|
// but they're primarily a back-channel communication
|
|
// mechanism for windbg's source management.
|
|
if (*FileLine == '<')
|
|
{
|
|
FileLine++;
|
|
while (*FileLine != '>')
|
|
{
|
|
switch(*FileLine)
|
|
{
|
|
case 'U':
|
|
// Restrict the search to just loaded modules.
|
|
NewSym |= SYMOPT_NO_UNQUALIFIED_LOADS;
|
|
break;
|
|
default:
|
|
error(SYNTAX);
|
|
}
|
|
|
|
FileLine++;
|
|
}
|
|
|
|
FileLine++;
|
|
}
|
|
|
|
// Crack string of the form [module!][file][:line] into its
|
|
// components. Note that ! is a valid filename character so
|
|
// it's possible for ambiguity to occur between module references
|
|
// and filenames. This code assumes that ! is uncommon and
|
|
// handles it as a module separator unless there's a : or \ or /
|
|
// before it. : can also occur in paths and is filtered
|
|
// in a similar manner.
|
|
|
|
File = strchr(FileLine, '!');
|
|
LineStr = strchr(FileLine, ':');
|
|
SlashF = strchr(FileLine, '/');
|
|
SlashB = strchr(FileLine, '\\');
|
|
|
|
if (File != NULL &&
|
|
(LineStr != NULL && File > LineStr) ||
|
|
(SlashF != NULL && File > SlashF) ||
|
|
(SlashB != NULL && File > SlashB))
|
|
{
|
|
File = NULL;
|
|
}
|
|
|
|
if (File != NULL)
|
|
{
|
|
if (File == FileLine)
|
|
{
|
|
error(SYNTAX);
|
|
}
|
|
|
|
Mod = FileLine;
|
|
*File++ = 0;
|
|
}
|
|
else
|
|
{
|
|
Mod = NULL;
|
|
File = FileLine;
|
|
}
|
|
|
|
// If a module was specified check and see if it's
|
|
// a module that's currently present as that
|
|
// will affect which error code is returned.
|
|
if (Mod != NULL)
|
|
{
|
|
Image = GetImageByName(g_CurrentProcess, Mod, INAME_MODULE);
|
|
}
|
|
|
|
// Look for the first colon after path components.
|
|
while (LineStr != NULL &&
|
|
(LineStr < File || LineStr < SlashF || LineStr < SlashB))
|
|
{
|
|
LineStr = strchr(LineStr + 1, ':');
|
|
}
|
|
|
|
LineNum = 1;
|
|
if (LineStr != NULL)
|
|
{
|
|
PSTR NumEnd;
|
|
|
|
// A specific line was given so don't allow a displacement.
|
|
AllowDisp = FALSE;
|
|
*LineStr = 0;
|
|
LineNum = strtoul(LineStr + 1, &NumEnd, 0);
|
|
|
|
if (*NumEnd == '+')
|
|
{
|
|
// Setting the high bit of the line number
|
|
// tells dbghelp to search in at-or-greater mode.
|
|
// This may produce displacements so allow them.
|
|
LineNum |= 0x80000000;
|
|
AllowDisp = TRUE;
|
|
}
|
|
else if (*NumEnd == '~')
|
|
{
|
|
// Find the closest line number.
|
|
AllowDisp = TRUE;
|
|
}
|
|
else if (*NumEnd && *NumEnd != ' ' && *NumEnd != '\t')
|
|
{
|
|
error(SYNTAX);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AllowDisp = TRUE;
|
|
}
|
|
|
|
Line.SizeOfStruct = sizeof(Line);
|
|
Ret = FALSE;
|
|
|
|
// If this is a pure linenumber reference then we need to fill in
|
|
// the line information with a current location before doing
|
|
// the line-relative query.
|
|
if (*File == 0)
|
|
{
|
|
ADDR Pc;
|
|
|
|
if (Mod != NULL)
|
|
{
|
|
goto EH_Ret;
|
|
}
|
|
|
|
g_Machine->GetPC(&Pc);
|
|
if (!SymGetLineFromAddr64(g_CurrentProcess->Handle,
|
|
Flat(Pc),
|
|
&Disp,
|
|
&Line))
|
|
{
|
|
goto EH_Ret;
|
|
}
|
|
|
|
File = NULL;
|
|
}
|
|
|
|
// Establish any special symbol options requested.
|
|
SymSetOptions(NewSym);
|
|
|
|
Ret = SymGetLineFromName64(g_CurrentProcess->Handle, Mod,
|
|
File, LineNum, (PLONG)&Disp, &Line);
|
|
|
|
SymSetOptions(OldSym);
|
|
|
|
EH_Ret:
|
|
if (Mod != NULL)
|
|
{
|
|
*(File-1) = '!';
|
|
}
|
|
|
|
if (LineStr != NULL)
|
|
{
|
|
*LineStr = ':';
|
|
}
|
|
|
|
// Only return a match if it's exact or no line number was specified.
|
|
if (Ret && (Disp == 0 || AllowDisp))
|
|
{
|
|
*Offset = Line.Address;
|
|
return LINE_FOUND;
|
|
}
|
|
else if (Image != NULL)
|
|
{
|
|
return LINE_NOT_FOUND_IN_MODULE;
|
|
}
|
|
else
|
|
{
|
|
return LINE_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
void
|
|
ParseSrcOptCmd(
|
|
CHAR Cmd
|
|
)
|
|
{
|
|
char Cmd2;
|
|
DWORD Opt;
|
|
|
|
Cmd2 = PeekChar();
|
|
if (Cmd2 == 'l')
|
|
{
|
|
g_CurCmd++;
|
|
Opt = SRCOPT_LIST_LINE;
|
|
}
|
|
else if (Cmd2 == 'o')
|
|
{
|
|
g_CurCmd++;
|
|
Opt = SRCOPT_LIST_SOURCE_ONLY;
|
|
}
|
|
else if (Cmd2 == 's')
|
|
{
|
|
g_CurCmd++;
|
|
Opt = SRCOPT_LIST_SOURCE;
|
|
}
|
|
else if (Cmd2 == 't')
|
|
{
|
|
g_CurCmd++;
|
|
Opt = SRCOPT_STEP_SOURCE;
|
|
}
|
|
else if (Cmd2 == '0')
|
|
{
|
|
// Numeric options.
|
|
if (*(++g_CurCmd) != 'x')
|
|
{
|
|
error(SYNTAX);
|
|
}
|
|
else
|
|
{
|
|
g_CurCmd++;
|
|
Opt = (DWORD)HexValue(4);
|
|
}
|
|
}
|
|
else if (Cmd2 == '*')
|
|
{
|
|
g_CurCmd++;
|
|
// All.
|
|
Opt = 0xffffffff;
|
|
}
|
|
else if (Cmd2 != 0 && Cmd2 != ';')
|
|
{
|
|
error(SYNTAX);
|
|
}
|
|
else
|
|
{
|
|
// No character means display current settings.
|
|
Opt = 0;
|
|
}
|
|
|
|
ULONG OldSrcOpt = g_SrcOptions;
|
|
|
|
if (Cmd == '+')
|
|
{
|
|
g_SrcOptions |= Opt;
|
|
|
|
if ((SymGetOptions() & SYMOPT_LOAD_LINES) == 0)
|
|
{
|
|
WarnOut(" WARNING: Line information loading disabled\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_SrcOptions &= ~Opt;
|
|
}
|
|
|
|
if ((OldSrcOpt ^ g_SrcOptions) & SRCOPT_STEP_SOURCE)
|
|
{
|
|
NotifyChangeEngineState(DEBUG_CES_CODE_LEVEL,
|
|
(g_SrcOptions & SRCOPT_STEP_SOURCE) ?
|
|
DEBUG_LEVEL_SOURCE : DEBUG_LEVEL_ASSEMBLY,
|
|
TRUE);
|
|
}
|
|
|
|
dprintf("Source options are %x:\n", g_SrcOptions);
|
|
if (g_SrcOptions == 0)
|
|
{
|
|
dprintf(" None\n");
|
|
}
|
|
else
|
|
{
|
|
if (g_SrcOptions & SRCOPT_STEP_SOURCE)
|
|
{
|
|
dprintf(" %2x/t - Step/trace by source line\n",
|
|
SRCOPT_STEP_SOURCE);
|
|
}
|
|
if (g_SrcOptions & SRCOPT_LIST_LINE)
|
|
{
|
|
dprintf(" %2x/l - List source line for LN and prompt\n",
|
|
SRCOPT_LIST_LINE);
|
|
}
|
|
if (g_SrcOptions & SRCOPT_LIST_SOURCE)
|
|
{
|
|
dprintf(" %2x/s - List source code at prompt\n",
|
|
SRCOPT_LIST_SOURCE);
|
|
}
|
|
if (g_SrcOptions & SRCOPT_LIST_SOURCE_ONLY)
|
|
{
|
|
dprintf(" %2x/o - Only show source code at prompt\n",
|
|
SRCOPT_LIST_SOURCE_ONLY);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ParseSrcLoadCmd(
|
|
void
|
|
)
|
|
{
|
|
LPSTR Semi;
|
|
PSRCFILE SrcFile;
|
|
char Cur;
|
|
BOOL Unload;
|
|
|
|
// Check for an unload request.
|
|
Unload = FALSE;
|
|
if (*g_CurCmd == '-')
|
|
{
|
|
g_CurCmd++;
|
|
Unload = TRUE;
|
|
}
|
|
|
|
while ((Cur = *g_CurCmd) == ' ' || Cur == '\t')
|
|
{
|
|
g_CurCmd++;
|
|
}
|
|
|
|
if (Cur == 0 || Cur == ';')
|
|
{
|
|
error(SYNTAX);
|
|
}
|
|
|
|
// Look for a semicolon, otherwise assume the whole command
|
|
// line is the file path.
|
|
|
|
Semi = strchr(g_CurCmd, ';');
|
|
if (Semi != NULL)
|
|
{
|
|
*Semi = 0;
|
|
}
|
|
|
|
if (Unload)
|
|
{
|
|
if (UnloadSrcFile(g_CurCmd))
|
|
{
|
|
dprintf("Unloaded '%s'\n", g_CurCmd);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SrcFile = FindSrcFile(g_CurCmd);
|
|
if (SrcFile == NULL)
|
|
{
|
|
dprintf("Unable to load '%s'\n", g_CurCmd);
|
|
}
|
|
}
|
|
|
|
if (Semi != NULL)
|
|
{
|
|
*Semi = ';';
|
|
g_CurCmd = Semi;
|
|
}
|
|
else
|
|
{
|
|
g_CurCmd += strlen(g_CurCmd);
|
|
}
|
|
|
|
if (!Unload && SrcFile != NULL)
|
|
{
|
|
g_CurSrcFile = SrcFile;
|
|
g_CurSrcLine = 1;
|
|
}
|
|
}
|
|
|
|
void
|
|
ParseSrcListCmd(
|
|
CHAR Cmd
|
|
)
|
|
{
|
|
LONG First, Count;
|
|
char Cur;
|
|
ULONG OldBase;
|
|
ADDR Addr;
|
|
ULONG Mark;
|
|
|
|
if (Cmd == '.')
|
|
{
|
|
g_CurCmd++;
|
|
|
|
PDEBUG_SCOPE Scope = GetCurrentScope();
|
|
if (Scope->Frame.InstructionOffset)
|
|
{
|
|
// List current frame
|
|
ADDRFLAT(&Addr, Scope->Frame.InstructionOffset);
|
|
}
|
|
else
|
|
{
|
|
// List at PC.
|
|
g_Machine->GetPC(&Addr);
|
|
}
|
|
Cmd = 'a';
|
|
}
|
|
else if (Cmd == 'a')
|
|
{
|
|
g_CurCmd++;
|
|
|
|
// List at address, so get an address.
|
|
GetAddrExpression(SEGREG_CODE, &Addr);
|
|
|
|
// Search for and consume trailing ,
|
|
while ((Cur = *g_CurCmd) == ' ' || Cur == '\t')
|
|
{
|
|
g_CurCmd++;
|
|
}
|
|
if (Cur == ',')
|
|
{
|
|
Cur = *++g_CurCmd;
|
|
if (Cur == 0 || Cur == ';')
|
|
{
|
|
error(SYNTAX);
|
|
}
|
|
}
|
|
}
|
|
else if (Cmd == 'c')
|
|
{
|
|
g_CurCmd++;
|
|
|
|
if (g_CurSrcFile != NULL)
|
|
{
|
|
dprintf("Current: %s(%d)\n", g_CurSrcFile->File, g_CurSrcLine);
|
|
}
|
|
else
|
|
{
|
|
dprintf("No current source file\n");
|
|
}
|
|
return;
|
|
}
|
|
|
|
while ((Cur = *g_CurCmd) == ' ' || Cur == '\t')
|
|
{
|
|
g_CurCmd++;
|
|
}
|
|
|
|
// Force base 10 for linenumbers.
|
|
|
|
OldBase = g_DefaultRadix;
|
|
g_DefaultRadix = 10;
|
|
|
|
if (Cur == 0 || Cur == ';')
|
|
{
|
|
First = Cmd == 'a' ? -4 : g_CurSrcLine;
|
|
Count = 10;
|
|
}
|
|
else if (Cur == ',')
|
|
{
|
|
First = Cmd == 'a' ? -4 : g_CurSrcLine;
|
|
g_CurCmd++;
|
|
Count = (ULONG)GetExpression();
|
|
}
|
|
else
|
|
{
|
|
First = (ULONG)GetExpression();
|
|
if (*g_CurCmd == ',')
|
|
{
|
|
g_CurCmd++;
|
|
Count = (ULONG)GetExpression();
|
|
}
|
|
else
|
|
{
|
|
Count = 10;
|
|
}
|
|
}
|
|
|
|
g_DefaultRadix = OldBase;
|
|
|
|
if (Count < 1)
|
|
{
|
|
error(SYNTAX);
|
|
}
|
|
|
|
Mark = 0;
|
|
|
|
if (Cmd == 'a')
|
|
{
|
|
DWORD Disp;
|
|
IMAGEHLP_LINE64 Line;
|
|
PSRCFILE SrcFile;
|
|
|
|
// Listing from the source file that Addr is in.
|
|
|
|
Line.SizeOfStruct = sizeof(Line);
|
|
if (!SymGetLineFromAddr64(g_CurrentProcess->Handle,
|
|
Flat(Addr),
|
|
&Disp,
|
|
&Line))
|
|
{
|
|
return;
|
|
}
|
|
|
|
SrcFile = FindSrcFile(Line.FileName);
|
|
if (SrcFile == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
g_CurSrcFile = SrcFile;
|
|
g_CurSrcLine = Line.LineNumber;
|
|
Mark = Line.LineNumber;
|
|
}
|
|
|
|
if (g_CurSrcFile == NULL)
|
|
{
|
|
dprintf("No current source file\n");
|
|
return;
|
|
}
|
|
|
|
// Address list commands are always relative,
|
|
// as are negative starting positions.
|
|
if (Cmd == 'a' || First < 0)
|
|
{
|
|
g_CurSrcLine += First;
|
|
}
|
|
else
|
|
{
|
|
g_CurSrcLine = First;
|
|
}
|
|
|
|
OutputSrcLines(g_CurSrcFile, g_CurSrcLine, g_CurSrcLine+Count-1, Mark);
|
|
|
|
g_CurSrcLine += Count;
|
|
}
|
|
|
|
void
|
|
ParseOciSrcCmd(
|
|
void
|
|
)
|
|
{
|
|
if (PeekChar() != ';' && *g_CurCmd)
|
|
{
|
|
ULONG64 Val1 = GetExpression();
|
|
ULONG64 Val2 = 0;
|
|
|
|
if (PeekChar() != ';' && *g_CurCmd)
|
|
{
|
|
Val2 = GetExpression();
|
|
}
|
|
else
|
|
{
|
|
Val2 = (Val1 + 1) / 2;
|
|
Val1 -= Val2;
|
|
}
|
|
|
|
g_OciSrcBefore = (ULONG)Val1;
|
|
g_OciSrcAfter = (ULONG)Val2;
|
|
}
|
|
|
|
if ((g_SrcOptions & SRCOPT_LIST_SOURCE) == 0)
|
|
{
|
|
WarnOut("WARNING: Source line display is disabled\n");
|
|
}
|
|
|
|
dprintf("At the prompt, display %d source lines before and %d after\n",
|
|
g_OciSrcBefore, g_OciSrcAfter);
|
|
}
|
|
|
|
void
|
|
ParseLines(PSTR Args)
|
|
{
|
|
ULONG NewOpts = g_SymOptions ^ SYMOPT_LOAD_LINES;
|
|
|
|
while (Args && *Args)
|
|
{
|
|
while (*Args == ' ' || *Args == '\t')
|
|
{
|
|
Args++;
|
|
}
|
|
|
|
if (*Args == '-' || *Args == '/')
|
|
{
|
|
Args++;
|
|
switch(*Args++)
|
|
{
|
|
case 'd':
|
|
NewOpts &= ~SYMOPT_LOAD_LINES;
|
|
break;
|
|
case 'e':
|
|
NewOpts |= SYMOPT_LOAD_LINES;
|
|
break;
|
|
case 't':
|
|
// Toggle, already done.
|
|
break;
|
|
default:
|
|
error(SYNTAX);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
SetSymOptions(NewOpts);
|
|
|
|
if (g_SymOptions & SYMOPT_LOAD_LINES)
|
|
{
|
|
dprintf("Line number information will be loaded\n");
|
|
}
|
|
else
|
|
{
|
|
dprintf("Line number information will not be loaded\n");
|
|
}
|
|
}
|
|
|
|
void
|
|
ChangeSrcPath(
|
|
PSTR Args,
|
|
BOOL Append
|
|
)
|
|
{
|
|
while (*Args == ' ' || *Args == '\t')
|
|
{
|
|
Args++;
|
|
}
|
|
|
|
if (*Args != 0)
|
|
{
|
|
if (ChangePath(&g_SrcPath, Args, Append,
|
|
DEBUG_CSS_PATHS) != S_OK)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (g_SrcPath == NULL)
|
|
{
|
|
dprintf("No source search path\n");
|
|
}
|
|
else
|
|
{
|
|
dprintf("Source search path is: %s\n", g_SrcPath);
|
|
CheckPath(g_SrcPath);
|
|
}
|
|
}
|
|
|
|
void
|
|
ChangeExePath(
|
|
PSTR Args,
|
|
BOOL Append
|
|
)
|
|
{
|
|
while (*Args == ' ' || *Args == '\t')
|
|
{
|
|
Args++;
|
|
}
|
|
|
|
if (*Args != 0)
|
|
{
|
|
if (ChangePath(&g_ExecutableImageSearchPath, Args, Append,
|
|
DEBUG_CSS_PATHS) != S_OK)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (g_ExecutableImageSearchPath == NULL)
|
|
{
|
|
dprintf("No exectutable image search path\n");
|
|
}
|
|
else
|
|
{
|
|
dprintf("Executable image search path is: %s\n",
|
|
g_ExecutableImageSearchPath);
|
|
CheckPath(g_ExecutableImageSearchPath);
|
|
}
|
|
}
|