Dgoldman's WebLog

The daily adventures of an Escalation Engineer

Defensive programming 101

Defensive programming 101

  • Comments 1

What is defensive programming? Defensive programming is an approach to improve your source code and software, in the general terms of:

  • Quality - Reducing the number of bugs
  • Making source code readable: Comments, Comments, Comments....
  • Making the software behave in a predictable manner.
  • Code review and test the code in retail and debug builds.

I often see programmers make common mistakes like:

  • Never checking return values after calling a function
  • Hard coding HRESULTS to something other than the true error code was
  • Using try/catch(...) statements (I will clarify this one).
  • Not handling the error at all

I am about to show you the simple try/catch(...) statement. This is a powerful piece of code, however it is very deceptive because of what it hides - your exception if one occurs!! Don't get me wrong try catch statements are great, however catching everything with the catch(...) is very bad.

This is what a typical try/catch mistake:

Sample #1 (No try/catch statement) - This will cause an access violation when we try to write to the memory address of wszBuffer because it is uninitialized

#include "stdafx.h"
#include <stdio.h>
#include <strsafe.h>

#define BufferSize 64

int main(int argc, char *argv[])
{
     HRESULT hr = S_OK;
     wchar_t* wszBuffer = NULL;
     wchar_t* wsString = L"This is an access violation"
     hr = StringCchCopyW(wszBuffer, BufferSize, wsString);  <-- Raise exception here!!
     wprintf(L"Out string = %ws", wszBuffer);
     return 0;

}

When we run this code we get the following results: Unhandled exception at 0x00411ac3 in AccessViolation.exe: 0xC0000005: Access violation writing location 0x00000000.

Sample #2 (Using try/catch(...) statement) - This will catch the exception and allow the application continue and we will eventually crash later on.

#include "stdafx.h"
#include <windows.h>
#include <strsafe.h>

#define BufferSize 64

int main(int argc, char *argv[])
{
     HRESULT hr = S_OK;
     wchar_t* wszBuffer = NULL;
     wchar_t* wsString = L"This is an access violation"

     try
     {

          // Raise exception here and return the HRESULT for the failure
          hr = StringCchCopyW(wszBuffer, BufferSize, wsString);  

     }
     catch(...)
     {

        // 1. The StringCchcopyW failed now set the HRESULT to E_FAIL
        // 2. Output the wrong HRESULT and leave no indication to why we really failed.

          hr = E_FAIL;                     
          wprintf(L"Error hr = %08X", hr); 
     }

          wprintf(L"Out string = %ws", wszBuffer);
          return 0;
}

When we run this code we get the following results: Error hr = 80004005 - Out string = (null).

From a programmers view this would look ok because we caught and handled the problem, however we really raised an exception and never handled properly or reported it correctly. In addition to this being really bad here are a few more reasons:

  1. You could raise an exception a few functions deeper and never ever know which function caused the exception.
  2. You are throwing your exception away.

This can and will produce very unstable results for your application. One very important thing that was missed in the catch(...) statement was the exception that was raised. There are a few things that you need to keep in mind here when you are using exception handling.

  1. Exception handling is very expensive because the compiler has to setup and tear down every try/catch statement even if they are not used.
  2. Every time you throw an exception we are calling RaiseException which causes a trip to the kernel. For more information on RaiseException please see: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/raiseexception.asp

Why use expensive calls and throw away the most important information that can help you fix your application? Also what was so important and what did we miss when we the exception was raised?

Well, when you program runs a frame (known as a stack frame) is created and pushed on to the stack. Each frame can have multiple exception handlers (try/catch statements) which guard certain portions of your code like in the above example. At the time of an exception the kernel has to do a few things:

  1. Invoke the Trap Handler. The Trap Handler will create a trap frame and an Exception Record. This Exception Record is a pointer to the EXCEPTION_POINTERS structure and contains the machine-independent description of the exception. For more information on EXCEPTION_RECORDS please see this article: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/exception_record_str.asp
  2. Invoke the Exception Dispatcher. The Exception Dispatchers only job is to look for a handler to take care of the exception.
    1. The Exception Dispatchers first attempt will be to send a notification event to the debugger port to see if a debugger is attached. (This is where the First Chance exception comes from).
    2. If there is no debugger like WinDBG, CDB or Adplus attached to the process that caused the exception, or if the debugger doesn't handle that exception the exception dispatcher has to perform an extra task. Being that all processing happens behind the scenes in the kernel we don't have access the exception dispatcher helps us out by copying the trapped frame back over to the user mode process space. When this data is copied back over gets formatted in the form of a CONTEXT_RECORD. A CONTEXT_RECORD is pointer to the CONTEXT structure that contains the processor-specific description of the state of the processor at the time of the exception. Additionally the CONTEXT_RECORD sits at offset 0x004 inside the _EXCEPTION_POINTERS structure.
  3. The Exception Dispatcher keeps looking for an exception handler and if one is not found it will keep unwinding all of the frames until it either finds one. If no handlers are found then the Exception Dispatcher will hand the exception back over to the kernel and you will end up with(a Second Chance Exception. At this point the application will be terminated.

This data is essential to fixing the bugs in your applications. As a alternative to the try/catch(...) is the at try/except statement. This statement will allow you to write your own exception filter that can display the exception and context records if you choose too. I am sharing my sample code out for those of you that are interesting in learning more about exception handling and what happens behind the scenes. This exception filter will call GetExceptionInformation which returns a pointer to the EXCEPTION_RECORDS structure and then searches for the exception handler. I did not write the portion for walking the call stacks and displaying the symbols. This exception filter is for X86 only, I didn't add the registers for the 64 bit processor.

For your code you would do the following:

#include "stdafx.h"
#include <windows.h>
#include <strsafe.h>

#define BufferSize 64

// Needed for X86 systems

#ifdef _X86_

LONG SampleExceptionFilter( EXCEPTION_POINTERS *pExceptionPointer )

{

            DWORD dwExCode = pExceptionPointer->ExceptionRecord->ExceptionCode;

            bool fAvTrue = false;

            static wchar_t wszRegisterBuffer[1024] = { 0 };

            static wchar_t wszErrorBuffer[256] = { 0 };

            static wchar_t wszAvBuffer[256] = { 0 };

 

// Switch on error code and then print it out at the end before we exit

switch(dwExCode)

{

case EXCEPTION_ACCESS_VIOLATION :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Access Violation: %08X\n", dwExCode);

StringCchPrintfW(wszAvBuffer, 256, L"\nBit Flag: %08X - Memory Address Accessed: %08X : %08X\n",

pExceptionPointer->ExceptionRecord->ExceptionInformation[0],

pExceptionPointer->ExceptionRecord->ExceptionInformation[1]

&pExceptionPointer->ExceptionRecord->ExceptionInformation[1]);

fAvTrue = true;

break;

 

case EXCEPTION_DATATYPE_MISALIGNMENT :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - DataType Misalignment: %08X\n", dwExCode);

break;

 

case EXCEPTION_BREAKPOINT :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Breakpoint: %08X\n", dwExCode);

break;

 

case EXCEPTION_SINGLE_STEP :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Single Step: %08X\n", dwExCode);

break;

 

case EXCEPTION_ARRAY_BOUNDS_EXCEEDED :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Array Bounds Exceeded: %08X\n", dwExCode);

break;

 

case EXCEPTION_FLT_DENORMAL_OPERAND :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Float Normal Operand: %08X\n", dwExCode);

break;

 

case EXCEPTION_FLT_DIVIDE_BY_ZERO :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Float Divide By Zero: %08X\n", dwExCode);

break;

 

case EXCEPTION_FLT_INEXACT_RESULT :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Float InExact Result: %08X\n", dwExCode);

break;

 

case EXCEPTION_FLT_INVALID_OPERATION :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Float Invalid Operation: %08X\n", dwExCode);

break;

 

case EXCEPTION_FLT_OVERFLOW :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Float Overflow: %08X\n", dwExCode);

break;

 

case EXCEPTION_FLT_STACK_CHECK :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Float Stack Check: %08X\n", dwExCode);

break;

 

case EXCEPTION_FLT_UNDERFLOW :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Float Underflow: %08X\n", dwExCode);

break;

 

case EXCEPTION_INT_DIVIDE_BY_ZERO :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Integer Divide By Zero: %08X\n", dwExCode);

break;

 

case EXCEPTION_INT_OVERFLOW :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Integer Overflow: %08X\n", dwExCode);

break;

 

case EXCEPTION_PRIV_INSTRUCTION :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Private Instruction: %08X\n", dwExCode);

break;

 

case EXCEPTION_IN_PAGE_ERROR :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - In Page Error: %08X\n", dwExCode);

break;

 

case EXCEPTION_ILLEGAL_INSTRUCTION:

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Illegal Instruction: %08X\n", dwExCode);

break;

 

case EXCEPTION_NONCONTINUABLE_EXCEPTION :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Non-Continuable: %08X\n", dwExCode);

break;

 

case EXCEPTION_STACK_OVERFLOW :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Stack Overflow: %08X\n", dwExCode);

break;

 

case EXCEPTION_INVALID_DISPOSITION :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Invalid Disposition: %08X\n", dwExCode);

break;

 

case EXCEPTION_GUARD_PAGE :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Guardpage: %08X\n", dwExCode);

break;

 

case EXCEPTION_INVALID_HANDLE :

StringCchPrintfW(wszErrorBuffer, 256, L"\nException - Invalid Handle: %08X\n", dwExCode);

break;

 

case 0xE06D7363 :

StringCchPrintfW(wszErrorBuffer, 256, L"\nC++ Exception: %08X\n", dwExCode);

break;

 

default :

wprintf(L"\nDefault case statement. Program will continue to execute\n");

return EXCEPTION_CONTINUE_EXECUTION;

break;

}

 

// Print the context record data

wprintf(wszErrorBuffer);

StringCchPrintfW(wszRegisterBuffer, 1024, _T(L"EAX=%08X EBX=%08X ECX=%08X EDX=%08X ESI=%08X\n")\

                         _T(L"EDI=%08X EBP=%08X ESP=%08X EIP=%08X FLG=%08X\n")\

                         _T(L"CS=%04X DS=%04X SS=%04X ES=%04X FS=%04X GS=%04X"),

                         pExceptionPointer->ContextRecord->Eax,

                         pExceptionPointer->ContextRecord->Ebx,

                         pExceptionPointer->ContextRecord->Ecx,

                         pExceptionPointer->ContextRecord->Edx,

                         pExceptionPointer->ContextRecord->Esi,

                         pExceptionPointer->ContextRecord->Edi,

                         pExceptionPointer->ContextRecord->Ebp,

                         pExceptionPointer->ContextRecord->Esp,

                         pExceptionPointer->ContextRecord->Eip,

                         pExceptionPointer->ContextRecord->EFlags,

                         pExceptionPointer->ContextRecord->SegCs,

                         pExceptionPointer->ContextRecord->SegCs,

                         pExceptionPointer->ContextRecord->SegDs,

                         pExceptionPointer->ContextRecord->SegSs,

                         pExceptionPointer->ContextRecord->SegEs,

                         pExceptionPointer->ContextRecord->SegFs,

                         pExceptionPointer->ContextRecord->SegGs,

                         pExceptionPointer->ExceptionRecord->ExceptionInformation[0],

                         pExceptionPointer->ExceptionRecord->ExceptionInformation[1]);

 

// Print register buffers

wprintf(wszRegisterBuffer);

 

// Check to see if this was an access violation

if (fAvTrue)

{

wprintf(wszAvBuffer);

wprintf(L"\nNOTE: If the bit flag value is zero, this thread attempted to read the inaccessible data.\n");

wprintf(L"If the bit flag value is 1, this thread attempted to write to an inaccessible address.\n");

wprintf(L"The second array element specifies the virtual address of the inaccessible data.\n\n");

}

 

// Check the exception code and handle it

if (dwExCode == EXCEPTION_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH);

wprintf(L"\nException handler executed!\nProgram will terminate!!\n");

 

}

#endif

 

// Main entry point

int main(int argc, char *argv[])
{
     HRESULT hr = S_OK;
     wchar_t* wszBuffer = NULL;
     wchar_t* wsString = L"This is an access violation";

     __try
     {

          // Raise an exception in this code block
          hr = StringCchCopyW(wszBuffer, BufferSize, wsString);       
     }
     __except(SampleExceptionFilter(GetExceptionInformation()))    <- Exception will be caught and handled here!!

    {
         // do some sort of handling if you are allowing the application to continue for debug purposes

    } 

          wprintf(L"Out string = %ws", wszBuffer);
          return 0;
}

When we run this code we get the following results:

Exception - Access Violation: C0000005
EAX=CDCDCDCD EBX=01312720 ECX=01312720 EDX=01312720 ESI=00000000
EDI=7C809E01 EBP=0012FC30 ESP=0012FC0C EIP=38F373E0 FLG=00010246
CS=001B DS=001B SS=0023 ES=0023 FS=0023 GS=003B
Bit Flag: 00000001 - Memory Address Accessed: CDCDCDCD : 00000000

NOTE: If the bit flag value is zero, this thread attempted to read the inaccessible data.

If the bit flag value is 1, this thread attempted to write to an inaccessible address.

The second array element specifies the virtual address of the inaccessible data.

Exception handler executed!
Program will terminate!!

A more practical and real world approach to this for real world scenareos would be to run ADPlus -crash -pn <pid of your application>. By using ADPlus to capture all this information you will capture all first / second chance exceptions, and all of the call stacks associated.

For more information on AdPlus please see: How to use ADPlus to troubleshoot "hangs" and "crashes"

For more information on exception handling filters please see: http://msdn2.microsoft.com/en-us/library/s58ftw19.aspx

Dave

Comments