Link to home
Start Free TrialLog in
Avatar of tpchang
tpchang

asked on

How to dump call stack in C++

Is there any way to dump the call stack in VC5? I am designing some debugging function that requires to dump the stack of its caller. Also, is it possible to dump the symbolic function name like the VC5 debugger does?
Avatar of nietod
nietod

What do you want to dump?  the caller's local variables?

if so in your procedure BP will point to the start of your locals.  directly below that in the stack (higher in mem) is the return address of the caller, then bellow that should be the caller's saved BP, this will point to the caller locals.  (assuming that this convention was used and was not optimized away.
I was just debugging and noticed that what I told you wasn't  100% right.  I switched the return address and the caller's BP.  

BP points to the start of your locals.  That was right.  Below that on the stack is the caller's BP (not the return address.)  So that is the pointer you want to use to get the caller's locals.  ie,
MOV EAX,[EBP]+4 should give you a pointer to the caller's locals.  The caller's locals should extend from that location to [EBP]+8 (which is the return address.)

Avatar of tpchang

ASKER

I want to dump the "call stack". For example, I have the following call path in which function "f1" calls "f2" which in turns calls "f3", then finally calls "dump_call_stack".

   f1() -> f2() -> f3() -> dump_call_stack()

"dump_call_stack" will print out the function symbol names of "f1", "f2", "f3".
By the way, how can I access the procedure BP using C++?
I don't think there is any reasonable way to access ANY of the debug information for a program.  I don;t even think the format of that information is published.  

But VC provies that information from its debugger, is't it sufficient to find it there.

the only other posibility would be to use locally declared C++ objects to create a "chain" of this information.  each function would begin by declaring an object that takes the function name as a parameter.  The constructor would find the last object in the chain (linked list) and add the new one to the end.  (actualy add it to the start, would be easier.)  Then when the function ends, the obect is destroyed and the destructor will remove the object from the list.

You might be able to use the preprocessor to help automate the process of declaring these objects at the start of each function.
>>By the way, how can I access the procedure BP using C++?
__asm { .... }
Actually there might be hope.

The way the debugger (ussually)finds out where it is it uses IP to get the address of the procedure and then looks  it up in a table that lists the procedures by starting addresses.  You can't use that table though.  From there it looks back down the stack at the return addresses and again looks them up in the table you don't have.

You can do the same thing, but you will have to make the table by producing a map file (or listing file) and then use some sort of utility to create a nice neat table (in a text file or binary file)  from the information.  the program can load this file if it needs to walk the stack.  

That should work well.
For nietods question about getting EBP:

An example which walks up the stack building a checksum of
all caller-adresses. This can be very useful
to identify an special programstate.

I used this function in an alloc-hook for detecting
and re-identifying memory-leaks.

Of course, this may cause an exception when accessing
invalid memory-adresses (reaching the end of the stack,
or if theres a function without a stackframe). Therefore
a little consistency-check is included.


---

typedef struct SimpleStack
{
    struct SimpleStack* pPrevious;
    long                nData[8];   // nData[0] = CallerAddress
} SimpleStack;                      // nData[1]... = Params

SimpleStack *GetStack()
{
      SimpleStack *pStack;
      __asm mov pStack, ebp
      pStack = pStack->pPrevious;
      return pStack;
}

long BuildStackChecksum(int nInspectStackDepth)
{
   SimpleStack *pStack = GetStack();
   SimpleStack *pNext = pStack;
   long nCallerAddress = pNext->nData[0];
   long nCallerCode = nCallerAddress;
   for(int i=0; i<nInspectStackDepth; i++)
   {
      long nDiff = ((long)pNext->pPrevious)-(long)pNext;
      if ((nDiff<=0)||(nDiff>0x1000)||(nDiff&0x3!=0))
         break;   // check consistency
      pNext = pNext->pPrevious;
      nCallerAddress = pNext->nData[0];
      nCallerCode ^= nCallerAddress;
//    nLongParameter = pNext->nData[s_nIndex];
   }
   return nCallerCode;
}

---

bye,
  feri
the two checks for stack "consistency" probably help a lot, but are no guarantee you still won't get an invalid memory address exception.   So you probably should use structured exception handling within this process to recover from such errors.
Avatar of jkr
Just a little 'working' example:
ULONG   DbgIntelStackWalk   (   HANDLE  hProcess,   PDBGTHRD    pDbgThrd)
{
    int                 nErr;

    CONTEXT             ctx;
    STACKFRAME          sf;

    PIMAGEHLP_SYMBOL    pimgSym;
    DWORD               dwDisplacement;

    pimgSym =   ( PIMAGEHLP_SYMBOL) malloc (    sizeof  (   IMAGEHLP_SYMBOL)    +   256);

    pimgSym->SizeOfStruct   =   sizeof  (   IMAGEHLP_SYMBOL)    +   256;
    pimgSym->MaxNameLength  =   256;

    SuspendThread   (   pDbgThrd->hThread);

    ZeroMemory  (   &ctx,   sizeof  (   CONTEXT));
    ZeroMemory  (   &sf,    sizeof  (   STACKFRAME));

    ctx.ContextFlags    =   CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;

    if  (   !GetThreadContext   (   pDbgThrd->hThread,  &ctx))
        {
            return  (   0);
        }

    printf  (   "\nStack trace:\n");

    sf.AddrPC.Offset    =   ctx.Eip;
    sf.AddrPC.Mode      =   AddrModeFlat;
    sf.AddrStack.Offset =   ctx.Esp;
    sf.AddrStack.Mode   =   AddrModeFlat;
    sf.AddrFrame.Offset =   ctx.Ebp;
    sf.AddrFrame.Mode   =   AddrModeFlat;

    while   (   TRUE)
            {
                if  (   !StackWalk  (   IMAGE_FILE_MACHINE_I386,
                                        hProcess,
                                        pDbgThrd->hThread,
                                        &sf,
                                        &ctx,
                                        0,
                                        SymFunctionTableAccess,
                                        SymGetModuleBase,
                                        0
                                    )
                    )   break;

                if  (   0   ==  sf.AddrFrame.Offset) // Basic sanity check to make sure
                        break;                       // the frame is OK.  Bail if not.

                printf  (   "TID: %u H:0x%x %08X  %08X  ",  
                            pDbgThrd->dwThreadId,
                            pDbgThrd->hThread,
                            sf.AddrPC.Offset,
                            sf.AddrFrame.Offset
                        );

                if  (   !SymGetSymFromAddr  (   hProcess,
                                                sf.AddrPC.Offset,
                                                &dwDisplacement,
                                                pimgSym
                                            )
                    )
                    {
                        char    acModule    [   MAX_PATH]   =   "\0";

                        DWORD   dwSection   =   0;
                        DWORD   dwOffset    =   0;

                        nErr    =   GetLastError    ();
           
                        GetLogicalAddress   (  (PVOID) sf.AddrPC.Offset,
                                                acModule,
                                                MAX_PATH,
                                                dwSection,
                                                dwOffset
                                            );

                        printf  (   "%04X:%08X %s\n",
                                    dwSection,
                                    dwOffset,
                                    acModule
                                );
                    }
                else    printf  (   "%hs+%X\n", pimgSym->Name, dwDisplacement);
            }

    free    (   pimgSym);

    ResumeThread    (   pDbgThrd->hThread);

    printf  (   "trace complete.\n");

    return  (   0);
}

OK, this needs some clarification (can't elaborate now, as i'm in a hurry), so feel free to ask...
nietod - sorry for just throwing in a piece of code without any further comment...

So here it is ;-)

On Win32 systems, the way to go definitely is using 'StackWalk()' - why to reinvent the wheel?

The symblic information about the contents of EIP can be retrieved using 'imagehlp.dll' (that's what it is for ;-). If no symbolic information is available, it will at least provide the entry point names for DLL functions.
Well that sounds a little easier that what I proposed.  I may have to look into some of those ImageHlp features myself.
nietod - if you are interested in the capabilities, i could send you our 'mini debugger' (16k .zip, the stack trace function is listed above ;-) - the drawback of ImageHlp is that the documatation on how to use it is rudimentary at best...
I'm very interested.  the address is nietod@theshop.net.

I would think that rudimentary documentation (which I was just reading) would be better than most of the rest of the windows documentation.
Ok, you'll get it tomorrow (well, 10pm here, i have to head home and just noticed that the release build is outdated ;-)
Concerning the documentation - maybe it has been improved, but when i started using it, it was <censored> ...
Hi jkr,

I´m also very interested in the mini-debuger-example. My adress is ferenc.hechler@bigfoot.de

thanks,
  feri
the little working sample needs the function
GetLogicalAddress(...) from MSSJ May/97 "under the hood" by Matt Pietrek

#include <imagehlp.h>
#pragma comment(lib, "imagehlp.lib")



BOOL GetLogicalAddress(
        PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD& offset )
{
    MEMORY_BASIC_INFORMATION mbi;

    if ( !VirtualQuery( addr, &mbi, sizeof(mbi) ) )
        return FALSE;

    DWORD hMod = (DWORD)mbi.AllocationBase;

    if ( !GetModuleFileName( (HMODULE)hMod, szModule, len ) )
        return FALSE;

    // Point to the DOS header in memory
    PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod;

    // From the DOS header, find the NT (PE) header
    PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + pDosHdr->e_lfanew);

    PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr );

    DWORD rva = (DWORD)addr - hMod; // RVA is offset from module load address

    // Iterate through the section table, looking for the one that encompasses
    // the linear address.
    for (   unsigned i = 0;
            i < pNtHdr->FileHeader.NumberOfSections;
            i++, pSection++ )
    {
        DWORD sectionStart = pSection->VirtualAddress;
        DWORD sectionEnd = sectionStart
                    + max(pSection->SizeOfRawData, pSection->Misc.VirtualSize);

        // Is the address in this section???
        if ( (rva >= sectionStart) && (rva <= sectionEnd) )
        {
            // Yes, address is in the section.  Calculate section and offset,
            // and store in the "section" & "offset" params, which were
            // passed by reference.
            section = i+1;
            offset = rva - sectionStart;
            return TRUE;
        }
    }

    return FALSE;   // Should never get here!
}

Yes, indeed!!! This function gets involved when 'SymGetSymFromAddr()' can't find any usable information ;-)
(fkurucz, thanks for posting it! Expect to receive some mail in the close future <s>. PS: Gruß aus Schwaben <S>...)
tpchang - have you tried any of the suggestions?
tpchang - what's going to happen with this Q?
ASKER CERTIFIED SOLUTION
Avatar of jkr
jkr
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial