/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    linesym.c

Abstract:

    Source file and line support.

Author:

    Drew Bliss (drewb) 07-07-1997

Environment:

    User Mode

--*/

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <ntldr.h>

#include "private.h"
#include "symbols.h"
#include "globals.h"

// private version of qsort used to avoid compat problems on NT4 and win2k.
// code is published from base\crts
extern
void __cdecl dbg_qsort(void *, size_t, size_t,
                       int (__cdecl *) (const void *, const void *));

// #define DBG_LINES
// #define DBG_COFF_LINES
// #define DBG_ADDR_SEARCH
BOOL
diaAddLinesForAllMod(
    PMODULE_ENTRY mi
    );


#if defined(DBG_LINES) || defined(DBG_COFF_LINES) || defined(DBG_ADDR_SEARCH)
void __cdecl
DbgOut(PCSTR Format, ...)
{
    char Buf[512];
    va_list Args;

    va_start(Args, Format);
    _vsnprintf(Buf, sizeof(Buf), Format, Args);
    va_end(Args);
    OutputDebugStringA(Buf);
}
#endif

int
__cdecl
CompareLineAddresses(
    const void *v1,
    const void *v2
    )
{
    PSOURCE_LINE Line1 = (PSOURCE_LINE)v1;
    PSOURCE_LINE Line2 = (PSOURCE_LINE)v2;

    if (Line1->Addr < Line2->Addr) {
        return -1;
    } else if (Line1->Addr > Line2->Addr) {
        return 1;
    } else {
        return 0;
    }
}

VOID
AddSourceEntry(
    PMODULE_ENTRY mi,
    PSOURCE_ENTRY Src
    )
{
    PSOURCE_ENTRY SrcCur;

    // Overlap is currently permitted.
#if 0
    // Check for overlap between SOURCE_ENTRY address ranges.
    for (SrcCur = mi->SourceFiles;
         SrcCur != NULL;
         SrcCur = SrcCur->Next)
    {
        if (!(SrcCur->MinAddr > Src->MaxAddr ||
              SrcCur->MaxAddr < Src->MinAddr))
        {
            DbgOut("SOURCE_ENTRY overlap between %08I64X:%08I64X "
                   "and %08I64X:%08I64X\n",
                   Src->MinAddr, Src->MaxAddr,
                   SrcCur->MinAddr, SrcCur->MaxAddr);
        }
    }
#endif

    // Sort line info by address.
    dbg_qsort((PVOID)Src->LineInfo, Src->Lines, sizeof(Src->LineInfo[0]),
          CompareLineAddresses);

    // Link new source information into list, sorted by address
    // range covered by information.

    for (SrcCur = mi->SourceFiles;
         SrcCur != NULL;
         SrcCur = SrcCur->Next) {
        if (SrcCur->MinAddr > Src->MinAddr) {
            break;
        }
    }

    Src->Next = SrcCur;
    if (SrcCur == NULL) {
        if (mi->SourceFilesTail == NULL) {
            mi->SourceFiles = Src;
        } else {
            mi->SourceFilesTail->Next = Src;
        }
        Src->Prev = mi->SourceFilesTail;
        mi->SourceFilesTail = Src;
    } else {
        if (SrcCur->Prev == NULL) {
            mi->SourceFiles = Src;
        } else {
            SrcCur->Prev->Next = Src;
        }
        Src->Prev = SrcCur->Prev;
        SrcCur->Prev = Src;
    }

#ifdef DBG_LINES
    DbgOut("%08I64X %08I64X: %5d lines, '%s'\n",
           Src->MinAddr, Src->MaxAddr, Src->Lines, Src->File);
#endif
}

#define IS_SECTION_SYM(Sym) \
    ((Sym)->StorageClass == IMAGE_SYM_CLASS_STATIC && \
     (Sym)->Type == IMAGE_SYM_TYPE_NULL && \
     (Sym)->NumberOfAuxSymbols == 1)

BOOL
AddLinesForCoff(
    PMODULE_ENTRY mi,
    PIMAGE_SYMBOL allSymbols,
    DWORD numberOfSymbols,
    PIMAGE_LINENUMBER LineNumbers
    )
{
    PIMAGE_LINENUMBER *SecLines;
    BOOL Ret = FALSE;
    PIMAGE_SECTION_HEADER sh;
    ULONG i;
    PIMAGE_SYMBOL Symbol;
    ULONG LowestPointer;

    // Allocate some space for per-section data.
    SecLines = (PIMAGE_LINENUMBER *)MemAlloc(sizeof(PIMAGE_LINENUMBER)*mi->NumSections);
    if (SecLines == NULL) {
        return FALSE;
    }

    //
    // Add line number information for file groups if such
    // groups exist.
    //

    // First locate the lowest file offset for linenumbers.  This
    // is necessary to be able to compute relative linenumber pointers
    // in split images because currently the pointers aren't updated
    // during stripping.
    sh = mi->SectionHdrs;
    LowestPointer = 0xffffffff;
    for (i = 0; i < mi->NumSections; i++, sh++) {
        if (sh->NumberOfLinenumbers > 0 &&
            sh->PointerToLinenumbers != 0 &&
            sh->PointerToLinenumbers < LowestPointer)
        {
            LowestPointer = sh->PointerToLinenumbers;
        }
    }

    if (LowestPointer == 0xffffffff) {
        goto EH_FreeSecLines;
    }

    sh = mi->SectionHdrs;
    for (i = 0; i < mi->NumSections; i++, sh++) {
        if (sh->NumberOfLinenumbers > 0 &&
            sh->PointerToLinenumbers != 0)
        {
            SecLines[i] = (PIMAGE_LINENUMBER)
                (sh->PointerToLinenumbers - LowestPointer + (DWORD_PTR)LineNumbers);

#ifdef DBG_COFF_LINES
            DbgOut("Section %d: %d lines at %08X\n",
                   i, sh->NumberOfLinenumbers, SecLines[i]);
#endif
        } else {
            SecLines[i] = NULL;
        }
    }

    // Look for a file symbol.
    Symbol = allSymbols;
    for (i = 0; i < numberOfSymbols; i++) {
        if (Symbol->StorageClass == IMAGE_SYM_CLASS_FILE) {
            break;
        }

        i += Symbol->NumberOfAuxSymbols;
        Symbol += 1+Symbol->NumberOfAuxSymbols;
    }

    // If no file symbols were found, don't attempt to add line
    // number information.  Something could be done with the raw
    // linenumber info in the image (if it exists) but this probably
    // isn't an important enough case to worry about.

    while (i < numberOfSymbols) {
        ULONG iNextFile, iAfterFile;
        ULONG iCur, iSym;
        PIMAGE_SYMBOL SymAfterFile, CurSym;
        PIMAGE_AUX_SYMBOL AuxSym;
        ULONG Lines;
        ULONG MinAddr, MaxAddr;
        LPSTR FileName;
        ULONG FileNameLen;

#ifdef DBG_COFF_LINES
        DbgOut("%3X: '%s', %X\n", i, Symbol+1, Symbol->Value);
#endif

        // A file symbol's Value is the index of the next file symbol.
        // In between the two file symbols there may be static
        // section symbols which give line number counts for all
        // the line numbers in the file.
        // The file chain can be NULL terminated or a circular list,
        // in which case this code assumes the end comes when the
        // list wraps around.

        if (Symbol->Value == 0 || Symbol->Value <= i) {
            iNextFile = numberOfSymbols;
        } else {
            iNextFile = Symbol->Value;
        }

        // Compute the index of the first symbol after the current file
        // symbol.
        iAfterFile = i+1+Symbol->NumberOfAuxSymbols;
        SymAfterFile = Symbol+1+Symbol->NumberOfAuxSymbols;

        // Look for section symbols and count up the number of linenumber
        // references, the min address and the max address.
        CurSym = SymAfterFile;
        iCur = iAfterFile;
        Lines = 0;
        MinAddr = 0xffffffff;
        MaxAddr = 0;
        while (iCur < iNextFile) {
            DWORD Addr;

            if (IS_SECTION_SYM(CurSym) &&
                SecLines[CurSym->SectionNumber-1] != NULL)
            {
                AuxSym = (PIMAGE_AUX_SYMBOL)(CurSym+1);

                Lines += AuxSym->Section.NumberOfLinenumbers;

                Addr = (ULONG)(CurSym->Value+mi->BaseOfDll);

#ifdef DBG_COFF_LINES
                DbgOut("    Range %08X %08X, min %08X max %08X\n",
                       Addr, Addr+AuxSym->Section.Length-1,
                       MinAddr, MaxAddr);
#endif

                if (Addr < MinAddr) {
                    MinAddr = Addr;
                }
                Addr += AuxSym->Section.Length-1;
                if (Addr > MaxAddr) {
                    MaxAddr = Addr;
                }
            }

            iCur += 1+CurSym->NumberOfAuxSymbols;
            CurSym += 1+CurSym->NumberOfAuxSymbols;
        }

        if (Lines > 0) {
            PSOURCE_ENTRY Src;
            PSOURCE_LINE SrcLine;
            ULONG iLine;

            // We have a filename and some linenumber information,
            // so create a SOURCE_ENTRY and fill it in.

            FileName = (LPSTR)(Symbol+1);
            FileNameLen = strlen(FileName);

            Src = (PSOURCE_ENTRY)MemAlloc(sizeof(SOURCE_ENTRY)+
                                          sizeof(SOURCE_LINE)*Lines+
                                          FileNameLen+1);
            if (Src == NULL) {
                goto EH_FreeSecLines;
            }

            Src->ModuleId = 0;
            Src->MinAddr = MinAddr;
            Src->MaxAddr = MaxAddr;
            Src->Lines = Lines;

            SrcLine = (PSOURCE_LINE)(Src+1);
            Src->LineInfo = SrcLine;

            // Now that we've got a place to put linenumber information,
            // retraverse the section symbols and grab COFF linenumbers
            // from the appropriate sections and format them into
            // the generic format.
            CurSym = SymAfterFile;
            iCur = iAfterFile;
            while (iCur < iNextFile) {
                if (IS_SECTION_SYM(CurSym) &&
                    SecLines[CurSym->SectionNumber-1] != NULL) {
                    PIMAGE_LINENUMBER CoffLine;

                    AuxSym = (PIMAGE_AUX_SYMBOL)(CurSym+1);
                    CoffLine = SecLines[CurSym->SectionNumber-1];

#ifdef DBG_COFF_LINES
                    DbgOut("    %d lines at %08X\n",
                           AuxSym->Section.NumberOfLinenumbers,
                           CoffLine);
#endif

                    for (iLine = 0;
                         iLine < AuxSym->Section.NumberOfLinenumbers;
                         iLine++)
                    {
                        SrcLine->Addr = CoffLine->Type.VirtualAddress+
                            mi->BaseOfDll;
                        SrcLine->Line = CoffLine->Linenumber;
                        CoffLine++;
                        SrcLine++;
                    }

                    SecLines[CurSym->SectionNumber-1] = CoffLine;
                }

                iCur += 1+CurSym->NumberOfAuxSymbols;
                CurSym += 1+CurSym->NumberOfAuxSymbols;
            }

            // Stick file name at the very end of the data block so
            // it doesn't interfere with alignment.
            Src->File = (LPSTR)SrcLine;
            memcpy(Src->File, FileName, FileNameLen+1);

            AddSourceEntry(mi, Src);

            // This routine is successful as long as it adds at least
            // one new source entry.
            Ret = TRUE;
        }

        // After the loops above iCur and CurSym refer to the next
        // file symbol, so update the loop counters from them.
        i = iCur;
        Symbol = CurSym;
    }

 EH_FreeSecLines:
    MemFree(SecLines);

    return Ret;
}

BOOL
AddLinesForOmfSourceModule(
    PMODULE_ENTRY mi,
    BYTE *Base,
    OMFSourceModule *OmfSrcMod,
    PVOID PdbModule
    )
{
    BOOL Ret;
    ULONG iFile;

    Ret = FALSE;

    for (iFile = 0; iFile < (ULONG)OmfSrcMod->cFile; iFile++) {
        OMFSourceFile *OmfSrcFile;
        BYTE OmfFileNameLen;
        LPSTR OmfFileName;
        PULONG OmfAddrRanges;
        OMFSourceLine *OmfSrcLine;
        ULONG iSeg;
        PSOURCE_ENTRY Src;
        PSOURCE_ENTRY Seg0Src;
        PSOURCE_LINE SrcLine;
        ULONG NameAllocLen;

        OmfSrcFile = (OMFSourceFile *)(Base+OmfSrcMod->baseSrcFile[iFile]);

        // The baseSrcLn array of offsets is immediately followed by
        // SVA pairs which define the address ranges for the segments.
        OmfAddrRanges = &OmfSrcFile->baseSrcLn[OmfSrcFile->cSeg];

        // The name length and data immediately follows the address
        // range information.
        OmfFileName = (LPSTR)(OmfAddrRanges+2*OmfSrcFile->cSeg)+1;
        OmfFileNameLen = *(BYTE *)(OmfFileName-1);

        // The compiler can potentially generate a lot of segments
        // per file.  The segments within a file have disjoint
        // address ranges as long as they are treated as separate
        // SOURCE_ENTRYs.  If all segments for a particular file get
        // combined into one SOURCE_ENTRY it leads to address range overlap
        // because of combining non-contiguous segments.  Allocating
        // a SOURCE_ENTRY per segment isn't that bad, particularly since
        // the name information only needs to be allocated in the first
        // entry for a file and the rest can share it.

        NameAllocLen = OmfFileNameLen+1;

        for (iSeg = 0; iSeg < (ULONG)OmfSrcFile->cSeg; iSeg++) {
            PULONG Off;
            PUSHORT Ln;
            ULONG iLine;
            PIMAGE_SECTION_HEADER sh;

            OmfSrcLine = (OMFSourceLine *)(Base+OmfSrcFile->baseSrcLn[iSeg]);

            Src = (PSOURCE_ENTRY)MemAlloc(sizeof(SOURCE_ENTRY)+
                                          sizeof(SOURCE_LINE)*OmfSrcLine->cLnOff+
                                          NameAllocLen);
            if (Src == NULL) {
                return Ret;
            }

            Src->ModuleId = (ULONG) (ULONG64) PdbModule;
            Src->Lines = OmfSrcLine->cLnOff;

            sh = &mi->SectionHdrs[OmfSrcLine->Seg-1];

            // Process raw segment limits into current addresses.
            Src->MinAddr = mi->BaseOfDll+sh->VirtualAddress+(*OmfAddrRanges++);
            Src->MaxAddr = mi->BaseOfDll+sh->VirtualAddress+(*OmfAddrRanges++);

            // Retrieve line numbers and offsets from raw data and
            // process them into current pointers.

            SrcLine = (SOURCE_LINE *)(Src+1);
            Src->LineInfo = SrcLine;

            Off = (ULONG *)&OmfSrcLine->offset[0];
            Ln = (USHORT *)(Off+OmfSrcLine->cLnOff);

            for (iLine = 0; iLine < OmfSrcLine->cLnOff; iLine++) {
                SrcLine->Line = *Ln++;
                SrcLine->Addr = (*Off++)+mi->BaseOfDll+sh->VirtualAddress;

                // Line symbol information names the IA64 bundle
                // syllables with 0,1,2 whereas the debugger expects
                // 0,4,8.  Convert.
                if (mi->MachineType == IMAGE_FILE_MACHINE_IA64 &&
                    (SrcLine->Addr & 3)) {
                    SrcLine->Addr = (SrcLine->Addr & ~3) |
                        ((SrcLine->Addr & 3) << 2);
                }
                
                SrcLine++;
            }

            if (iSeg == 0) {
                // Stick file name at the very end of the data block so
                // it doesn't interfere with alignment.
                Src->File = (LPSTR)SrcLine;
                memcpy(Src->File, OmfFileName, OmfFileNameLen);
                Src->File[OmfFileNameLen] = 0;

                // Later segments will share this initial name storage
                // space so they don't need to alloc their own.
                NameAllocLen = 0;
                Seg0Src = Src;
            } else {
                Src->File = Seg0Src->File;
            }

            AddSourceEntry(mi, Src);

            // This routine is successful as long as it adds at least
            // one new source entry.
            Ret = TRUE;
        }
    }

    return Ret;
}


VOID
FillLineInfo(
    PSOURCE_ENTRY Src,
    PSOURCE_LINE SrcLine,
    PIMAGEHLP_LINE64 Line
    )
{
    Line->Key = (PVOID)SrcLine;
    Line->LineNumber = SrcLine->Line;
    Line->FileName = Src->File;
    Line->Address = SrcLine->Addr;
}

PSOURCE_LINE
FindLineInSource(
    PSOURCE_ENTRY Src,
    DWORD64 Addr
    )
{
    int Low, Middle, High;
    PSOURCE_LINE SrcLine;

    Low = 0;
    High = Src->Lines-1;

    while (High >= Low) {
        Middle = (High <= Low) ? Low : (Low + High) >> 1;
        SrcLine = &Src->LineInfo[Middle];

#ifdef DBG_ADDR_SEARCH
        DbgOut("    Checking %4d:%x`%08X\n", Middle,
               (ULONG)(SrcLine->Addr>>32), (ULONG)SrcLine->Addr);
#endif

        if (Addr < SrcLine->Addr) {
            High = Middle-1;
        }
        else if (Middle < (int)Src->Lines-1 &&
                 Addr >= (SrcLine+1)->Addr) {
            Low = Middle+1;
        } else {
            PSOURCE_LINE HighLine;
            
            // Find the highest source line with this offset.
            // Source lines are sorted by offset so the highest
            // source line could be before or after this one.
            
            while (SrcLine > Src->LineInfo &&
                   (SrcLine - 1)->Addr == SrcLine->Addr) {
                SrcLine--;
            }
            HighLine = SrcLine;
            while (SrcLine < Src->LineInfo + Src->Lines - 1 &&
                   (++SrcLine)->Addr == HighLine->Addr) {
                if (SrcLine->Line > HighLine->Line) {
                    HighLine = SrcLine;
                }
            }
            return HighLine;
        }
    }

    return NULL;
}

PSOURCE_ENTRY
FindNextSourceEntryForAddr(
    PMODULE_ENTRY mi,
    DWORD64 Addr,
    PSOURCE_ENTRY SearchFrom
    )
{
    PSOURCE_ENTRY Src;

    Src = SearchFrom != NULL ? SearchFrom->Next : mi->SourceFiles;
    while (Src != NULL) {
        if (Addr < Src->MinAddr) {
            // Source files are kept sorted by increasing address so this
            // means that the address will not be found later and
            // we can stop checking.
            return NULL;
        } else if (Addr <= Src->MaxAddr) {
            // Found one.
            return Src;
        }
        Src = Src->Next;
    }

    return NULL;
}

BOOL
GetLineFromAddr(
    PMODULE_ENTRY mi,
    DWORD64 Addr,
    PDWORD Displacement,
    PIMAGEHLP_LINE64 Line
    )
{
    PSOURCE_ENTRY Src;
    DWORD Bias;
    DWORD64 srcAddr;

    if (mi == NULL) {
        return FALSE;
    }

    if (mi->dia)
        return diaGetLineFromAddr(mi, Addr, Displacement, Line);

    srcAddr = ConvertOmapToSrc( mi,
                               Addr,
                               &Bias,
                               (g.SymOptions & SYMOPT_OMAP_FIND_NEAREST) != 0
                               );

    if (srcAddr == 0) {
        return FALSE;
    }

    // We have successfully converted

    srcAddr += Bias;

    for (;;) {
        PSOURCE_ENTRY BestSrc;
        PSOURCE_LINE BestSrcLine;
        DWORD64 BestDisp;

        // Search through all the source entries that contain the given
        // address, looking for the line with the smallest displacement.

        BestDisp = 0xffffffffffffffff;
        BestSrc = NULL;
        Src = NULL;
        while (Src = FindNextSourceEntryForAddr(mi, srcAddr, Src)) {
            PSOURCE_LINE SrcLine;

#ifdef DBG_ADDR_SEARCH
            DbgOut("Found '%s' %d lines: %08I64X %08I64X for %08I64X\n",
                   Src->File, Src->Lines, Src->MinAddr, Src->MaxAddr, Addr);
#endif

            // Found a matching source entry, so look up the line
            // information.
            SrcLine = FindLineInSource(Src, srcAddr);
            if (SrcLine != NULL &&
                Addr-SrcLine->Addr < BestDisp) {
                BestDisp = Addr-SrcLine->Addr;

#ifdef DBG_ADDR_SEARCH
                DbgOut("  Best disp %I64X\n", BestDisp);
#endif

                BestSrc = Src;
                BestSrcLine = SrcLine;
                if (BestDisp == 0) {
                    break;
                }
            }
        }

        // Only accept displaced answers if there's no more symbol
        // information to load.
        if (BestSrc != NULL && BestDisp == 0) {
            FillLineInfo(BestSrc, BestSrcLine, Line);
            *Displacement = (ULONG)BestDisp;
            return TRUE;
        }

        return FALSE;
    }

    return FALSE;
}

PSOURCE_ENTRY
FindNextSourceEntryForFile(
    PMODULE_ENTRY mi,
    LPSTR FileStr,
    PSOURCE_ENTRY SearchFrom
    )
{
    PSOURCE_ENTRY Src;

    Src = SearchFrom != NULL ? SearchFrom->Next : mi->SourceFiles;
    while (Src != NULL)
    {
        if (SymMatchFileName(Src->File, FileStr, NULL, NULL))
        {
            return Src;
        }

        Src = Src->Next;
    }

    return NULL;
}

PSOURCE_ENTRY
FindPrevSourceEntryForFile(
    PMODULE_ENTRY mi,
    LPSTR FileStr,
    PSOURCE_ENTRY SearchFrom
    )
{
    PSOURCE_ENTRY Src;

    Src = SearchFrom != NULL ? SearchFrom->Prev : mi->SourceFilesTail;
    while (Src != NULL)
    {
        if (SymMatchFileName(Src->File, FileStr, NULL, NULL))
        {
            return Src;
        }

        Src = Src->Prev;
    }

    return NULL;
}

BOOL
FindLineByName(
    PMODULE_ENTRY mi,
    LPSTR FileName,
    DWORD LineNumber,
    PLONG Displacement,
    PIMAGEHLP_LINE64 Line
    )
{
    PSOURCE_ENTRY Src;
    BOOL TryLoad;
    BOOL AtOrGreater;

    if (mi == NULL)
    {
        return FALSE;
    }

    if (FileName == NULL)
    {
        // If no file was given then it's assumed that the file
        // is the same as for the line information passed in.
        FileName = Line->FileName;
    }

    // If the high bit of the line number is set
    // it means that the caller only wants lines at
    // or greater than the given line.
    AtOrGreater = (LineNumber & 0x80000000) != 0;
    LineNumber &= 0x7fffffff;
    
    if (mi->dia)
        if (diaGetLineFromName(mi, FileName, LineNumber, Displacement, Line))
            return TRUE;

    // We only lazy load here for symbols, and only if we're allowed to.
    TryLoad = mi->SymType == SymDia &&
        (g.SymOptions & SYMOPT_LOAD_LINES) != 0;

    for (;;)
    {
        ULONG Disp;
        ULONG BestDisp;
        PSOURCE_ENTRY BestSrc;
        PSOURCE_LINE BestSrcLine;

        //
        // Search existing source information for a filename match.
        // There can be multiple SOURCE_ENTRYs with the same filename,
        // so make sure and search them all for an exact match
        // before settling on an approximate match.
        //

        Src = NULL;
        BestDisp = 0x7fffffff;
        BestSrcLine = NULL;
        while (Src = FindNextSourceEntryForFile(mi, FileName, Src))
        {
            PSOURCE_LINE SrcLine;
            ULONG i;

            // Found a matching source entry, so look up the closest
            // line.  Line number info is sorted by address so the actual
            // line numbers can be in any order so we can't optimize
            // this linear search.

            SrcLine = Src->LineInfo;
            for (i = 0; i < Src->Lines; i++)
            {
                if (LineNumber > SrcLine->Line)
                {
                    if (AtOrGreater)
                    {
                        Disp = 0x7fffffff;
                    }
                    else
                    {
                        Disp = LineNumber-SrcLine->Line;
                    }
                }
                else
                {
                    Disp = SrcLine->Line-LineNumber;
                }

                if (Disp < BestDisp)
                {
                    BestDisp = Disp;
                    BestSrc = Src;
                    BestSrcLine = SrcLine;
                    if (Disp == 0)
                    {
                        break;
                    }
                }

                SrcLine++;
            }

            // If we found an exact match we can stop.
            if (BestDisp == 0)
            {
                break;
            }
        }

        // Only accept displaced answers if there's no more symbol
        // information to load.
        if (BestSrcLine != NULL && (BestDisp == 0 || !TryLoad))
        {
            FillLineInfo(BestSrc, BestSrcLine, Line);
            *Displacement = (LONG)(LineNumber-BestSrcLine->Line);
            return TRUE;
        }
        if (!TryLoad)
        {
            // There's no more line information to try and load so
            // we're out of luck.
            return FALSE;
        }

        TryLoad = FALSE;

        // There doesn't seem to be an easy way to look up a module by
        // filename.  It's possible to query by object filename, but
        // that can be much different from the source filename.
        // Just load the info all PDB modules.

        if (!diaAddLinesForAllMod(mi))
        {
            return FALSE;
        }

    }

    return FALSE;
}

#define LINE_ERROR 0xffffffff

ULONG
GetFileLineOffsets(
    PMODULE_ENTRY mi,
    LPSTR FileName,
    PDWORD64 Buffer,
    ULONG BufferLines
    )
{
    PSOURCE_ENTRY Src;
    ULONG HighestLine = 0;
    
    // This routine collects all line information in one pass so
    // there's no opportunity for lazy loading.  We have to
    // force lines to be loaded up front.
    if (mi->dia && (g.SymOptions & SYMOPT_LOAD_LINES) != 0) {
        if (!diaAddLinesForAllMod(mi)) {
            return LINE_ERROR;
        }
    }


    Src = NULL;
    while (Src = FindNextSourceEntryForFile(mi, FileName, Src)) {
        PSOURCE_LINE Line;
        ULONG i;
        ULONG Num;

        Line = Src->LineInfo;
        for (i = 0; i < Src->Lines; i++) {
            if (Line->Line > HighestLine) {
                HighestLine = Line->Line;
            }

            Num = Line->Line - 1;
            if (Num < BufferLines) {
                Buffer[Num] = Line->Addr;
            }

            Line++;
        }
    }

    return HighestLine;
}

ULONG
IMAGEAPI
SymGetFileLineOffsets64(
    IN  HANDLE                  hProcess,
    IN  LPSTR                   ModuleName,
    IN  LPSTR                   FileName,
    OUT PDWORD64                Buffer,
    IN  ULONG                   BufferLines
    )

/*++

Routine Description:

    This function locates the given file's line information
    and fills the given buffer with offsets for each
    line.  Buffer[Line - 1] is set to the offset for
    Line.  Buffer entries for lines that do not have information
    are left unchanged.

Arguments:

    hProcess            - Process handle, must have been previously registered
                          with SymInitialize.

    ModuleName          - Module name or NULL.

    FileName            - File name.

    Buffer              - Array of offsets to fill.

    BufferLines         - Number of entries in the Buffer array.

Return Value:

    0                   - No information was found.

    LINE_ERROR          - Failure during operation.  Call GetLastError to
                          discover the cause of the failure.

    Otherwise the return value is the highest line number found.

--*/

{
    PPROCESS_ENTRY      ProcessEntry;
    PMODULE_ENTRY       mi;
    ULONG               HighestLine = 0;
    PLIST_ENTRY         Next;

    __try {
        ProcessEntry = FindProcessEntry( hProcess );
        if (!ProcessEntry) {
            SetLastError( ERROR_INVALID_HANDLE );
            return LINE_ERROR;
        }

        if (ModuleName != NULL) {

            mi = FindModule(hProcess, ProcessEntry, ModuleName, TRUE);
            if (mi != NULL) {
                return GetFileLineOffsets(mi, FileName, Buffer, BufferLines);
            }

            SetLastError( ERROR_MOD_NOT_FOUND );
            return LINE_ERROR;
        }

        Next = ProcessEntry->ModuleList.Flink;
        if (Next) {
            while (Next != &ProcessEntry->ModuleList) {

                mi = CONTAINING_RECORD( Next, MODULE_ENTRY, ListEntry );
                Next = mi->ListEntry.Flink;

                if (!LoadSymbols(hProcess, mi, LS_QUALIFIED | LS_LOAD_LINES)) {
                    continue;
                }

                HighestLine = GetFileLineOffsets(mi, FileName, Buffer,
                                                 BufferLines);
                // This will break on lines found or LINE_ERROR.
                if (HighestLine > 0) {
                    break;
                }
            }
        }

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        ImagepSetLastErrorFromStatus( GetExceptionCode() );
        return LINE_ERROR;

    }

    return HighestLine;
}