Solved

How to dump call stack in C++

Posted on 1998-12-14
21
1,346 Views
Last Modified: 2008-03-04
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?
0
Comment
Question by:tpchang
  • 9
  • 8
  • 3
  • +1
21 Comments
 
LVL 22

Expert Comment

by:nietod
ID: 1180184
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.
0
 
LVL 22

Expert Comment

by:nietod
ID: 1180185
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.)

0
 

Author Comment

by:tpchang
ID: 1180186
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++?
0
 
LVL 22

Expert Comment

by:nietod
ID: 1180187
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.
0
 
LVL 22

Expert Comment

by:nietod
ID: 1180188
>>By the way, how can I access the procedure BP using C++?
__asm { .... }
0
 
LVL 22

Expert Comment

by:nietod
ID: 1180189
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.
0
 
LVL 1

Expert Comment

by:fkurucz
ID: 1180190
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
0
 
LVL 22

Expert Comment

by:nietod
ID: 1180191
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.
0
 
LVL 86

Expert Comment

by:jkr
ID: 1180192
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);
}

0
 
LVL 86

Expert Comment

by:jkr
ID: 1180193
OK, this needs some clarification (can't elaborate now, as i'm in a hurry), so feel free to ask...
0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 86

Expert Comment

by:jkr
ID: 1180194
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.
0
 
LVL 22

Expert Comment

by:nietod
ID: 1180195
Well that sounds a little easier that what I proposed.  I may have to look into some of those ImageHlp features myself.
0
 
LVL 86

Expert Comment

by:jkr
ID: 1180196
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...
0
 
LVL 22

Expert Comment

by:nietod
ID: 1180197
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.
0
 
LVL 86

Expert Comment

by:jkr
ID: 1180198
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> ...
0
 
LVL 1

Expert Comment

by:fkurucz
ID: 1180199
Hi jkr,

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

thanks,
  feri
0
 
LVL 1

Expert Comment

by:fkurucz
ID: 1180200
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!
}

0
 
LVL 86

Expert Comment

by:jkr
ID: 1180201
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>...)
0
 
LVL 86

Expert Comment

by:jkr
ID: 1180202
tpchang - have you tried any of the suggestions?
0
 
LVL 86

Expert Comment

by:jkr
ID: 1180203
tpchang - what's going to happen with this Q?
0
 
LVL 86

Accepted Solution

by:
jkr earned 100 total points
ID: 1180204
OK, as the above code definitely works to dump a call stack, i'll lock the Q after not receiving a response after 1 1/2 months...
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

  Included as part of the C++ Standard Template Library (STL) is a collection of generic containers. Each of these containers serves a different purpose and has different pros and cons. It is often difficult to decide which container to use and …
Go is an acronym of golang, is a programming language developed Google in 2007. Go is a new language that is mostly in the C family, with significant input from Pascal/Modula/Oberon family. Hence Go arisen as low-level language with fast compilation…
The viewer will learn how to use the return statement in functions in C++. The video will also teach the user how to pass data to a function and have the function return data back for further processing.
The viewer will be introduced to the technique of using vectors in C++. The video will cover how to define a vector, store values in the vector and retrieve data from the values stored in the vector.

706 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now