HOWTO: Retrieve Line Number from CodeView Debug Information

ID: Q194921

The information in this article applies to:

SUMMARY

When a Windows dynamic-link library or executable is generated with Microsoft CodeView debug format, the source line number and module information are emitted by the linker in a data block. This article documents how to search this data block and retrieve the source line number and file name for a specified virtual address. The format of this data block is described in the CV4 Symbolic Debug Information Specification.

MORE INFORMATION

The CodeView debug information contains a table or data block named sstSrcModule. This table describes the source line number to address mapping for a module. Every module linked in an application or a dynamic- link library will have a table of this type. A module might be composed of either a single or multiple source file(s). Each source file in a module might contribute code to one or more segment(s). The following sample code analyzes the line/address mapping tables of all code segments and obtains the source line number and file name for a specified virtual address.

For detailed information on the format of the sstSrcModule table, please refer to the CV4 Symbolic Debug Specification. The Specification is in Microsoft Developer Network (MSDN) Library under Specifications\Technologies and Languages topic.

Sample Code

NOTE: NT4SymGetLineFromAddr will work only when the debugging information is either embedded within an executable (dynamic-link library) or created as a separate .dbg (Debug) file. This functionality is not supported when debugging information is placed in a .pdb (Program Database) file generated by Microsoft Visual C++, 32-bit Edition. Microsoft Windows NT 5.0 version of ImageHlp provides SymGetLineFromAddr API functionality. The NT4SymGetLineFromAddr function in the following code is necessary only for Windows NT 4.0 or any older version of ImageHlp.

    #include <windows.h>
    #include <imagehlp.h>
    #include <stdio.h>

    /* CV4 Directory header structure indicating size
       and number of subsection directory entries that
       follow.
    */ 
    struct CV4DirectoryHeader
    {
        WORD    cbDirHeader;
        WORD    cbDirEntry;
        DWORD   cDir;
        DWORD   IfoNextDir;
        DWORD   flags;
    };
    typedef struct CV4DirectoryHeader *PCV4DirectoryHeader;

    /* CV4 Directory entry structure specifying the subsection
       type, offset, and size.
    */ 
    struct CV4DirectoryEntry
    {
        WORD    subsection;
        WORD    iMod;
        DWORD   Ifo;
        DWORD   cb;
    };
    typedef struct CV4DirectoryEntry *PCV4DirectoryEntry;

    /* NT4SymGetLineFromAddr Public API. */ 
    BOOL NT4SymGetLineFromAddr(
                    IN  HANDLE                      hProcess,
                    IN  DWORD                       dwAddress,
                    OUT DWORD                       *pLineNumber,
                    OUT LPTSTR                      pFileName,
                    IN  DWORD                       nSize);

    /* Private Function - Locates a source module corresponding
       to an address. */ 
    BOOL LocateSrcModule(LPVOID pCV4DebugData, DWORD dwAddress,
                         DWORD *pLineNumber, LPTSTR pFileName,
                         DWORD nSize);

    /* Private Function - Retrieves source module information by
       analyzing line/address mapping tables in a module.
    */ 
    BOOL GetSrcModuleInfo(LPSTR ptr, DWORD dwAddress,
                          DWORD *pLineNumber, LPTSTR pFileName,
                          DWORD nSize);

    /*  The NT4SymGetLineFromAddr function locates the source line
        number and module name for the specified virtual address.

        BOOL NT4SymGetLineFromAddr(
                    IN  HANDLE                      hProcess,
                    IN  DWORD                       dwAddress,
                    OUT DWORD                       *pLineNumber,
                    OUT LPTSTR                      pFileName;
                    IN  DWORD                       nSize)

        Parameters

        hProcess - Handle to the process that was originally passed to
                   the SymInitialize function.

        dwAddress - Specifies the virtual address for which the
                    source line number and module name should be
                    located. The instruction at the specified address
                    does not have to be on a line boundary. If the
                    address comes after the beginning of a line and
                    before the end of the line, the beginning of the
                    source line is returned in pLineNumber.

        pLineNumber - Pointer to a DWORD to receive source line number.

        pFileName - Pointer to a buffer to receive source file name.

        nSize - Size of pFileName buffer, in characters.

        Return Values

            If the function succeeds, the return value is TRUE.

            If the function fails, the return value is FALSE.
            To retrieve extended error information, call GetLastError.
    */ 

    BOOL NT4SymGetLineFromAddr(
                IN  HANDLE                      hProcess,
                IN  DWORD                       dwAddress,
                OUT DWORD                       *pLineNumber,
                OUT LPTSTR                      pFileName,
                IN  DWORD                       nSize)
    {
        IMAGEHLP_MODULE             mi;
        PIMAGE_DEBUG_INFORMATION    pDebugInfo = 0;
        PIMAGE_DEBUG_DIRECTORY      pDebug;
        DWORD                       i, nEntries;
        BOOL                        bReturn = FALSE;
        char                        searchPath[4096];

        if (!dwAddress || !pLineNumber
            || !pFileName || !nSize)
        {
            SetLastError(ERROR_INVALID_PARAMETER);
            return FALSE;
        }

        *pFileName = 0;
        *pLineNumber = 0;

        /* Retrieve module information corresponding
           to the specified virtual address. */ 

        mi.SizeOfStruct = sizeof(mi);
        if (!SymGetModuleInfo(hProcess, dwAddress, &mi))
            return FALSE;

        if (mi.SymType == SymPdb)
        {
            SetLastError(ERROR_INVALID_FUNCTION);
            return FALSE;
        }

        bReturn = SymGetSearchPath(hProcess,
                        searchPath,
                        sizeof(searchPath));

        /* Map the debug information of the required module. */ 
        pDebugInfo = MapDebugInformation(0,
                        mi.ImageName,
                        bReturn ? searchPath : 0,
                        0);
        if (!pDebugInfo) return FALSE;

        /* Obtain pointer to debug directory. */ 
        pDebug = pDebugInfo->DebugDirectory;
        /* Number of entries in debug directory. */ 
        nEntries = pDebugInfo->NumberOfDebugDirectories;

        __try
        {
            for (i = 0; i < nEntries; i++)
            {
                /* Locate Codeview debug type. */ 
                if (pDebug->Type == IMAGE_DEBUG_TYPE_CODEVIEW)
                {
                    DWORD                   j;
                    PIMAGE_SECTION_HEADER   pSection;

                    pSection = pDebugInfo->Sections;
                    for (j = 0;
                         j < pDebugInfo->NumberOfSections;
                         j++)
                    {
                        /* Get the virtual address of executable
                           code section. */ 
                        if (pSection
                            && (pSection->Characteristics
                                    & IMAGE_SCN_MEM_EXECUTE))
                        {
                            bReturn = LocateSrcModule(
                                        (char*)pDebugInfo->MappedBase
                                          + pDebug->PointerToRawData,
                                        /* Calculate relative offset
                                           within the executable code
                                           section.
                                        */ 
                                        dwAddress
                                        - (pDebugInfo->ImageBase
                                           + pSection->VirtualAddress),
                                        pLineNumber,
                                        pFileName,
                                        nSize);
                            UnmapDebugInformation(pDebugInfo);
                            return bReturn;
                        }
                        pSection++;
                    }
                }
                pDebug = (PIMAGE_DEBUG_DIRECTORY)((char *)pDebug
                          + sizeof(IMAGE_DEBUG_DIRECTORY));
            }
            SetLastError(ERROR_INVALID_ADDRESS);
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            SetLastError(GetExceptionCode());
        }

        UnmapDebugInformation(pDebugInfo);
        return FALSE;
    }

    BOOL LocateSrcModule(LPVOID pCV4DebugData, DWORD dwAddress,
                         DWORD *pLineNumber, LPTSTR pFileName,
                         DWORD nSize)
    {
        char                *ptr;
        PCV4DirectoryHeader pHeader;
        PCV4DirectoryEntry  pEntry;
        WORD                i;

        ptr = (char*) pCV4DebugData;
        ptr = ptr + *(DWORD *)(ptr + sizeof(DWORD));

        /* CV4 Directory header. */ 
        pHeader = (PCV4DirectoryHeader) ptr;
        if (pHeader->cbDirHeader != sizeof(*pHeader))
        {
            SetLastError(ERROR_INVALID_ADDRESS);
            return FALSE;
        }
        pEntry = (PCV4DirectoryEntry) (ptr + pHeader->cbDirHeader);

        /* Search CV4 directory entries. */ 
        for (i = 0; i < pHeader->cDir; i++, pEntry++)
        {
            switch (pEntry->subsection)
            {
                /* Locate sstSrcModule data block. */ 
                case 0x0127:
                    if (GetSrcModuleInfo((char *)pCV4DebugData
                          + pEntry->Ifo,
                          dwAddress, pLineNumber, pFileName, nSize))
                    {
                        if (GetLastError() ==
                            ERROR_INSUFFICIENT_BUFFER)
                            return FALSE;
                        return TRUE;
                    }
                    break;
                default:
                    break;
            }
        }

        SetLastError(ERROR_INVALID_ADDRESS);
        return FALSE;
    }

    BOOL GetSrcModuleInfo(LPSTR ptr, DWORD dwAddress,
                          DWORD *pLineNumber, LPTSTR pFileName,
                          DWORD nSize)
    {
        WORD    i, j, cFile, cSegInMod;
        char    *pModule;
        DWORD   fileTableOffset;
        char    *baseSrcFile;
        char    *mapTable;

        SetLastError(0);

        *pFileName = 0;
        *pLineNumber = 0;

        pModule = ptr;

        /* Number of source files in a module. */ 
        cFile = *(WORD *)ptr;
        ptr += sizeof(WORD);

        /* Total number of code segments in a module. */ 
        cSegInMod = *(WORD *)ptr;
        ptr += sizeof(WORD);
        for (i = 0; i < cFile; i++)
        {
            WORD    cSegInFile;
            DWORD   mapTableOffset;
            LPTSTR  pFilePtr;
            WORD    fileNameLen;

            fileTableOffset = *(DWORD *)(ptr + i * sizeof(DWORD));
            baseSrcFile = pModule + fileTableOffset;

            /* Number of code segments in a source file. */ 
            cSegInFile = *(WORD *)baseSrcFile;
            baseSrcFile += (sizeof(WORD) + sizeof(WORD));
            for (j = 0; j < cSegInFile; j++)
            {
                mapTableOffset = *(DWORD *)(baseSrcFile
                                    + j * sizeof(DWORD));
                mapTable = pModule + mapTableOffset;
                {
                    WORD segIndex, nPairs, low, middle, high;

                    segIndex = *(WORD *)mapTable;
                    mapTable += sizeof(WORD);
                    nPairs = *(WORD *)mapTable;
                    mapTable += sizeof(WORD);

                    /* Analyze line/address mapping table. */ 
                    low = 0;
                    high = nPairs - 1;

                    while (high >= low)
                    {
                        DWORD offset, nextOffset;

                        middle = (low + high) / 2;
                        offset = *(DWORD*)(mapTable
                                    + middle * sizeof(DWORD));

                        if (dwAddress < offset)
                        {
                            if (middle < 1)
                                break;
                            high = middle - 1;
                            continue;
                        }

                        if (middle < (nPairs - 1))
                        {
                            nextOffset = *(DWORD*)(mapTable
                                + (middle+1) * sizeof(DWORD));

                            if (dwAddress >= nextOffset)
                            {
                                low = middle + 1;
                                if ((dwAddress > nextOffset)
                                    && (low == (nPairs - 1)))
                                    break;
                                continue;
                            }
                        }

                        *pLineNumber = *(WORD*)(mapTable
                                        + nPairs * sizeof(DWORD)
                                        + middle * sizeof(WORD));

                        fileNameLen = *(char *)(
                                        baseSrcFile
                                        + cSegInFile
                                          * 3 * sizeof(DWORD));
                        pFilePtr = (LPTSTR)(
                                    baseSrcFile
                                    + cSegInFile
                                      * 3 * sizeof(DWORD)
                                    + sizeof(char));
                        if (nSize <= fileNameLen)
                        {
                            SetLastError
                                (
                                ERROR_INSUFFICIENT_BUFFER
                                );
                        }
                        else
                        {
                            CopyMemory(pFileName,
                                     pFilePtr,
                                     fileNameLen);
                            pFileName[fileNameLen] = 0;
                        }
                        return TRUE;
                    }
                }
            }
        }

        return FALSE;
    }

   // NT4SymGetLineFromAddr Usage:

    DWORD ProcessException(PEXCEPTION_POINTERS ptr)
    {
        DWORD   exceptionAddress = 0;
    #ifdef  UNICODE
        WCHAR
    #else
        char
    #endif
            fileName[MAX_PATH+1];

        DWORD   lineNumber;

        if (ptr && ptr->ExceptionRecord
            && ptr->ExceptionRecord->ExceptionAddress)
        {
            exceptionAddress =
             (DWORD) ptr->ExceptionRecord->ExceptionAddress;
        }

        {
            HANDLE                      hProcess;

            hProcess = GetCurrentProcess();

            if (SymInitialize(hProcess, NULL, TRUE))
            {
                if (NT4SymGetLineFromAddr(
                        hProcess,
                        exceptionAddress,
                        &lineNumber,
                        fileName,
                        MAX_PATH))
                {
                    printf("\n\n0x%08x Address Maps to\n",
                        exceptionAddress);
                    printf("--------------------------\n");
                    printf("FileName %s\nLineNumber %d\n",
                        fileName, lineNumber);
                }
                else
                {
                    printf("\n\nNT4SymGetLineFromAddr failed"
                        " with an error : %d\n",
                        GetLastError());
                }
                SymCleanup(hProcess);
            }
        }

        return EXCEPTION_EXECUTE_HANDLER;
    }

    int main(int argc, char** argv)
    {
        char* ptr = 0;

        __try
        {
            *ptr = 0;
        }
        __except (ProcessException(GetExceptionInformation()))
        {
            printf("Exception %x occurred\n", GetExceptionCode());
        }
        __try
        {
            *ptr = 0;
        }
        __except (ProcessException(GetExceptionInformation()))
        {
            printf("Exception %x occurred\n", GetExceptionCode());
        }
        return 0;
    }

REFERENCES

CV4 Symbolic Debug Information Specification

Additional query words: imagehlp CV4 SymGetLineFromAddr linenumber debug

Keywords          : kbcode kbAPI kbImgHlp kbKernBase kbNTOS400 
Issue type        : kbhowto

Last Reviewed: October 31, 1998