Search code examples
c++windowsdebuggingstacknames

Stackwalk get Function Name, Line number and File Name, in debug


Quick summary

In a nut shell i wish to access the debug information regarding the stack, preferably for passing information to Logger.

I wish for the information to tell me the Function Name, Line Number and File Name.

I've got the symbols and i'm attempting to access the junk values in them and turn them in to English. However nothing seems to work.

I have commented the code for people to read and see if they can help me effectively walk the stack to pull the information out i need.

  • So far i can point out SymGetModuleBase() does not return a positive number only 0, according to MSDN it fails if returns 0. Which is correct as it returns a memory address.

  • SymGetSymFromAddr() fails to return true, which i'm assuming gets the name of the stack frame/function

  • SymGetLineFromAddr() goes on to fail as well and doesn't return the line number location in the file and also doesn't gather the file path.

I believe this is due to the process parameter being invalid. I will elaborate below.

Attempts to locate and fix the problem

  • I have read the MSDN documentation repeatedly and feel like i'm banging my head off the wall, i've done pretty much what it said and i feel like it's just not working.

  • However i have noticed SymInitialize() should be called prior to attempting this, which i do call. This changed the GetLastError() value from 6 ERROR_INVALID_HANDLE to 0 ERROR_SUCCESS. Yet SymGetModuleBase() still returns 0 no matter if SymInitialize() although GetLastError() reports different error codes depending on SymInitialize() use. It should return a valid virtual memory address this is where i think the main problem lies in the code.

  • HANDLE process = ::GetCurrentProcess(); this line in the code below returns 0xffffffffffffffff very suspect if you ask me. This should return a pseudo virtual memory address but it to me anyway looks like a false result. This happens every time i run the program which leads me to think ::GetCurrentProcess() this is either got a bug, or doesn't work somehow. According to MSDN this is the correct a up to date way of getting the current process and i don't know how to get a valid HANDLE to a the process another way. So i can't pass the first parameter in SymGetModuleBase() the correct process, although i maybe wrong.

Full code for the function

void Logger::WriteStackFrames(log::TextColor tc)
{
    // Initalize some memory
    DWORD                           machine = IMAGE_FILE_MACHINE_AMD64;
    HANDLE                          process = ::GetCurrentProcess();
    HANDLE                          thread = GetCurrentThread();

    // Initalize more memory
    CONTEXT                         context;
    STACKFRAME                      stack_frame;

    // Set some memory
    memset(&context, 0, sizeof(CONTEXT));
    memset(&stack_frame, 0, sizeof(STACKFRAME));

    // Capture the context
    RtlCaptureContext(&context);

    // Initalize a few things here and there
    stack_frame.AddrPC.Offset       = context.Rip;
    stack_frame.AddrPC.Mode         = AddrModeFlat;
    stack_frame.AddrStack.Offset    = context.Rsp;
    stack_frame.AddrStack.Mode      = AddrModeFlat;
    stack_frame.AddrFrame.Offset    = context.Rbp;
    stack_frame.AddrFrame.Mode      = AddrModeFlat;

    // Randomly saw this was supposed to be called prior to StackWalk so tried it
    if (!SymInitialize(process, 0, false))
    {
        wprintf(L"SymInitialize unable to find process!! Error: %d\r\n", GetLastError());
    }

    for (ULONG frame = 0; ; frame++)
    {
        // Set text color
        SetTextColor(tc);

        // Check for frames
        BOOL result = StackWalk(machine, process, thread, &stack_frame, &context, 0,
            SymFunctionTableAccess, SymGetModuleBase, 0);

        // Get memory address of base module. Returns 0 although when SymInitialize is called before it the GetLastError returns 0 without return 6
        DWORD64 module_base = SymGetModuleBase(process, stack_frame.AddrPC.Offset);
        if (module_base == 0) {
            wprintf(L"SymGetModuleBase is unable to get virutal address!! Error: %d\r\n", GetLastError());
        }

        // Initalize more memory
        MODULEINFO                  module_info;
        SecureZeroMemory(&module_info, sizeof(MODULEINFO));

        // Get the file name of the file containing the function
        TCHAR module_buffer[log::MaxPath];
        DWORD mod_file = GetModuleFileName((HINSTANCE)module_base, module_buffer, log::MaxPath);
        if ((module_base != 0) && (mod_file != 0))
        {
            module_info.module_name = module_buffer;
        }

        // Initalize more memory and clear it out
        PIMAGEHLP_SYMBOL64      symbol;
        IMAGEHLP_LINE64         line_num;
        SecureZeroMemory(&symbol, sizeof(PIMAGEHLP_SYMBOL64));
        SecureZeroMemory(&symbol, sizeof(IMAGEHLP_LINE64));

        // Get the symbol
        TCHAR symbol_buffer[log::MaxPath];
        symbol = (PIMAGEHLP_SYMBOL)symbol_buffer;
        symbol->SizeOfStruct = (sizeof(IMAGEHLP_SYMBOL) + log::MaxPath);
        symbol->MaxNameLength = 254;

        // Attempt to get name from symbol (fails)
        LPSTR name_buffer = new CHAR[254];
        if (SymGetSymFromAddr(process, stack_frame.AddrPC.Offset, 0, symbol))
        {
            name_buffer = symbol->Name;
        }

        // Set the size of something
        DWORD offset = 0;
        line_num.SizeOfStruct = sizeof(IMAGEHLP_LINE64);

        // Attempt to get the line and file name of where the symbol is
        if (SymGetLineFromAddr(process, stack_frame.AddrPC.Offset, &offset, &line_num))
        {
            module_info.line = line_num.LineNumber;
            module_info.file = line_num.FileName;
        }

        // Initalize memory
        LPWSTR console_message = new TCHAR[log::MaxMsgLength];
        LPWSTR file_message = new TCHAR[log::MaxMsgLength];

        // Set some strings
        swprintf(console_message, log::MaxMsgLength, L">> Frame %02lu: called from: %016X Stack: %016X Frame: %016X Address return: %016X\r\n",
            frame, stack_frame.AddrPC.Offset, stack_frame.AddrStack.Offset, stack_frame.AddrFrame.Offset, stack_frame.AddrReturn.Offset);
        swprintf(file_message, log::MaxMsgLength, L"Frame %02lu: called from: %016X Stack: %016X Frame: %016X Address return: %016X\r\n",
            frame, stack_frame.AddrPC.Offset, stack_frame.AddrStack.Offset, stack_frame.AddrFrame.Offset, stack_frame.AddrReturn.Offset);

        /* When the symbol can yield the name, line and file name the above strings
        will also include that information */
        // To go here . . . 

        // Write some strings
        wprintf(console_message);
        WriteAsync(file_message);

        // Delete some memory
        if (console_message) {
            delete[] console_message;   console_message = nullptr;
        }
        if (file_message) {
            delete[] file_message;  file_message = nullptr;
        }

        // If nothing else to do break loop
        if (!result) {
            break;
        }
    }
}

What i hope to achieve

Although i realize this will only work in debug mode that is fine, and i know i could write a macro using the __LINE__ __FUNCTION__ __FILE__ macros but that isn't what i'm looking for.

The results should be a wind up from the bottom stack showing the memory addresses of the calling PC, stack and frame. This works.

However it should also show me which the Name of the function, the Line number and the File path. This doesn't work.

FYI: I realize i need to add the code in to the generate the string and output it, but the code isn't capable of getting the information for the strings so that isn't coded in yet.

Please if anyone can help me, it would be fantastic all the code is focused around the "DbgHelp.h" windows file and most information is available on MSDN. So for the long question but i felt i should provide everything i know.


Solution

  • ::GetCurrentProcess() = 0xffffffffffffffff
    

    is not suspicious.