CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 11 of 11
  1. #1
    Join Date
    Aug 2004
    Location
    Bucharest, Romania... sometimes
    Posts
    1,039

    dumping stack for all threads

    Aloha!
    I'm in a bit of a jam... any hints will be greatly appreciated.
    Description:
    There's this multithreaded process which throws an exception. In the exception handler, am trying to dump the stack for all the threads in the process. For the thread generating the exception is a piece of cake, and I figured will be almost as easy for the other threads:
    Step 1 - get the context of each thread using GetThreadContext() API
    Step 2 - get TIB (Thread Information Block) of the thread in question
    Step 3 - dump from stack's top until stack's base
    Unexpected result:
    I believed that when switching the thread's context for yielding the execution to a different thread, SegFs member of the CONTEXT structure must have a different value from one thread to another, in order to have FS:[18h] pointing to a different TIB structure. It seems I was wrong!
    Questions:
    - Is the following function retrieving the TIB of the calling thread?
    Code:
    	static tagXTIB* GetTIB()
    	{
    		tagXTIB* pTib;
    		__asm
    		{
    			MOV EAX , FS:[18h]
    			MOV pTib , EAX
    		}
    		return pTib;
    	}
    ...where tagXTIB is defined as:
    Code:
    typedef struct tagXTIB
    {
    	PEXCEPTION_REGISTRATION_RECORD pvExcept; //00h Head of exception record list
    	PVOID pvStackUserTop; //04 Top of user stack
    	PVOID pvStackUserBase; //08h Base of user stack
    	union
    	{
    		struct //Win95 fields
    		{
    			WORD pvTDB; //0Ch
    			WORD pvThunkSS; //0Eh SS selector used for thunking to 16 bits
    			DWORD unknown1; //10h
    		} WIN95;
    		struct //WinNT fields
    		{
    			PVOID SubSystemTib; //0Ch
    			ULONG FiberData; //10h
    		} WINNT;
    	} TIB_UNION1;
    	PVOID pvArbitrary; //14h Available for application
    	struct _tib *ptibSelf; //18h Linear address of TIB structure
    	union
    	{
    		struct //Win95 fields
    		{
    			WORD TIBFlags; //1Ch
    			WORD Win16MutexCount; //1Eh
    			DWORD DebugContext; //20h
    			DWORD pCurrentPriority; //24h
    			DWORD pvQueue; //28h Message Queue
    		} WIN95;
    		struct //WinNT fields
    		{
    			DWORD unknown1; //1Ch
    			DWORD processID; //20h
    			DWORD threadID; //24h
    			DWORD unknown2; //28h
    		} WINNT;
    	} TIB_UNION2;
    	PVOID* pvTLSArray;
    	union
    	{
    		struct //Win95 fields
    		{
    			PVOID* pProcess; //30h Pointer to owning process database
    		} WIN95;
    	} TIB_UNION3;
    } XTIB;
    #pragma pack()
    - Are the pvStackUserTop & pvStackUserBase members of tagXTIB structure the addresses defining the memory interval where the thread's stack is located?

    - Do you know any other way of getting these values, besides reading them from the TIB?

    - In order to get the address of the TIB for all the other threads of the process (not the calling thread), is it correct to use the SegFs member of the CONTEXT structure filled by a GetThreadContext() call, instead of FS from GetTIB() function?

    - Then, why SegFs is equal with FS? Makes no sense to get the TIBs this way, since FS:[18h] will be equal with SegFs:[18h], but TIB should be different for two threads, isn't it?

    Conclusions:
    My interest is to dump the stack for each thread belonging to the process. Having the stack's top & base addresses, and also the Esp pointer (which seems to be retrieved correctly by the API), I will be able to list the call stack. All registers are looking as valid information, as returned by GetThreadContext() API. I've checked Eip value, too. So, makes me believe that SegFs is also correct. But then, why has the same value with the calling thread's FS? If so, from where to read the TIB of the other threads? Or, at least, how to get the correct location of their stack (because I know for sure that threads are not sharing the stack)?

    Many anticipated thanks,
    Bogdan Apostol
    ESRI Developer Network

    Compilers demystified - Function pointers in Visual Basic 6.0
    Enables the use of function pointers in VB6 and shows how to embed native code in a VB application.

    Customize your R2H
    The unofficial board dedicated to ASUS R2H UMPC owners.

  2. #2
    Join Date
    Aug 1999
    Location
    <Classified>
    Posts
    6,882

    Re: dumping stack for all threads

    I am not sure if your question is answered already, so excuse me if I am posting in old thread.

    Here is a good article from Matt Patriek on TIBs, Very similar to what you are trying to do,

    MSJ Article on TIBs
    http://www.microsoft.com/msj/archive/S2CE.aspx

    He also had the sample code (SHOWTIB), see DisplayTIB function, not present in source code downloads (at least I couldnt find it there)
    So here is a copy I got from somewhere on web,
    Code:
    tib.h and showtib.cpp
    
    TIB.H
     //===========================================================
    // File: TIB.H
    // Author: Matt Pietrek
    // From: Microsoft Systems Journal "Under the Hood", May 1996
    //===========================================================
    #pragma pack(1)
    
    typedef struct _EXCEPTION_REGISTRATION_RECORD
    {
        struct _EXCEPTION_REGISTRATION_RECORD * pNext;
        FARPROC                                 pfnHandler;
    } EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;
    
    typedef struct _TIB
    {
    PEXCEPTION_REGISTRATION_RECORD pvExcept; // 00h Head of exception record list
    PVOID   pvStackUserTop;     // 04h Top of user stack
    PVOID   pvStackUserBase;    // 08h Base of user stack
    
    union                       // 0Ch (NT/Win95 differences)
    {
        struct  // Win95 fields
        {
            WORD    pvTDB;         // 0Ch TDB
            WORD    pvThunkSS;     // 0Eh SS selector used for thunking to 16 bits
            DWORD   unknown1;      // 10h
        } WIN95;
    
        struct  // WinNT fields
        {
            PVOID SubSystemTib;     // 0Ch
            ULONG FiberData;        // 10h
        } WINNT;
    } TIB_UNION1;
    
    PVOID   pvArbitrary;        // 14h Available for application use
    struct _tib *ptibSelf;      // 18h Linear address of TIB structure
    
    union                       // 1Ch (NT/Win95 differences)
    {
        struct  // Win95 fields
        {
            WORD    TIBFlags;           // 1Ch
            WORD    Win16MutexCount;    // 1Eh
            DWORD   DebugContext;       // 20h
            DWORD   pCurrentPriority;   // 24h
            DWORD   pvQueue;            // 28h Message Queue selector
        } WIN95;
    
        struct  // WinNT fields
        {
            DWORD unknown1;             // 1Ch
            DWORD processID;            // 20h
            DWORD threadID;             // 24h
            DWORD unknown2;             // 28h
        } WINNT;
    } TIB_UNION2;
    
    PVOID*  pvTLSArray;         // 2Ch Thread Local Storage array
    
    union                       // 30h (NT/Win95 differences)
    {
        struct  // Win95 fields
        {
            PVOID*  pProcess;     // 30h Pointer to owning process database
        } WIN95;
    } TIB_UNION3;
        
    } TIB, *PTIB;
    #pragma pack()
    
    
    SHOWTIB.CPP
     //==========================================================================
    // File: SHOWTIB.CPP
    // Author: Matt Pietrek
    // To Build:
    //  CL /MT SHOWTIB.CPP USER32.LIB (Visual C++)
    //  BCC32 -tWM SHOWTIB.CPP (Borland C++, TASM32 required)
    //==========================================================================
    #define WIN32_LEAN_AND_MEAN
    #include <windows.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <process.h>
    #pragma hdrstop
    #include "tib.h"
    
    #define SHOWTIB_MAX_THREADS 64
    
    CRITICAL_SECTION gDisplayTIB_CritSect;
    
    void DisplayTIB( PSTR pszThreadName )
    {
        PTIB pTIB;
        WORD fsSel;
    
        EnterCriticalSection( &gDisplayTIB_CritSect );
    
        __asm
        {
            mov     EAX, FS:[18h]
            mov     [pTIB], EAX
            mov     [fsSel], FS
        }
            
        printf( "Contents of thread %s\n", pszThreadName );
        
        printf( "  TIB %04X (Address: %08X)\n", fsSel, pTIB );
        printf( "  SEH chain: %08X\n", pTIB->pvExcept );
        printf( "  Stack top: %08X\n", pTIB->pvStackUserTop );
        printf( "  Stack base: %08X\n", pTIB->pvStackUserBase );
        printf( "  pvArbitray: %08X\n", pTIB->pvArbitrary );
        printf( "  TLS array *: %08X\n", pTIB->pvTLSArray );
    
        printf( "  ----OS Specific fields----\n" );
        if ( 0xC0000000 == (GetVersion() & 0xC0000000) )    // Is this Win95 ?
        {
            printf( "  TDB: %04X\n", pTIB->TIB_UNION1.WIN95.pvTDB );
            printf( "  Thunk SS: %04X\n", pTIB->TIB_UNION1.WIN95.pvThunkSS );
            printf( "  TIB flags: %04X\n", pTIB->TIB_UNION2.WIN95.TIBFlags );
            printf( "  Win16Mutex count: %04X\n",
                        pTI->TIB_UNION2.WIN95.Win16MutexCount );
            printf( "  DebugContext: %08X\n", pTIB->TIB_UNION2.WIN95.DebugContext );
            printf( "  Current Priority *: %08X (%u)\n",
                        pTIB->TIB_UNION2.WIN95.pCurrentPriority,
                        *(PDWORD)(pTIB->TIB_UNION2.WIN95.pCurrentPriority) );
            printf( "  Queue: %04X\n", pTIB->TIB_UNION2.WIN95.pvQueue );
            printf( "  Process *: %08X\n", pTIB->TIB_UNION3.WIN95.pProcess );
        }
        else if ( 0 == (GetVersion() & 0xC0000000) )    // Is this WinNT?
        {
            printf("  SubSystem TIB: %08X\n", pTIB->TIB_UNION1.WINNT.SubSystemTib);
            printf("  FiberData: %08X\n", pTIB->TIB_UNION1.WINNT.FiberData );
            printf("  unknown1: %08X\n", pTIB->TIB_UNION2.WINNT.unknown1);
            printf("  process ID: %08X\n", pTIB->TIB_UNION2.WINNT.processID);
            printf("  thread ID: %08X\n", pTIB->TIB_UNION2.WINNT.threadID);
            printf("  unknown2: %08X\n", pTIB->TIB_UNION2.WINNT.unknown2);
        }
        else
        {
            printf("  Unsupported Win32 implementation\n" );
        }
        
        printf( "\n" );
    
        LeaveCriticalSection( &gDisplayTIB_CritSect );
    }
    
    void MyThreadFunction( void * threadParam )
    {
        char szThreadName[128];
    
        wsprintf( szThreadName, "%u", threadParam );    // Give the thread a name
    
        // If multiple threads are specified, give'em different priorities
        if ( (DWORD)threadParam & 1 )
            SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_HIGHEST );
    
        DisplayTIB( szThreadName );     // Display the thread's TIB
    
        // Let other threads execute while this thread is still alive.  The idea
        // here is to try and prevent memory region and selector reuse.
        Sleep( 1000 );
    }
    
    int main( int argc, char *argv[] )
    {
        if ( argc < 2 )
        {
            printf( "Syntax: SHOWTIB [# of threads]\n" );
            return 1;
        }
    
        InitializeCriticalSection( &gDisplayTIB_CritSect );
        
        unsigned cThreads = atoi( argv[1] );
    
        if ( (cThreads < 1) || (cThreads > SHOWTIB_MAX_THREADS) )
        {
            printf( "thread count must be > 1 and < %u\n", SHOWTIB_MAX_THREADS );
        }
        else
        {
            // Allocate an array to hold the thread handles
            HANDLE threadHandles[ SHOWTIB_MAX_THREADS ];
    
            // Create the specified number of threads
            for ( unsigned i = 0; i < cThreads; i++ )
                threadHandles[i] = (HANDLE)
                    _beginthread(MyThreadFunction,0,(PVOID)i);
    
            // Wait for all the threads to finish before we exit the program
            WaitForMultipleObjects( cThreads, threadHandles, TRUE, INFINITE );
    
            // We don't need the thread handles anymore.  Close'em!
            for ( i = 0; i < cThreads; i++ )
                CloseHandle( threadHandles[i] );
        }
        
        DeleteCriticalSection( &gDisplayTIB_CritSect );
    
        return 0;
    }
    Regards,
    Ramkrishna Pawar

  3. #3
    Join Date
    Aug 2004
    Location
    Bucharest, Romania... sometimes
    Posts
    1,039

    Re: dumping stack for all threads

    Thanks Krishnaa for your reply! I appreciate your effort of gathering some information about TIBs, but unfortunatelly does not answer my question. Your sample code displays the TIB of each thread from within the threads themselves. What I am looking for is to read from a thread "A" the TIB of a thread "B". I assume it is possible to obtain the address of the TIB of each thread in the process when an exception occurs because most debuggers can.
    This is my purpose: to install a "global" hook for a process which catches all exceptions raised by all its threads, and log some info about the state of all threads (not only the one that raised the exception). I have managed to implement everything else (including exe patching used to debug applications which can't be recompiled) but currently my log contains only info about the thread that crashed (i do log only unhandled exceptions, not handled ones) and sometimes that isn't enough to catch a bug.
    I have also implemented a function call stack retrieval algorithm that works also in some cases of corrupted stack (a bit better than the one in VS 6.0, and similar to the one in VS 7.1). When exceptions are not handled, my implementation uses some heuristics, trying to skip the bad code and resume execution, which in many cases helps an application to regain control and let the user save data,... etc.
    Before I've posted here I have read most of the related articles (also from MSJ) I could find and tested many sample source code, but unfortunatelly I couldn't find an answer until today, after more than 2 years since it all started.

    Regards,
    Bogdan Apostol
    ESRI Developer Network

    Compilers demystified - Function pointers in Visual Basic 6.0
    Enables the use of function pointers in VB6 and shows how to embed native code in a VB application.

    Customize your R2H
    The unofficial board dedicated to ASUS R2H UMPC owners.

  4. #4
    Join Date
    Aug 1999
    Location
    <Classified>
    Posts
    6,882

    Re: dumping stack for all threads

    Ohh...

    Are those other threads suspended before using GetThreadContext() ?
    Regards,
    Ramkrishna Pawar

  5. #5
    Join Date
    Aug 2004
    Location
    Bucharest, Romania... sometimes
    Posts
    1,039

    Re: dumping stack for all threads

    Quote Originally Posted by Krishnaa
    Ohh...

    Are those other threads suspended before using GetThreadContext() ?
    Yes, but FS register contains the address of the TIB for the calling thread, so:
    Code:
        __asm
        {
            mov     EAX, FS:[18h]
            mov     [pTIB], EAX
            mov     [fsSel], FS
        }
    will not help to obtain the address of the TIB of another thread. In fact, I would be happy enough to get the "top" and "base" address for the stack of each thread when the unhandled exception occurs. Yes, all threads are suspended and other requirements met, but I need to figure out how context switching occurs. Do I have to execute code in Ring 0 to be able to access this kind of information? I don't even know where to start looking into this matter. uf...
    Bogdan Apostol
    ESRI Developer Network

    Compilers demystified - Function pointers in Visual Basic 6.0
    Enables the use of function pointers in VB6 and shows how to embed native code in a VB application.

    Customize your R2H
    The unofficial board dedicated to ASUS R2H UMPC owners.

  6. #6
    Join Date
    Aug 1999
    Location
    <Classified>
    Posts
    6,882

    Re: dumping stack for all threads

    I am not sure of TIB information, but here is one StackWalker written by Jochen Kalmbach [MVP VC++], It can dump the stack for any thread, of any process.

    http://www.codeproject.com/threads/StackWalker.asp

    This can at least meet your requirement of dumping the call stack of all threads.
    Regards,
    Ramkrishna Pawar

  7. #7
    Join Date
    Aug 2004
    Location
    Bucharest, Romania... sometimes
    Posts
    1,039

    Re: dumping stack for all threads

    Quote Originally Posted by Krishnaa
    I am not sure of TIB information, but here is one StackWalker written by Jochen Kalmbach [MVP VC++], It can dump the stack for any thread, of any process.

    http://www.codeproject.com/threads/StackWalker.asp

    This can at least meet your requirement of dumping the call stack of all threads.
    Thanks for finding this article for me. The source code is quite well documented, and hope it will work with multiple threads (didn't have time yet to test it in a multithreaded app). I recall having GetThreadContext working just fine for most registers (eax, ecx,...) but returning the same value for FS register of different threads, which confused me in the way TIB are retrieved. I'll go through the StackWalker and figure out what is it doing to get the info about all threads of a process.
    Thanks for all your help,
    Bogdan Apostol
    ESRI Developer Network

    Compilers demystified - Function pointers in Visual Basic 6.0
    Enables the use of function pointers in VB6 and shows how to embed native code in a VB application.

    Customize your R2H
    The unofficial board dedicated to ASUS R2H UMPC owners.

  8. #8
    Join Date
    Aug 1999
    Location
    <Classified>
    Posts
    6,882

    Re: dumping stack for all threads

    I have tested it, by uncommenting the TestDifferentThread() call in _tmain() function. It works pretty well.
    Regards,
    Ramkrishna Pawar

  9. #9
    Join Date
    Aug 2004
    Location
    Bucharest, Romania... sometimes
    Posts
    1,039

    Re: dumping stack for all threads

    Quote Originally Posted by Krishnaa
    I have tested it, by uncommenting the TestDifferentThread() call in _tmain() function. It works pretty well.
    Thanks, I will have a look as soon as I get the time. I still wonder why the SegFS returned by GetThreadContext is the same as FS register of the calling thread, for a given handle of a different thread.
    Bogdan Apostol
    ESRI Developer Network

    Compilers demystified - Function pointers in Visual Basic 6.0
    Enables the use of function pointers in VB6 and shows how to embed native code in a VB application.

    Customize your R2H
    The unofficial board dedicated to ASUS R2H UMPC owners.

  10. #10
    Join Date
    Oct 2011
    Posts
    1

    Re: dumping stack for all threads

    I realize this is a very old thread that I'm resurrecting, but after struggling with these exact issues, and others in the dark corners of windows I thought I'd add to it. Justification: This is one of the only relevant hits on google for trying to dump the stack contents for all threads in an owned process, and the same or trying to find the TIB of other threads in the running process. Furthermore, the conversation dead ends with the OP still having questions of relevance.

    First, StackWalker is appropriate for getting the call stacks, but not the stack contents. There's a caveat however, since windows can delay load the relevant dlls (Toolhelp, psapi, dbghelp, etc), if you're dumping all of your threads at once you need to make a call into each of these libraries before suspending all target threads: If a different thread holds the loader lock, a call into any of these (or any other DLL you need) will deadlock if the call target is load delayed. On the same note, if you're using any language features that take a lock (various functions in C, almost everything in C++) and another thread holds that lock then you will deadlock.

    Getting the actual stack contents was non-obvious at first, at least for me...

    After a week of searching, hacking reactos code, and delving into some very ugly places in winapi, I've come to the conclusion that it's not actually possible to read the TIB from another thread reliably. I tried multiple ways of trying to resolve this; the first issue that you come up against is that SegFs is static in all threads -- on a thread context change, the actual descriptor base address is changed. I'm not sure which component is responsible for managing that though. That explains why GetThreadContext for other threads returns the same FS value as the currently running thread.

    That leads to the obvious idea of GetThreadSelectorEntry -- a very poorly documented 'feature' of this API however is that it only works on WOW16 (I didn't even know there was a WOW16 until now) -- bonus: from looking at source, it appears this api works as documented in wine, but not in any actual windows distribution. That makes the base address of FS for other threads unmappable. A pointer to the thread's TIB has to be stored somewhere though, with this method, possibly in the internal thread structure stored by the OS -- I couldn't find it; but I didn't try very hard (there's no documented way, but memory scans might lead somewhere).

    As an added bonus, attempting to read the stack from the values in the TIB structure wouldn't work anyway. The stack top value is correct, but the stack bottom value is not. It appears the TIB values are not updated on context changes. StackTop appears to be correct, StackBottom does not. It's not clear when or how StackBottom is updated. Doubly added bonus: ESP returned from RtlCaptureContext is also wrong __asm { mov stackptr, esp }, ironically, is the only accurate way to get that value for the current thread if the running thread stack needs to be dumped. A cherry on top: Since there's no way to tell, you have to assume that context.ESP is correct for other (suspended) threads.

    The best method I could find for getting the stack addresses for other threads, and what should have been the obvious way: VirtualQuery on context.ESP, AllocationBase will be the top of the stack. If you follow that up with VirtualQueryEx and look for pages following AllocationBase that are marked MEM_COMMIT you can find the page limits for the actual committed portion of the stack to dump -- ESP for the specified thread should fall in the range of the last page and can be used as a delimiting address (or not) for said thread.

    The second best method I could come up with is to ask each thread for either a TIB pointer or simply the top of stack pointer with QueueUserAPC. The only problem here is that the thread in question has to service an APC. (In my case, storage is probably unreliable as well anyway)

  11. #11
    Join Date
    Aug 2004
    Location
    Bucharest, Romania... sometimes
    Posts
    1,039

    Re: dumping stack for all threads

    @Corwinoid
    I'm impressed with your deep investigation.
    I feel that you belong to a dying breed... since RAD is what most use nowadays.
    Thanks for sharing your findings.
    At that point in time I had to choose different means which did not require the stack contents of all application's threads.
    The first method you've found might've worked, while the second was not possible since we were having quite a few threads created by 3rd party modules, and our interest was mainly in these threads.
    My immediate comment on your first approach of quering allocated & commited pages is that ESP may be anywhere within the thread's stack.
    I have once "almost" built a recovery module responsible with a "never crash" feature for a critical process.
    One of its capabilities was to recover from stack overflow conditions, typically generated by infinite recursive loops.
    After such a recovery, the ESP ends-up close to the beginning of the stack, while the stack remains grown to its maximum allowed size.
    Once again, thanks for resurecting this thread...
    All the best!
    Bogdan Apostol
    ESRI Developer Network

    Compilers demystified - Function pointers in Visual Basic 6.0
    Enables the use of function pointers in VB6 and shows how to embed native code in a VB application.

    Customize your R2H
    The unofficial board dedicated to ASUS R2H UMPC owners.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured