Re: Problem with code that inspects thread context and dumps stack trace
- From: Joseph M. Newcomer <newcomer@xxxxxxxxxxxx>
- Date: Wed, 01 Jun 2005 11:46:22 -0400
It is called "the debugger". It doesn't do a dump of all threads, but you can use the
Debug>Threads command to look at each thread in turn to see who is blocked on a particular
lock.
One way to handle this in a more elegant fashion is to always do a WaitForMultipleObjects,
the *second* of which is your desired lock, and the first of which is the one-and-only
"please dump your context" lock. When you come out of the lock, if the index of the lock
is 0, then you invoke your dump code.
I'm curious why you felt it necessary to re-implement _tprintf yourself. This already
exists. I'm also curious why you included a huge mass of largely irrelevant code. You do
simple things like test GetModuleFileNameEx and return FALSE if it fails, but you do NOT
do anything sensible, like use ::GetLastError to report what ACTUAL error occurred, so you
are clueless as to what happened. If you read the documentation, you will see that
GetModuleFileName does not require any rights to a process; it only requires a module
handle. But GetModuleFileNameEx takes a process handle and requires certain rights. And it
very carefully says "To get extended error information, call GetLastError". Failing to do
that means you cannot have any hope of figuring out why it failed. It may simply be that
the result is "access denied", so you don't have enough rights to the handle. I have never
tested this, but I believe that if you have sufficiently high privileges you could do a
DuplicateHandle asking for these rights and they will be granted; if you get "access
denied" that would be the first thing to try, to get a handle with sufficient privileges.
There are other solutions. For example, you could assign lock numbers to each
synchronization lock and see, by simple code inspection, if every locking sequence uses
monotonically increasing numbers. You could wrap your synchronization objects in a class
that maintains these numbers (InterlockedCompareAndExchange might be a good candidate for
doing the testing, although I've not tried to write the code myself; perhaps a simple
CRITICAL_SECTION would suffice...). You could put timeouts on all the waits, and if you
get a timeout after some long duration you could declare yourself stuck in a deadlock.
Note that the recovery is to typically throw an exception and each exception handler must
"roll back" the enclosing transaction to undo any changes before it releases its
containing lock, and you need a negative-exponential back-off-and-retry delay like CS/MDA
(Carrier-Sense Multi-Detect Access, that is, Ethernet) uses, to minimize the probability
of re-entering a deadlock.
On the whole, I've found that care in doing the locking and building resilient structures
is a lot more effective than after-the-fact diagnostics applied externally. I don't get
deadlock very often, but when I do, the debugger usually is sufficient, because I do
fairly good testing during the development cycle.
Canonical order is the ONLY way to avoid deadlock. If you don't have a canonical locking
order, you WILL get deadlock. Therefore, by ensuring that during every locking sequence,
the lock number of the current lock is strictly greater than all previous locks is
sufficient. I suppose this could be done by subclassing one of the MFC synchronization
objects, but since I avoid them entirely, I just implement it by my own wrapping class
(only once did I have to do this, on a piece of code I didn't write).
class CanonicalObject {
public:
CanonicalObject(UINT order, HANDLE object) {
CanonicalOrder = order;
handle = object;;
if(!initialized) InitializeCriticalSection(&canonicalLock); }
HANDLE Acquire() {
#ifdef _DEBUG
EnterCriticalSection(&canonicalLock);
if(order <= CurrentLock[lockindex])
{ /* failed */
ASSERT(FALSE); // locking order is incorrect
... other recovery was here, e.g.
LeaveCriticalSection(&canonicalLock);
// throw new DeadlockException;
} /* failed */
else
{ /* still canonical */
lockindex++;
CurrentLock[lockindex] = order;
LeaveCriticalSection(&canonicalLock);
return handle;
} /* still canonical */
#else
return handle;
#endif
}
virtual void Release() PURE;
protected:
HANDLE handle;
UINT canonicalOrder;
protected: // methods
void Remove() {
#ifdef _DEBUG
long n = InterlockedDecrement(&lockindex);
if(n < 0)
{ /* too many unlocks */
ASSERT(FALSE); // more unlocks than locks
} /* too many unlocks */
#endif
}
protected: // static
static UINT CurrentLock[MAX_LOCKS];
static long lockindex;
static CRITICAL_SECTION canonicalLock;
// make sure this is initialized!
static BOOL initialized;
};
class CanonicalMutext : public CanonicalObject {
public:
CanonicalMutex(UINT order, HANDLE object) : CanonicalObject(order,
object) {}
virtual void Release()
{
Remove();
ReleaseMutex(hande);
}
};
(repeat subclasses as necessary for other objects other than mutexes, e.g., events,
semaphores, etc.)
The technique I use is to always do
HANDLE mymutex = ::CreateMutex(...);
CanonicalMutex myobject(MYMUTEX_CANONICAL_ORDER_NUMBER, mymutex);
// then when I want to acquire the mutex
WaitForSingleObject(myobject.Acquire(), ...);
myobject.Release();
Note that appropriate recovery for timeout and other failures has to also release the
object.
I spent a couple hours putting this in, and within ten minutes I had not only my deadlock
but the complete traceback in the lock stack I maintained of the preceding locking
sequence, which is a lot more useful than a post-lock dump which doesn't actually say why
the lock error occurred).
You might also consider making the canonical order stack contain the thread ID of the
thread that did the lock, rather than just being a sequence of integers, so you can see
all the relevant detail. In my case, the locking sequence was enough to go on. All I had
was a bunch of #defines of the form
#define MYMUTEX_CANONICAL_ORDER_NUMBER 6
(Note this code is a little bit cleaner than what I had actually done, but subject tot he
usual caveats as I am writing it off the top of my head, so you should take it as a sketch
rather than as definitive, tested piece of code...)
joe
On 1 Jun 2005 01:39:42 -0700, "Lalabel" <lalabel_roncagliolo@xxxxxxxxxxx> wrote:
>Hi!
>Some years ago, I had to debug a Java application that had a deadlock
>problem.
>It wasn't so hard, because I've been told about a JVM command (now, I
>don't even remember how to activate it!) that dumps the status of all
>threads in an app, including "what they are waiting for" (locks).
>
>Now I'm in the "real" world of C++, Win32 applications and MFC (no
>virtual machines around!). We have many multithreaded applications:
>what if some bug causes a dedlock, e.g. in a test scenario? We must
>inspect it, and I'd like to have a tool to simplify my life. Does such
>tool exist under Win32, something that dumps status&locks for threads?
>I didn't find anything, so I'm trying another way.
>
>Three months ago, "inspired" by the following articles
>http://www.microsoft.com/msj/0497/hood/hood0497.aspx
>http://www.microsoft.com/msj/0597/hood0597.aspx
>http://www.microsoft.com/msj/0498/bugslayer0498.aspx
>I wrote an exeption handler that dumps a stack trace when an access
>violation occurs. The handler is placed INSIDE the faulting
>application, and works very well, even in Release config. (of course,
>it works even better when PDB files are available)
>
>I'm trying to adapt this to create a tool that "spies" another
>application (either running or "frozen") and dumps the stack trace for
>each thread. It works, but... Well, here is the code, reduced to a bare
>minimum:
>
>#include <fstream>
>#include <string>
>
>#include <windows.h>
>#include <tlhelp32.h>
>
>#include <ImageHlp.h>
>#include <Psapi.h>
>
>using namespace std;
>
>typedef BOOL (__stdcall * SYMINITIALIZEPROC)( HANDLE, LPSTR, BOOL );
>typedef BOOL (__stdcall *SYMCLEANUPPROC)( HANDLE );
>typedef BOOL (__stdcall * STACKWALKPROC)
> ( DWORD, HANDLE, HANDLE, LPSTACKFRAME, LPVOID,
> PREAD_PROCESS_MEMORY_ROUTINE,PFUNCTION_TABLE_ACCESS_ROUTINE,
> PGET_MODULE_BASE_ROUTINE, PTRANSLATE_ADDRESS_ROUTINE );
>
>typedef LPVOID (__stdcall *SYMFUNCTIONTABLEACCESSPROC)( HANDLE, DWORD
>);
>typedef DWORD (__stdcall *SYMGETMODULEBASEPROC)( HANDLE, DWORD );
>typedef BOOL (__stdcall *SYMGETSYMFROMADDRPROC) ( HANDLE, DWORD,
>PDWORD, PIMAGEHLP_SYMBOL );
>typedef BOOL (__stdcall *SYMGETMODULEINFOPROC) (HANDLE, DWORD,
>PIMAGEHLP_MODULE );
>typedef BOOL (__stdcall *SYMGETLINEFROMADDRPROC) (HANDLE, DWORD,
>PDWORD, PIMAGEHLP_LINE );
>
>SYMINITIALIZEPROC _SymInitialize;
>SYMCLEANUPPROC _SymCleanup;
>STACKWALKPROC _StackWalk;
>SYMFUNCTIONTABLEACCESSPROC _SymFunctionTableAccess;
>SYMGETMODULEBASEPROC _SymGetModuleBase;
>SYMGETSYMFROMADDRPROC _SymGetSymFromAddr;
>SYMGETMODULEINFOPROC _SymGetModuleInfo;
>SYMGETLINEFROMADDRPROC _SymGetLineFromAddr;
>
>
>void __cdecl _tprintf(ostream& output, const TCHAR * format, ...)
>{
> TCHAR szBuff[1024];
> va_list argptr;
> va_start( argptr, format );
> wvsprintf( szBuff, format, argptr );
> va_end( argptr );
> output << szBuff;
>}
>
>
>BOOL InitImagehlpFunctions( HANDLE hProcess, PSTR szPath )
>{
> HMODULE hModImagehlp = LoadLibrary( "IMAGEHLP.DLL" );
> if ( !hModImagehlp )
> return FALSE;
>
> _SymInitialize = (SYMINITIALIZEPROC)GetProcAddress( hModImagehlp,
> "SymInitialize"
>);
> if ( !_SymInitialize )
> return FALSE;
>
> _SymCleanup = (SYMCLEANUPPROC)GetProcAddress( hModImagehlp,
>"SymCleanup" );
> if ( !_SymCleanup )
> return FALSE;
>
> _StackWalk = (STACKWALKPROC)GetProcAddress( hModImagehlp,
>"StackWalk" );
> if ( !_StackWalk )
> return FALSE;
>
> _SymFunctionTableAccess = (SYMFUNCTIONTABLEACCESSPROC)
> GetProcAddress( hModImagehlp, "SymFunctionTableAccess"
>);
>
> if ( !_SymFunctionTableAccess )
> return FALSE;
>
> _SymGetModuleBase = (SYMGETMODULEBASEPROC)GetProcAddress(
>hModImagehlp,
>
>"SymGetModuleBase");
> if ( !_SymGetModuleBase )
> return FALSE;
>
> _SymGetSymFromAddr = (SYMGETSYMFROMADDRPROC)GetProcAddress(
>hModImagehlp,
>
>"SymGetSymFromAddr" );
> if ( !_SymGetSymFromAddr )
> return FALSE;
>
> _SymGetModuleInfo = (SYMGETMODULEINFOPROC)GetProcAddress(
>hModImagehlp,
>
>"SymGetModuleInfo");
> if ( !_SymGetModuleInfo )
> return FALSE;
>
> _SymGetLineFromAddr = (SYMGETLINEFROMADDRPROC)GetProcAddress(
>hModImagehlp,
>
>"SymGetLineFromAddr");
> if ( !_SymGetLineFromAddr )
> return FALSE;
>
> // INITIALIZE IMAGEHLP LIBRARY FUNCTIONS
>
> if ( !_SymInitialize( hProcess, szPath, TRUE ) )
> return FALSE;
>
> return TRUE;
>}
>
>BOOL GetLogicalAddress(HANDLE hProcess,
> 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 ( GetModuleFileNameEx(hProcess, (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);
>
> //NOTE BY La: I ADDED THIS CHECK TO AVOID AN ACCESS VIOLATION, SEE THE
>EXPLANATION
> //AFTER THE CODE SNIPPET !
> //FOR THE PRESENT, CONSIDER IT COMMENTED OUT !
> //if (IsBadReadPtr((PVOID)pNtHdr, sizeof(PVOID)))
> // return TRUE;
>
> 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!
>}
>
>
>
>BOOL ImagehlpAreDebugInfoAvailable(HANDLE hProcess, DWORD dwAddr)
>{
> try
> {
> IMAGEHLP_MODULE stIM ;
> memset ( &stIM , NULL , sizeof ( IMAGEHLP_MODULE ) ) ;
> stIM.SizeOfStruct = sizeof ( IMAGEHLP_MODULE ) ;
>
> if ( _SymGetModuleInfo ( hProcess , dwAddr, &stIM ) )
> {
> if (stIM.SymType == SymPdb || stIM.SymType == SymDia)
> return true;
> }
> }
> catch(...) { }
> return false;
>}
>
>
>// Walks the stack, and writes the results to the report file
>void ImagehlpStackWalk( HANDLE hProcess, HANDLE hThread, PCONTEXT
>pContext, ostream& output)
>{
> _tprintf(output, "Call stack (using IMAGEHLP):\r\n" );
>
> _tprintf(output, "Rva+Base Frame Logical addr\r\n" );
>
> // Could use SymSetOptions here to add the SYMOPT_DEFERRED_LOADS
>flag
>
> STACKFRAME sf;
> memset( &sf, 0, sizeof(sf) );
>
> // Initialize the STACKFRAME structure for the first call. This is
>only
> // necessary for Intel CPUs, and isn't mentioned in the
>documentation.
> sf.AddrPC.Offset = pContext->Eip;
> sf.AddrPC.Mode = AddrModeFlat;
> sf.AddrStack.Offset = pContext->Esp;
> sf.AddrStack.Mode = AddrModeFlat;
> sf.AddrFrame.Offset = pContext->Ebp;
> sf.AddrFrame.Mode = AddrModeFlat;
>
> while ( 1 )
> {
> if ( ! _StackWalk( IMAGE_FILE_MACHINE_I386,
> hProcess,
> hThread,
> &sf,
> pContext,
> 0,
> _SymFunctionTableAccess,
> _SymGetModuleBase,
> 0 ) )
> break;
>
> if ( 0 == sf.AddrFrame.Offset ) // Basic sanity check to make
>sure
> break; // the frame is OK. Bail if
>not.
>
> _tprintf(output, "%08X %08X ", sf.AddrPC.Offset,
>sf.AddrFrame.Offset );
>
> BYTE symbolBuffer[ sizeof(IMAGEHLP_SYMBOL) + 512 ];
> PIMAGEHLP_SYMBOL pSymbol = (PIMAGEHLP_SYMBOL)symbolBuffer;
> pSymbol->SizeOfStruct = sizeof(symbolBuffer);
> pSymbol->MaxNameLength = 512;
>
> DWORD symDisplacement = 0; // Displacement of the input
>address,
> // relative to the start of the
>symbol
>
> TCHAR szModule[MAX_PATH] = "";
> DWORD section = 0, offset = 0;
>
> GetLogicalAddress( hProcess, (PVOID)sf.AddrPC.Offset,
> szModule, sizeof(szModule), section, offset );
>
> if ( _SymGetSymFromAddr(hProcess, sf.AddrPC.Offset,
> &symDisplacement, pSymbol) )
> {
> // Symbol found! Look for line information too...
> IMAGEHLP_LINE stLine ;
> memset ( &stLine , NULL , sizeof ( IMAGEHLP_LINE ) ) ;
> stLine.SizeOfStruct = sizeof ( IMAGEHLP_LINE ) ;
> DWORD dwDis ;
>
> if (_SymGetLineFromAddr ( hProcess,
> (DWORD)sf.AddrPC.Offset ,
> &dwDis , &stLine ) )
> {
> // Line information found, print also file name and line number
> _tprintf(output, "%04X:%08X %hs+%X \t[File: %hs, line
>%li] \r\n",
> section, offset,
> pSymbol->Name, symDisplacement,
> stLine.FileName, stLine.LineNumber);
> }
> else
> {
> // Line information NOT found, print address and function name only
> // Call ImagehlpAreDebugInfoAvailable to warn the user that the
>function
> // name may be incorrect when PDB files are not available
> _tprintf(output, "%04X:%08X %hs+%X \t[%s] %s\r\n",
> section, offset,
> pSymbol->Name, symDisplacement, szModule,
> (ImagehlpAreDebugInfoAvailable(hProcess, sf.AddrPC.Offset)) ? ""
>: "[?]");
> }
>
> // A very big displacement is a corruption clue
> // a single function cannot be bigger than 100 KB !
> if (symDisplacement >= 100000)
> _tprintf(output, "(Warning: the previous line may be
>incorrect, check your PDB files!)\r\n");
> }
> else // No symbol found. Print out the logical address
>instead.
> {
> TCHAR szModule[MAX_PATH] = "";
> DWORD section = 0, offset = 0;
>
> GetLogicalAddress( hProcess, (PVOID)sf.AddrPC.Offset,
> szModule, sizeof(szModule), section,
>offset );
>
> _tprintf(output, "%04X:%08X %s\r\n",
> section, offset, szModule );
> }
> }
>}
>
>
>BOOL SpyContext(HANDLE hProcess, HANDLE hThread, PCONTEXT pCtx, PSTR
>szPath,
> ostream& output)
>{
> try
> {
> if ( !InitImagehlpFunctions( hProcess, szPath ) )
> {
> OutputDebugString("IMAGEHLP.DLL or its exported procs not found");
> return TRUE;
> }
>
> ImagehlpStackWalk( hProcess, hThread, pCtx, output );
>
> _SymCleanup( hProcess );
> }
> catch(...)
> {
> output << "Exception! (SpyContext)" << endl;
> }
>
> return TRUE;
>}
>
>BOOL SpyThreadInfo (DWORD dwPID, HANDLE hProcess, ostream& output)
>{
> BOOL bRet = FALSE;
> BOOL bFound = FALSE;
> HANDLE hModuleSnap = NULL;
> THREADENTRY32 te32 = {0};
>
> output << "Threads in process: " << (DWORD)dwPID << endl;
>
> // Take a snapshot of all threads in the specified process.
>
> hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwPID);
> if (hModuleSnap == INVALID_HANDLE_VALUE)
> return (FALSE);
>
> // Fill the size of the structure before using it.
>
> te32.dwSize = sizeof(THREADENTRY32);
>
> if (Thread32First(hModuleSnap, &te32))
> {
> do
> {
> if (te32.th32OwnerProcessID == dwPID)
> {
> output << "Thread ID : " << te32.th32ThreadID << endl;
>
> HANDLE hThread = OpenThread(THREAD_ALL_ACCESS ,
> /*, THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION
> | THREAD_SUSPEND_RESUME, */
> FALSE, te32.th32ThreadID);
>
> // I TRY TO STOP THE THREAD....
> if (dwPID != GetCurrentProcessId() && te32.th32ThreadID !=
>GetCurrentThreadId() )
> {
> DWORD dwSuspendCount = SuspendThread(hThread);
>
> output << "Suspend count: " << dwSuspendCount << endl;
>
> CONTEXT ctx;
> memset(&ctx, 0, sizeof(CONTEXT));
> ctx.ContextFlags = CONTEXT_FULL;
> if (GetThreadContext(hThread, &ctx))
> {
> SpyContext(hProcess, hThread, &ctx, "C:\\MYAPP", output);
> }
>
> DWORD dwResumeCount = ResumeThread(hThread);
>
> output << "Resume count: " << dwResumeCount << endl << endl;
> }
>
> CloseHandle(hThread);
>
> }
>
> }
> while (Thread32Next(hModuleSnap, &te32));
>
> bRet = TRUE;
> }
> else
> bRet = FALSE;
>
> // Do not forget to clean up the snapshot object.
> CloseHandle (hModuleSnap);
>
> return bRet;
>}
>
>
>
>void Spy ()
>{
> HANDLE hProcessSnap = NULL;
> PROCESSENTRY32 pe32 = {0};
>
> // Take a snapshot of all processes in the system.
>
> hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
>
> if (hProcessSnap == INVALID_HANDLE_VALUE)
> return;
>
> // Fill in the size of the structure before using it.
> pe32.dwSize = sizeof(PROCESSENTRY32);
>
> // Find the process I want to spy!
>
> ofstream outFile("C:\\THDUMP.TXT");
>
> if (Process32First(hProcessSnap, &pe32))
> {
> do
> {
> if (_stricmp(pe32.szExeFile, "MYAPP.exe") == 0)
> {
> HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
>pe32.th32ProcessID );
>
> SpyThreadInfo(pe32.th32ProcessID, hProcess, outFile);
>
> CloseHandle(hProcess);
>
> outFile << endl;
> }
> }
> while (Process32Next(hProcessSnap, &pe32));
> }
>
> outFile.close();
>
> // Do not forget to clean up the snapshot object.
> CloseHandle (hProcessSnap);
>}
>
>int main(int argc, char* argv[])
>{
> Spy();
> return 0;
>}
>
>
>I'm using this under Windows XP SP1, my user account has local
>administrator rights.
>Problems occur in GetLogicalAddress: first, GetModuleFileNameEx does
>not work, because it always returns "?" in szModule (in the exception
>manager, I used GetModuleFileName because I was inside the process, and
>it worked beautifully). Furthermore, probably the returned module
>handle is wrong too, because the following line causes an access
>violation:
>
> PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr );
>
>If I put a check before that line, uncommenting out the following
>lines:
>
> if (IsBadReadPtr((PVOID)pNtHdr, sizeof(PVOID)))
> return TRUE;
>
>then the "spy" tool works, but not perfectly. Here is a dump example
>("C:\MYAPP" contains the application to be inspected, its DLLs, and
>their PDB files. "Z:\SOURCE\MYAPP" contains the source code)
>
>Threads in process: 6628
>Thread ID : 7128
>Suspend count: 0
>Call stack (using IMAGEHLP):
>Rva+Base Frame Logical addr
>7FFE0304 0012FE90 0000:00000000 ?
>5F83988B 0012FEB8 0000:00000000 CWinThread::PumpMessage+30 [File:
>thrdcore.cpp, line 821]
>5F838FB0 0012FEDC 0000:00000000 CWinThread::Run+82 [File:
>thrdcore.cpp, line 487]
>5F839C64 0012FEE8 0000:00000000 CWinApp::Run+3A [File: appcore.cpp,
>line 400]
>5F8337CE 0012FF08 0000:00000000 AfxWinMain+CE [File: winmain.cpp,
>line 49]
>0049F348 0012FF20 0000:00000000 wWinMain+18 [File: appmodul.cpp,
>line 30]
>0049E4C1 0012FFC0 0000:00000000 wWinMainCRTStartup+1D1 [File:
>crtexe.c, line 330]
>77E814C7 0012FFF0 0000:00000000 BaseProcessStart+23
> [C:\WINDOWS\system32\kernel32.dll] Resume count: 1
>
>Thread ID : 7252
>Suspend count: 0
>Call stack (using IMAGEHLP):
>Rva+Base Frame Logical addr
>7FFE0304 02A7FDF8 0000:00000000 ?
>77E7AC21 02A7FE08 0000:00000000 WaitForSingleObject+F
> [C:\WINDOWS\system32\kernel32.dll]
>1003A09C 02A7FEDC 0000:00000000 fthWdManagerFunc+40C [File:
>Z:\SOURCE\MYAPP\Watchdog\Wd.cpp, line 300]
>5F838744 02A7FF80 0000:00000000 _AfxThreadEntry+2C4 [File:
>thrdcore.cpp, line 112]
>0097BF53 02A7FFB4 0000:00000000 _threadstartex+73 [File:
>threadex.c, line 212]
>77E7D33B 02A7FFEC 0000:00000000 BaseThreadStart+37
> [C:\WINDOWS\system32\kernel32.dll]
>Resume count: 1
>
>Here we inspect a MFC application named "MYAPP". It has a thread that
>owns the GUI, and another thread that is waiting for something (there
>are other threads, but I omitted them).
>Problems:
>- First, I'm still unable to know WHAT the thread is waiting for, but
>that's not the point
>- Since GetLogicalAddress fails, there are question marks here and
>there, and the logical address is always zero.
>- In this example, the function names and lines are correct (e.g. my
>threadproc "fthWdManagerFunc" actually calls WaitForSingleObject at
>line 300). But what if I don't have PDB files? I must look for the
>addressed in the MAP files. But sometimes the addresses listed under
>the "Rva+Base" column don't match with the correct functions in the MAP
>files (instead, in the exeption manager, addresses always match
>correctly).
>
>Is there something wrong with my code?
>Thank you very much in advance
>La.
Joseph M. Newcomer [MVP]
email: newcomer@xxxxxxxxxxxx
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
.
- References:
- Prev by Date: Re: How to force a file load?
- Next by Date: Re: How to get selected files Filename and full path?
- Previous by thread: Problem with code that inspects thread context and dumps stack trace
- Next by thread: how to disable entire menu?
- Index(es):
Relevant Pages
|