Exceptions and the Debugger

[Previous] [Next]

The Microsoft Visual C++ debugger has fantastic support for debugging exceptions. When a process's thread raises an exception, the operating system immediately notifies a debugger (if a debugger is attached). This notification is called a first-chance notification. Normally, the debugger responds to a first-chance notification by telling the thread to search for exception filters. If all of the exception filters return EXCEPTION_CONTINUE_SEARCH, the operating system notifies the debugger again with a last-chance notification. These two notifications exist in order to give the software developer more control over debugging an exception.

You use the debugger's Exceptions dialog box (shown below) to tell the debugger how to react to first-chance exception notifications.

click to view at full size.

As you can see, the dialog box consists of a list of all the system-defined exceptions. Each exception's 32-bit code is shown, followed by a text description and the debugger's action. In the window above, I have selected the access violation exception and changed its action to Stop Always. Now, whenever a thread in the debuggee raises an access violation, the debugger receives its first-chance notification and displays a message box similar to the following.

click to view at full size.

At this point, the thread has not had a chance to search for exception filters. I can now place breakpoints in the code, check variables, or examine the thread's call stack. No exception filters have executed yet; the exception has just occurred. If I now use the debugger to single-step through the code, I am prompted with the following message box.

Clicking on Cancel returns you to the debugger. Clicking on No tells the debuggee's thread to retry the CPU instruction that failed. For most exceptions, retrying the instruction will just raise the exception again and is not useful. However, for an exception raised with the RaiseException function, this tells the thread to continue executing as though the exception was never raised. Continuing in this manner can be particularly useful for debugging C++ programs: it would be as though a C++ throw statement never executed. C++ exception handling is discussed more toward the end of this chapter.

Finally, clicking on Yes allows the debuggee's thread to search for exception filters. If an exception filter is found that returns EXCEPTION_EXECUTE_HANDLER or EXCEPTION_CONTINUE_EXECUTION, all is well and the thread continues executing its code. However, if all filters return EXCEPTION_CONTINUE_SEARCH, the debugger receives a last-chance notification and displays a message box similar to the following.

click to view at full size.

At this point, you must debug the application or terminate it.

I have just shown you what happens if the debugger's action is set to Stop Always. However, for most exceptions, Stop If Not Handled is the default action. So, if a thread in the debuggee raises an exception, the debugger receives a first-chance notification. If the action is set to Stop If Not Handled, the debugger simply displays a string in the debugger's Output window indicating that it received the notification.

click to view at full size.

If the action for an access violation is set to Stop If Not Handled, the debugger allows the thread to search for exception filters. Only if the exception is not handled will the debugger display the message box shown here.

click to view at full size.

NOTE
The important point to remember is that first-chance notifications do not indicate problems or bugs in the application. In fact, this notification can only appear when your process is being debugged. The debugger is simply reporting that an exception was raised, but if the debugger does not display the message box, a filter handled the exception and the application continues to run just fine. A last-chance notification means that your code has a problem or bug that must be fixed.

Before leaving this section, I'd like to point out just one more thing about the debugger's Exceptions dialog box. This dialog box fully supports any software exceptions that you yourself define. All you have to do is enter your unique software exception code number, a string name for your exception, your preferred action, and then click on the Add button to add your exception to the list. The window below illustrates how I made the debugger aware of my own software exception.

click to view at full size.

The Spreadsheet Sample Application

The Spreadsheet sample application ("25 Spreadsheet.exe") listed in Figure 25-1 shows how to sparsely commit storage to a reserved address space region using structured exception handling. The source code and resource files for the application are in the "25-Spreadsheet" directory on the companion CD-ROM. When you execute the Spreadsheet sample, the following dialog box appears.

Internally, the application reserved a region for a two-dimensional spreadsheet. The spreadsheet contains 256 rows by 1024 columns and each cell is 1024 bytes in size. If the application were to commit storage up front for the entire spreadsheet, 268,435,456 bytes, or 256 MB, of storage would be required. In order to conserve precious storage space, the application reserves a 256-MB region of address space without committing any storage backing this region.

Let's say that the user attempts to place the value 12345 in a cell existing at row 100, column 100 (as shown in the previous window). When the user clicks on the Write Cell button, the application code tries to write to that location in the spreadsheet. Of course, this attempted write raises an access violation. However, since I'm using SEH in the application, my exception filter detects the attempted write, displays the "Violation: Attempting to Write" message at the bottom of the dialog box, commits storage for the cell, and has the CPU re-execute the instruction that raised the violation. Since storage has been committed, the value is written to the spreadsheet's cell.

Let's try another experiment. Try to read the value in the cell at row 5, column 20. When you attempt to read from this cell, an access violation is again raised. For an attempted read, the exception filter doesn't commit storage, but it does display the "Violation: Attempting to Read" message in the dialog box. The program gracefully recovers from the failed read by removing the number in the Value field of the dialog box, as shown here.

For our third experiment, try to read the cell value in row 100, column 100. Since storage was committed for this cell, no violation will occur and no exception filter is executed (improving the performance). The dialog box looks like this.

Now for our fourth and last experiment: Try to write the value 54321 into the cell at row 100, column 101. When you attempt this, no violation occurs because this cell is on the same storage page as the cell at (100, 100). We can verify this with the "No Violation raised" message at the bottom of the dialog box shown here.

I tend to use virtual memory and SEH quite a bit in my own projects. After a while, I decided to create a templated C++ class, CVMArray, which encapsulates all of the hard work. You can find the source code for this C++ class in the VMArray.h file (part of the Spreadsheet sample shown in Figure 25-1). You can work with the CVMArray class in two ways. First, you can just create an instance of this class passing the maximum number of elements in the array to the constructor. The class automatically sets up a process-wide unhandled exception filter so whenever any code in any thread accesses a memory address in the virtual memory array, the unhandled exception filter calls VirtualAlloc to commit storage for the new element and returns EXCEPTION_CONTINUE_EXECUTION. Using the CVMArray class in this way allows you to use sparse storage with just a few lines of code, and you don't have to sprinkle SEH frames throughout your source code. The only downside to this approach is that your application can't recover gracefully if for some reason storage cannot be committed when needed.

The second way to use the CVMArray class is to derive your own C++ class from it. If you use the derived class, you still get all of the benefits of the base class—but now you also get to add features of your own. For example, you can now handle insufficient storage problems more gracefully by overloading the virtual OnAccessViolation function. The Spreadsheet sample application shows how a CVMArray-derived class can add these features.

Figure 25-1. The Spreadsheet sample application

Spreadsheet.cpp

 /****************************************************************************** Module: Spreadsheet.cpp Notices: Copyright (c) 2000 Jeffrey Richter ******************************************************************************/ #include "..\CmnHdr.h" /* See Appendix A. */ #include <windowsx.h> #include <tchar.h> #include "Resource.h" #include "VMArray.h" /////////////////////////////////////////////////////////////////////////////// HWND g_hwnd; // Global window handle used for SEH reporting const int g_nNumRows = 256; const int g_nNumCols = 1024; // Declare the contents of a single cell in the spreadsheet typedef struct { DWORD dwValue; BYTE bDummy[1020]; } CELL, *PCELL; // Declare the data type for an entire spreadsheet typedef CELL SPREADSHEET[g_nNumRows][g_nNumCols]; typedef SPREADSHEET *PSPREADSHEET; /////////////////////////////////////////////////////////////////////////////// // A spreadsheet is a 2-dimensional array of CELLs class CVMSpreadsheet : public CVMArray<CELL> { public: CVMSpreadsheet() : CVMArray<CELL>(g_nNumRows * g_nNumCols) {} private: LONG OnAccessViolation(PVOID pvAddrTouched, BOOL fAttemptedRead, PEXCEPTION_POINTERS pep, BOOL fRetryUntilSuccessful); }; /////////////////////////////////////////////////////////////////////////////// LONG CVMSpreadsheet::OnAccessViolation(PVOID pvAddrTouched, BOOL fAttemptedRead, PEXCEPTION_POINTERS pep, BOOL fRetryUntilSuccessful) { TCHAR sz[200]; wsprintf(sz, TEXT("Violation: Attempting to %s"), fAttemptedRead ? TEXT("Read") : TEXT("Write")); SetDlgItemText(g_hwnd, IDC_LOG, sz); LONG lDisposition = EXCEPTION_EXECUTE_HANDLER; if (!fAttemptedRead) { // Return whatever the base class says to do lDisposition = CVMArray<CELL>::OnAccessViolation(pvAddrTouched, fAttemptedRead, pep, fRetryUntilSuccessful); } return(lDisposition); } /////////////////////////////////////////////////////////////////////////////// // This is the global CVMSpreadsheet object static CVMSpreadsheet g_ssObject; // Create a global pointer that points to the entire spreadsheet region SPREADSHEET& g_ss = * (PSPREADSHEET) (PCELL) g_ssObject; /////////////////////////////////////////////////////////////////////////////// BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) { chSETDLGICONS(hwnd, IDI_SPREADSHEET); g_hwnd = hwnd; // Save for SEH reporting // Put default values in the dialog box controls Edit_LimitText(GetDlgItem(hwnd, IDC_ROW), 3); Edit_LimitText(GetDlgItem(hwnd, IDC_COLUMN), 4); Edit_LimitText(GetDlgItem(hwnd, IDC_VALUE), 7); SetDlgItemInt(hwnd, IDC_ROW, 100, FALSE); SetDlgItemInt(hwnd, IDC_COLUMN, 100, FALSE); SetDlgItemInt(hwnd, IDC_VALUE, 12345, FALSE); return(TRUE); } /////////////////////////////////////////////////////////////////////////////// void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) { int nRow, nCol; switch (id) { case IDCANCEL: EndDialog(hwnd, id); break; case IDC_ROW: // User modified the row, update the UI nRow = GetDlgItemInt(hwnd, IDC_ROW, NULL, FALSE); EnableWindow(GetDlgItem(hwnd, IDC_READCELL), chINRANGE(0, nRow, g_nNumRows - 1)); EnableWindow(GetDlgItem(hwnd, IDC_WRITECELL), chINRANGE(0, nRow, g_nNumRows - 1)); break; case IDC_COLUMN: // User modified the column, update the UI nCol = GetDlgItemInt(hwnd, IDC_COLUMN, NULL, FALSE); EnableWindow(GetDlgItem(hwnd, IDC_READCELL), chINRANGE(0, nCol, g_nNumCols - 1)); EnableWindow(GetDlgItem(hwnd, IDC_WRITECELL), chINRANGE(0, nCol, g_nNumCols - 1)); break; case IDC_READCELL: // Try to read a value from the user's selected cell SetDlgItemText(g_hwnd, IDC_LOG, TEXT("No violation raised")); nRow = GetDlgItemInt(hwnd, IDC_ROW, NULL, FALSE); nCol = GetDlgItemInt(hwnd, IDC_COLUMN, NULL, FALSE); _ _try { SetDlgItemInt(hwnd, IDC_VALUE, g_ss[nRow][nCol].dwValue, FALSE); } _ _except ( g_ssObject.ExceptionFilter(GetExceptionInformation(), FALSE)) { // The cell is not backed by storage, the cell contains nothing. SetDlgItemText(hwnd, IDC_VALUE, TEXT("")); } break; case IDC_WRITECELL: // Try to read a value from the user's selected cell SetDlgItemText(g_hwnd, IDC_LOG, TEXT("No violation raised")); nRow = GetDlgItemInt(hwnd, IDC_ROW, NULL, FALSE); nCol = GetDlgItemInt(hwnd, IDC_COLUMN, NULL, FALSE); // If the cell is not backed by storage, an access violation is // raised causing storage to automatically be committed. g_ss[nRow][nCol].dwValue = GetDlgItemInt(hwnd, IDC_VALUE, NULL, FALSE); break; } } /////////////////////////////////////////////////////////////////////////////// INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog); chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand); } return(FALSE); } /////////////////////////////////////////////////////////////////////////////// int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) { DialogBox(hinstExe, MAKEINTRESOURCE(IDD_SPREADSHEET), NULL, Dlg_Proc); return(0); } //////////////////////////////// End of File ////////////////////////////////// 

Spreadsheet.rc

 //Microsoft Developer Studio generated resource script. // #include "Resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "Resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_SPREADSHEET DIALOG DISCARDABLE 18, 18, 164, 165 STYLE DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Spreadsheet" FONT 8, "MS Sans Serif" BEGIN LTEXT "Cell size:\nRows:\nColumns:\nTotal size:",IDC_STATIC,4, 4,36,36 LTEXT "1024 bytes\n256\n1024\n256 MB (268,435,456 bytes)", IDC_STATIC,44,4,104,36 LTEXT "R&ow (0-255):",IDC_STATIC,4,56,42,8 EDITTEXT IDC_ROW,60,52,40,14,ES_AUTOHSCROLL | ES_NUMBER LTEXT "&Column (0-1023):",IDC_STATIC,4,76,54,8 EDITTEXT IDC_COLUMN,60,72,40,14,ES_AUTOHSCROLL | ES_NUMBER PUSHBUTTON "&Read Cell",IDC_READCELL,108,72,50,14 LTEXT "&Value:",IDC_STATIC,4,96,21,8 EDITTEXT IDC_VALUE,60,92,40,14,ES_AUTOHSCROLL | ES_NUMBER PUSHBUTTON "&Write Cell",IDC_WRITECELL,108,92,50,14 LTEXT "Execution lo&g:",IDC_STATIC,4,118,48,8 EDITTEXT IDC_LOG,4,132,156,28,ES_MULTILINE | ES_AUTOHSCROLL | ES_READONLY END ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_SPREADSHEET ICON DISCARDABLE "Spreadsheet.Ico" #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED 

VMArray.h

 /****************************************************************************** Module: VMArray.h Notices: Copyright (c) 2000 Jeffrey Richter ******************************************************************************/ #pragma once /////////////////////////////////////////////////////////////////////////////// // NOTE: This C++ class is not thread safe. You cannot have multiple threads // creating and destroying objects of this class at the same time. // However, once created, multiple threads can access different CVMArray // objects simultaneously and you can have multiple threads accessing a single // CVMArray object if you manually synchronize access to the object yourself. /////////////////////////////////////////////////////////////////////////////// template <class TYPE> class CVMArray { public: // Reserves sparse array of elements CVMArray(DWORD dwReserveElements); // Frees sparse array of elements virtual ~CVMArray(); // Allows accessing an element in the array operator TYPE*() { return(m_pArray); } operator const TYPE*() const { return(m_pArray); } // Can be called for fine-tuned handling if commit fails LONG ExceptionFilter(PEXCEPTION_POINTERS pep, BOOL fRetryUntilSuccessful = FALSE); protected: // Override this to fine-tune handling of access violation virtual LONG OnAccessViolation(PVOID pvAddrTouched, BOOL fAttemptedRead, PEXCEPTION_POINTERS pep, BOOL fRetryUntilSuccessful); private: static CVMArray* sm_pHead; // Address of first object CVMArray* m_pNext; // Address of next object TYPE* m_pArray; // Pointer to reserved region array DWORD m_cbReserve; // Size of reserved region array (in bytes) private: // Address of previous unhandled exception filter static PTOP_LEVEL_EXCEPTION_FILTER sm_pfnUnhandledExceptionFilterPrev; // Our global unhandled exception filter for instances of this class static LONG WINAPI UnhandledExceptionFilter(PEXCEPTION_POINTERS pep); }; /////////////////////////////////////////////////////////////////////////////// // The head of the linked-list of objects template <class TYPE> CVMArray<TYPE>* CVMArray<TYPE>::sm_pHead = NULL; // Address of previous unhandled exception filter template <class TYPE> PTOP_LEVEL_EXCEPTION_FILTER CVMArray<TYPE>::sm_pfnUnhandledExceptionFilterPrev; /////////////////////////////////////////////////////////////////////////////// template <class TYPE> CVMArray<TYPE>::CVMArray(DWORD dwReserveElements) { if (sm_pHead == NULL) { // Install our global unhandled exception filter when // creating the first instance of the class. sm_pfnUnhandledExceptionFilterPrev = SetUnhandledExceptionFilter(UnhandledExceptionFilter); } m_pNext = sm_pHead; // The next node was at the head sm_pHead = this; // This node is now at the head m_cbReserve = sizeof(TYPE) * dwReserveElements; // Reserve a region for the entire array m_pArray = (TYPE*) VirtualAlloc(NULL, m_cbReserve, MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE); chASSERT(m_pArray != NULL); } /////////////////////////////////////////////////////////////////////////////// template <class TYPE> CVMArray<TYPE>::~CVMArray() { // Free the array's region (decommitting all storage within it) VirtualFree(m_pArray, 0, MEM_RELEASE); // Remove this object from the linked list CVMArray* p = sm_pHead; if (p == this) { // Removing the head node sm_pHead = p->m_pNext; } else { BOOL fFound = FALSE; // Walk list from head and fix pointers for (; !fFound && (p->m_pNext != NULL); p = p->m_pNext) { if (p->m_pNext == this) { // Make the node that points to us point to the next node p->m_pNext = p->m_pNext->m_pNext; break; } } chASSERT(fFound); } } /////////////////////////////////////////////////////////////////////////////// // Default handling of access violations attempts to commit storage template <class TYPE> LONG CVMArray<TYPE>::OnAccessViolation(PVOID pvAddrTouched, BOOL fAttemptedRead, PEXCEPTION_POINTERS pep, BOOL fRetryUntilSuccessful) { BOOL fCommittedStorage = FALSE; // Assume committing storage fails do { // Attempt to commit storage fCommittedStorage = (NULL != VirtualAlloc(pvAddrTouched, sizeof(TYPE), MEM_COMMIT, PAGE_READWRITE)); // If storage could not be committed and we're supposed to keep trying // until we succeed, prompt user to free storage if (!fCommittedStorage && fRetryUntilSuccessful) { MessageBox(NULL, TEXT("Please close some other applications and Press OK."), TEXT("Insufficient Memory Available"), MB_ICONWARNING | MB_OK); } } while (!fCommittedStorage && fRetryUntilSuccessful); // If storage committed, try again. If not, execute the handler return(fCommittedStorage ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER); } /////////////////////////////////////////////////////////////////////////////// // The filter associated with a single CVMArray object template <class TYPE> LONG CVMArray<TYPE>::ExceptionFilter(PEXCEPTION_POINTERS pep, BOOL fRetryUntilSuccessful) { // Default to trying another filter (safest thing to do) LONG lDisposition = EXCEPTION_CONTINUE_SEARCH; // We only fix access violations if (pep->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) return(lDisposition); // Get address of attempted access and get attempted read or write PVOID pvAddrTouched = (PVOID) pep->ExceptionRecord->ExceptionInformation[1]; BOOL fAttemptedRead = (pep->ExceptionRecord->ExceptionInformation[0] == 0); // Is attempted access within this VMArray's reserved region? if ((m_pArray <= pvAddrTouched) && (pvAddrTouched < ((PBYTE) m_pArray + m_cbReserve))) { // Access is in this array, try to fix the problem lDisposition = OnAccessViolation(pvAddrTouched, fAttemptedRead, pep, fRetryUntilSuccessful); } return(lDisposition); } /////////////////////////////////////////////////////////////////////////////// // The filter associated with all CVMArray objects template <class TYPE> LONG WINAPI CVMArray<TYPE>::UnhandledExceptionFilter(PEXCEPTION_POINTERS pep) { // Default to trying another filter (safest thing to do) LONG lDisposition = EXCEPTION_CONTINUE_SEARCH; // We only fix access violations if (pep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { // Walk all the nodes in the linked-list for (CVMArray* p = sm_pHead; p != NULL; p = p->m_pNext) { // Ask this node if it can fix the problem. // NOTE: The problem MUST be fixed or the process will be terminated! lDisposition = p->ExceptionFilter(pep, TRUE); // If we found the node and it fixed the problem, stop the loop if (lDisposition != EXCEPTION_CONTINUE_SEARCH) break; } } // If no node fixed the problem, try the previous exception filter if (lDisposition == EXCEPTION_CONTINUE_SEARCH) lDisposition = sm_pfnUnhandledExceptionFilterPrev(pep); return(lDisposition); } //////////////////////////////// End of File ////////////////////////////////// 



Programming Applications for Microsoft Windows
Programming Applications for Microsoft Windows (Microsoft Programming Series)
ISBN: 1572319968
EAN: 2147483647
Year: 1999
Pages: 193

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net