First chance exception debug question

Started by
17 comments, last by oliii 17 years ago
I am getting a curious first chance exception error 0xC0000005:Access Violation in my game. I have narrowed it down to one particular line, a declaration of a pointer to a unit class in my game: CGroundUnit* pGroundUnit; With this line commented out I get no exception. With the line included I get an unreferenced local variable from the Visual C++ (6.0) compiler that I am using and then I get an exception when I enter the procedure containing this line. Note that the first line of the procedure simply does a return so the above declaration is never executed. The exception is a new development in my game that I have been working on for years. My code contains over 100 declarations exactly like the above one and a week ago I was not experiencing this exception. My program runs fine if I ignore the exceptions. My program runs fine if I simply step over the offending procedure call using F10 key. But if I enter via F11, I get an exception. Any help would be appreciated. thanks, steve coward
Advertisement
The compiler can strip 'empty' functions, if they do nothing. Hence the procedure is never called if all it does is return. That can happen when you create debug functions, that have their implementation #defined out, so in some configurations, the function does absolutely nothing.

My point is, if the function does nothing, it wont even be called, and you won't get an exception.

If the function does something (here, it just add a local variable to the stack), it will possibly be called (it should be stripped though). If the object with that member function is called upon and NULL, then you will get the exception.

It looks like what's happening here.

1) Object is NULL.
2) empty function stripped from the binary, so never called.
3) function not empty, so not stripped, so called but the objects is NULL -> Exception at 0x0000005.

Sorry, a bit winded, early morning.

Everything is better with Metal.

basically

class C_Object{public:    int dummy;    C_Object() : dummy(rand())    {}    void DoNothing() {}    void DoSomething() { printf("a-Ha! dummy = %d", dummy); }};C_Object* pNULLObject = NULL;pNULLObject->DoNothing(); // should not crashpNULLObject->DoSomething(); // should crash with access violation 0x00000005

Everything is better with Metal.

As I stated in my initial post, the offending function (OnLButtonUp1) is executed because I am setting a breakpoint at the point of entry and hitting F11. The first line of the function is a return. After the return there are many variable declarations and many lines of code that do not get executed. Oddly enough a couple of the declarations after the return but still within the body of the function are several similar style of declarations:
CGroundUnit* pGroundUnitPrevSelected;
CGroundUnit* pGroundUnitCurrSelected;

These two seem to be OK. The situation is illustrated below. This is contrived code since my original code has been hacked to simplify the situation. (This is why many of the variables in the call stack are uninitialized.) (Also the classes that are being constructed are large, containing many STL and Microsoft lists, maps etc.) This code is being called from within the constructor of the CGS_GameStarted1 class.


bool CGS_GameStarted1::OnLButtonUp1(bool bAltPressed, bool bCtrlPressed, bool bShiftPressed, bool bSpacePressed, CPoint point)
{
return(true);
.
.
.
CGroundUnit* pGroundUnitPrevSelected;
CGroundUnit* pGroundUnitCurrSelected;

.
.
.
// comment out the next line to avoid exception
CGroundUnit* pGroundUnit;
return(false);
}

Looking at call stack, at the point in time with the call stack as below, and sitting at my breakpoint at OnLButtonUp1(), hitting F11 causes an exception (if that one line of code is uncommented.) I do no understand this.

CGS_GameStarted1::CGS_GameStarted1(GameState EGS_GAME_STARTED, CD3DApplication * 0x00000000, CMyD3DApplication * 0xcccccccc, CWindowRegions * 0xcccccccc, CDrawUtilities * 0xcccccccc, _APP_GAME_STATUS_INFO * 0xcccccccc, CDirectSound * 0xcccccccc) line 99
CMyD3DApplication::makeGameStates() line 5098 + 99 bytes
CMyD3DApplication::CMyD3DApplication() line 154
WinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, HINSTANCE__ * 0x00000000, HINSTANCE__ * 0x00000000) line 125
WinMainCRTStartup() line 330 + 54 bytes
KERNEL32! 7c816fd7()


CGS_GameStarted1::CGS_GameStarted1(GameState gameState, CD3DApplication* pD3DApp, CMyD3DApplication* pMyD3DApp, CWindowRegions* pWindowsRgns, CDrawUtilities* pDrawUtilities, LPAPP_GAME_STATUS_INFO pGameStatusInfo, CDirectSound* pDS)
{
OnLButtonUp1(false, false, false, false, CPoint(1,1));
.
.
.
}
I continue to play with this.

When I change the name of the pointer variable from

CGroundUnit* pGroundUnit;


to

CGroundUnit* _pGroundUnit;

I do not get an exception.

The name pGroundUnit is a generic name I use for over a hundred different declarations of pointers to CGroundUnit objects in my code. Somehow a collision is occurring?
You're barking up the wrong tree.

An access violation is a result of accessing memory you aren't allowed to access -- most often as a result of either failing to initialize a pointer or some memory, or attempting to access a null pointer.

Memory corruption errors can be subtle, and can sometimes manifest themselves (actually crash) very far from the actual location of the problem. Furthermore, small changes to the code generation can drastically alter the program flow in such a way as to make the problem appear to go away, when it really is still there waiting to bite you later.

Quote:
This is contrived code since my original code has been hacked to simplify the situation. (This is why many of the variables in the call stack are uninitialized.)

THIS IS BAD.

Because errors of this type are so subtle, presenting us with "contrived" code is not going to help us solve the problem -- you may have accidentally hidden the problem from us without knowing. Your handwaving dismissal of the uninitialized variables on the stack is suspicious, because uninitialized variables are a potential cause of the problem.

So, show us the real code for the function that crashes, exactly as you see it when you observe the crash. Also, show us the code for the function that immediately called the function that crashed, if you can. Also, use tags (using lowercase letters) to block off your code in posts so its easier to read.

Also, post the exact complete error message. Typically they look like: "error 0x00000005: Acces violation when trying to read 0x????????," or something. The question marks can be a big clue -- if the question marks form an address near 0, for example, you're probably dereferencing a null pointer.

Also, on an unrelated note, consider upgrading to Visual C++ Express. It's free, and it actually lets you write C++ instead of the pre-standard attempt at C++ that VC6 supports. It's just better.

Actually, VC6 might even be the cause of the problem, since it's prestandard C++. You're calling something that looks like it might be a virtual function (OnWhatever methods tend to be overridable, after all). Now, normally dynamic dispatch can't happen in a constructor because the object is not fully constructed -- typically then most compilers would optimize away attempts at doing so (and thus remove attempts to touch the 'this' pointer). VC6 may not have done this, since it's possible it decided to allow dynamic dispatch in constructors. This means you might be seeing a crash that's really caused by an attempt to access a screwed up 'this' pointer rather than a screwed up member of the class. The fact that unreachable code has an apparent effect on the code generation is also suspect.

This is a pretty blind shot in the dark, though. I can't do much more than speculate and wave my hands about without seeing the actual code that crashes, the actual line that does so, and the error messages. The call stack for this new example would also be helpful.
jpetrie - thank you for your response.

Perhaps I have not communicated clearly. The code that I am posting *is* a failing case - it is contrived only in the
manner that the code is useless in a meaningful way. Calling a OnLButtonUp function from a constructor is contrived. I mention this
only to prevent people from responding back with "Why are you calling function xxx from within yyy?", etc.
Nonetheless the code should operate properly as far as I can tell.

My project contains 100,000 lines of code. It takes 2 minutes to reach the failing line of code in the real world case.
I have reproduced the original error message in a test case that executes 3-4 lines of my code in a matter of 1 second.
Furthermore I have narrowed down the difference between a failing case to a passing case by the presence of 1 line of code
(out of 100,000). It seems to me I am standing under a correct tree.


Here is the code presented in a more readable form.
[Small changes have been added from the code posted above to further simplify
the situation. Please do not reference the above code.]


INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT ){	CMyD3DApplication myD3DApp;	.	.	.}


class CMyD3DApplication : public CD3DApplication{...}


CMyD3DApplication::CMyD3DApplication(){	makeGameStates();	.	.	.}


void CMyD3DApplication::makeGameStates(){	CGameState* pGameState;	pGameState = (CGameState*)new CGS_GameStarted1(EGS_GAME_STARTED1, NULL, NULL, NULL, NULL, NULL, NULL);...}


CGS_GameStarted::CGS_GameStarted(GameState gameState, CD3DApplication* pD3DApp, CMyD3DApplication* pMyD3DApp, CWindowRegions* pWindowsRgns, CDrawUtilities* pDrawUtilities, LPAPP_GAME_STATUS_INFO pGameStatusInfo, CDirectSound* pDS){   OnLButtonUp1(false, false, false, false, CCltPoint(1,1));...}


The exception occurs upon entry into this procedure below. I employ polymorphism in my unit definitions -
CUnit is base class, CGroundUnit derived from CUnit, CVehicle derived from CGroundUnit.

bool CGS_GameStarted::OnLButtonUp1(bool bAltPressed, bool bCtrlPressed, bool bShiftPressed, bool bSpacePressed, CCltPoint point){	//CUnit* pGroundUnit; // passes when other 5 declarations are commented out	//CVehicleUnit* pGroundUnit; // fails when other 5 declarations are commented out	//CVehicleUnit* _pGroundUnit; // passes when other 5 declarations are commented out	//CGroundUnit* pGroundUnit; // fails when other 5 declarations are commented out	CGroundUnit* _pGroundUnit; // passes when other 5 declarations are commented out	//CGroundUnit* y; // passes when other 5 declarations are commented out	bool x = true;	return(x);	.	.	.}



Here is call stack. At this point the execution has stopped because an exception has just occurred.
CGS_GameStarted1::OnLButtonUp1(unsigned char 0, unsigned char 0, unsigned char 0, unsigned char 0, CPoint {x=1 y=1}) line 658CGS_GameStarted1::CGS_GameStarted1(GameState EGS_GAME_STARTED, CD3DApplication * 0x00000000, CMyD3DApplication * 0x00000000, CWindowRegions * 0x00000000, CDrawUtilities * 0x00000000, _APP_GAME_STATUS_INFO * 0x00000000, CDirectSound * 0x00000000) line 102CMyD3DApplication::makeGameStates() line 5090 + 54 bytesCMyD3DApplication::CMyD3DApplication() line 154WinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, HINSTANCE__ * 0x00000000, HINSTANCE__ * 0x00000000) line 125WinMainCRTStartup() line 330 + 54 bytesKERNEL32! 7c816fd7()


The error statement is exactly:
First-chance exception in TankBattle.exe: 0xC0000005: Access Violation.


It occurred to me that there might be a collision with the Microsoft virtual function OnLButtonUp(), which is why I renamed my function to OnLButtonUp1().


Perhaps I should upgrade to the free version of Visual C++;
Quote:
It seems to me I am standing under a correct tree.

Yes, but the implication in your original posts that it has something to do with the declaration of a variable is barking up the wrong tree. I was referring to the fact that memory corruption errors often are caused by something far from the crash site.

Quote:
The code that I am posting *is* a failing case - it is contrived only in the
manner that the code is useless in a meaningful way.

I see; I took "contrived" to mean you doctored it like you did your most-current snippets (where you removed some code and replaced them with "..."). It's doctoring I take issue with, not the fact that the code is reduced. As long as the code you're working with actually crashes, that's fine. Unfortunately, the code you presented most recently is still doctored so doesn't show me everything I'd like to see.

My suspicion is still with the state of the "this" pointer. There's nothing in the function that is crashing that could cause a crash, as far as you've known, so the only possibilities are:

a) Something screwy with the "this" pointer. This should not have an effect, but since we are talking about VC6, who knows what weird and wonderful crap it's generating.
b) Something screwy with the stack itself. I raise this issue because your comments imply that the number of variables declared at the entry point of the function has an effect (is this right?). All that should change, given that they're all pointers, is the amount added to the stack pointer when the function is entered. If something overwrote the stack somewhere that might be enough to sufficient break something the boolean value of true is copied into the location for "x."

In both cases the line the source line the debugger shows you will be misleading, the actual faulting instruction is probably part of the prologue or call setup generated (e.g., assembly with no matching source lines).

Without even further information (the full code and headers for your simplified example), I'm not sure.

Something you might try is breaking into the debugger fairly early, enabling view of the disassembly, and single-stepping instruction-by-instruction until you find the offending instruction.
Unless I'm overlooking something, there is no reason why simply changing the name of a variable to start with an underscore should change the behaviour of something like this.

I am suspicious that the error message does not give the address of the access violation, though perhaps this is because it's an old version of VC. I would definitely recommend upgrading to a newer version - MS do not support VC6, it's effectively dead.

The only other thing of note is that it's a member function inside a constructor that is throwing the AV - which should be OK, unless something inside the member function is assuming the object already exists, which it doesn't. But there's nothing in the code you've shown us to suggest that.

Assuming there's nothing you're not showing us, something a bit funny is going on. Can you show the disassembly at the point the exception is thrown? We really need to know the location that is being violated in order to determine what is causing this.
///////////////////////////////
// FAIL
/////////////////////////////////

0043E25F   int         3--- d:\my projects\tankbattleexc\gs_gamestarted1.cpp  -------------------------------------------------------------------------------------656:  // assumes g_dpActivePlayer has been set657:  bool CGS_GameStarted1::OnLButtonUp1(bool bAltPressed, bool bCtrlPressed, bool bShiftPressed, bool bSpacePressed, CCltPoint point)658:  {0043E260   push        ebp0043E261   mov         ebp,esp0043E263   push        0FFh0043E265   push        offset __ehhandler$?OnLButtonUp1@CGS_GameStarted1@@QAE_N_N000VCPoint@@@Z (00724110)0043E26A   mov         eax,fs:[00000000]0043E270   push        eax0043E271   mov         dword ptr fs:[0],esp0043E278   sub         esp,2ACh0043E27E   push        ebx0043E27F   push        esi0043E280   push        edi0043E281   push        ecx0043E282   lea         edi,[ebp-2B8h]0043E288   mov         ecx,0ABh0043E28D   mov         eax,0CCCCCCCCh0043E292   rep stos    dword ptr [edi]0043E294   pop         ecx0043E295   mov         dword ptr [ebp-10h],ecx659:      //CUnit* pGroundUnit; // passes660:      //CVehicleUnit* pGroundUnit; // fails661:      //CVehicleUnit* _pGroundUnit; // passes662:      CGroundUnit* pGroundUnit; // fails663:      //CGroundUnit* _pGroundUnit; // passes664:      //CGroundUnit* y; // passes665:      bool x = true;0043E298   mov         byte ptr [ebp-18h],1666:      return(x);0043E29C   mov         al,byte ptr [ebp-18h]


EAX = 0012F0C4 EBX = 7FFD4000 ECX = 01116F08 EDX = 00000001 ESI = 00000000 EDI = 0012F108 EIP = 0043E260 ESP = 0012F054 EBP = 0012F114 EFL = 00000246 CS = 001B DS = 0023 ES = 0023 SS = 0023 FS = 003B GS = 0000 OV=0 UP=0 EI=1 PL=0 ZR=1 AC=0 PE=1 CY=0 ST0 = +0.00000000000000000e+0000 ST1 = +0.00000000000000000e+0000 ST2 = +0.00000000000000000e+0000 ST3 = +0.00000000000000000e+0000 ST4 = +9.96093750000000000e-0001 ST5 = +9.99609374999994360e-0001 ST6 = +1.00000000000000000e+0000 ST7 = +1.00000000000000000e+0000 CTRL = 027F STAT = 4020 TAGS = FFFF EIP = 773D6E8A CS = 001B DS = 0023 EDO = 773D1C48


//////////////////////////////////
// PASS
////////////////////////////////////
0043E25F   int         3--- D:\My Projects\TankBattleExc\GS_GameStarted1.cpp  -------------------------------------------------------------------------------------656:  // assumes g_dpActivePlayer has been set657:  bool CGS_GameStarted1::OnLButtonUp1(bool bAltPressed, bool bCtrlPressed, bool bShiftPressed, bool bSpacePressed, CCltPoint point)658:  {0043E260   push        ebp0043E261   mov         ebp,esp0043E263   push        0FFh0043E265   push        offset __ehhandler$?OnLButtonUp1@CGS_GameStarted1@@QAE_N_N000VCPoint@@@Z (00724110)0043E26A   mov         eax,fs:[00000000]0043E270   push        eax0043E271   mov         dword ptr fs:[0],esp0043E278   sub         esp,2ACh0043E27E   push        ebx0043E27F   push        esi0043E280   push        edi0043E281   push        ecx0043E282   lea         edi,[ebp-2B8h]0043E288   mov         ecx,0ABh0043E28D   mov         eax,0CCCCCCCCh0043E292   rep stos    dword ptr [edi]0043E294   pop         ecx0043E295   mov         dword ptr [ebp-10h],ecx659:      //CUnit* pGroundUnit; // passes660:      //CVehicleUnit* pGroundUnit; // fails661:      //CVehicleUnit* _pGroundUnit; // passes662:      //CGroundUnit* pGroundUnit; // fails663:      CGroundUnit* _pGroundUnit; // passes664:      //CGroundUnit* y; // passes665:      bool x = true;0043E298   mov         byte ptr [ebp-18h],1666:      return(x);0043E29C   mov         al,byte ptr [ebp-18h]


EAX = 0012F0C4 EBX = 7FFDA000 ECX = 01116F08 EDX = 00000001 ESI = 00000000 EDI = 0012F108 EIP = 0043E260 ESP = 0012F054 EBP = 0012F114 EFL = 00000246 CS = 001B DS = 0023 ES = 0023 SS = 0023 FS = 003B GS = 0000 OV=0 UP=0 EI=1 PL=0 ZR=1 AC=0 PE=1 CY=0 ST0 = +0.00000000000000000e+0000 ST1 = +0.00000000000000000e+0000 ST2 = +0.00000000000000000e+0000 ST3 = +0.00000000000000000e+0000 ST4 = +9.96093750000000000e-0001 ST5 = +9.99609374999994360e-0001 ST6 = +1.00000000000000000e+0000 ST7 = +1.00000000000000000e+0000 CTRL = 027F STAT = 4020 TAGS = FFFF EIP = 773D6E8A CS = 001B DS = 0023 EDO = 773D1C48


Registers are identical. Disassembly is identical.

Looking into migrating to VC8 I see that it would not support MFC for free.
Looks like much work fixing all porting issues as well.

This topic is closed to new replies.

Advertisement