HOME
PROJECTS
LOOK AT MORE TUTORIALS
Laying The Groundwork For An Emulator - Error Handling
Author: zenogais Difficulty Level: Beginner


    When beginning work on any emulator there are often details left forgotten except at the critical junctures when they are needed. More often than not these items are basic, and not only that but they are details which are required by any decent emulator. These are of course facilities for error handling and facilities for logging. In this article I'll describe a generic object-oriented approach to error handling. This approach involves the use of C++ exceptions as well as C++ file IO, so if these components are not familiar to you then I suggest you learn C++ a bit more before attempting any of this.

    Firstly, exceptions are objects to be thrown only when the condition which triggers this is truly exceptional. This is a fairly unclear statement, but one which you'll find in many books. Basically what I have taken this to mean is that exceptions should not be used to pass information around a system, but instead their purpose is solely handling errors. In the following sections I'll describe a generic exception class which will report enough information about an error that it may be tracked down easily by anyone who uses it, but first let's look at some code:

 

class EFException
{
public:
	///////////////////////////////////////////////////////////////////////////
	// Enumerations

	/// An enumeration with individual exception categorizations.
	enum ExceptionCode
	{
		EC_FATAL_ERROR,
		EC_GENERAL_ERROR,
		EC_INVALID_PARAMETERS,
		EC_ILLEGAL_ADDRESS,
		EC_ILLEGAL_OPCODE,
		EC_ILLEGAL_PACKET
	};
	
	///////////////////////////////////////////////////////////////////////////
	// Constructors
	
	/** The basic constructor.
	  */
	EFException(const ExceptionCode& code, const std::string& message, const std::string& file, 
				const std::string& function, long line);
	/** The assignment operator.
	  */
	EFException operator = (EFException& right);

	///////////////////////////////////////////////////////////////////////////
	// Exception Information Functions

	/** Returns the exception code for this message.
	  */
	ExceptionCode GetExceptionCode() const throw();
	/** Returns detailed information about the error.
	  */
	std::string GetDetailedInformation() throw();
protected:
	///////////////////////////////////////////////////////////////////////////
	// Exception Code Function

	/** Returns the name of the exception code for this exception.
	  */
	std::string GetExceptionCodeStr() throw();

	///////////////////////////////////////////////////////////////////////////
	// Variables

	long          mLine;
	ExceptionCode mCode;
	std::string   mFile;
	std::string   mMessage;
	std::string   mFunction;
};

    As you can see this class will be our exception class. This is the class which will be thrown every time an exception occurs. This exception describes every aspect of the area where the exception occurred, most notably the name of the file, the function in that file, the line in that file, and also a message describing the error that occurred. This is very useful for getting important, specific information about the error that occurred, as this information is invaluable when you are writing an emulator and tracking down bugs. In addition to all that information you may notice that I have also included an error code. The error codes create a simple way in which a fatal error can be distinguished from a non-fatal error at any point in your program. In addition to this ability to distinguish differences, they also help to categorize an error into a group, making it easier to asses the error when it is thrown.
    Now lets explain some of the more advanced C++ concepts presented here for those who don't know about them. Firstly the word "const" following a function is used to show the fact that within the body of this function no values are modified, this means that the function is only returning a value - nothing more and nothing less. Secondly the word "throw()" following a function is used to create a contract between the function caller and the function itself meaning that this function will not throw any object of any kind any time that it is called. This is very important, especially in an exception class. Also you may have noticed that we specified an "operator =", this is because the compiler (Microsoft Visual C++ .NET) was unable to generate this function at compilation time. Anyways now lets move on to what the functions themselves are supposed to do.
    The purpose of the constructor should be obvious given its parameter names and the names of our protected variables, as should the purpose of the assignment operator, so I will not delve into these as these are necessary knowledge at this point. However let'
s discuss the function "GetExceptionCode". This function's purpose is to retrieve the error code that categorizes this exception. Inside the body of this function nothing will be modified or thrown, and in fact all the internals of this function do is return the value "mCode" which is a protected member variable. Moving onto the next function, we see "GetDetailedInformation". This function's purpose is to return an std::string containing detailed information about the exception. This function in fact returns a message formatted as follows:


---------------------------------------------
Exception Details:
---------------------------------------------
Code    : EC_ILLEGAL_OPCDE
Line    : 387
Function: NeoPSX::PSXInterpreter::ExecuteNext
File    : PSXInterpreter.cpp
Message : Current CPU opcode is invalid or unknown.
						


    As you can see this formatting makes the error very easy to recognize and even possibly solve, if this is not in fact just an invalid CPU opcode but possibly an unimplemented one. This is one of the main reasons why I believe exceptions can be very beneficial when working on an emulator. Also by giving you the exact line, function, and file at which this error occurred it is much easier to track down and eventually fix. This function also returns the exception code for the the exception that was thrown, this is done through a call to the protected function "GetExceptionCodeStr". This function translates the numbered exception code from the enumeration into an std::string version of the exception code. Anyways now that we're all familiar with what this function does let's examine the implementation of all these functions:


EFException::EFException(const ExceptionCode& code, const std::string& message, 
                         const std::string& file, const std::string& function, long line)
: mCode( code ),
  mLine( line ),
  mFile( file ),
  mMessage( message ),
  mFunction( function )
{ }

EFException EFException::operator = (EFException& right)
{
	mCode     = right.mCode;
	mLine     = right.mLine;
	mFile     = right.mFile;
	mMessage  = right.mMessage;
	mFunction = right.mFunction;

	return *this;
}

////////////////////////////////////////////////////////////////////////////////////////////////

EFException::ExceptionCode EFException::GetExceptionCode() const throw()
{
	return mCode;
}

std::string EFException::GetDetailedInformation() throw()
{
	// Use a static buffer to avoid unncessary allocations on every call.
	static char sBuffer[20];

	// This is the header of our detailed exception description.
	std::string mDetailed = "Exception Has Been Caught!\r\n";
	mDetailed += std::string("---------------------------------------\r\n
	                          Exception Details:\r\n
	                          ---------------------------------------\r\n");

	// Name the exception code which triggered this.
	mDetailed += "Code    : " + GetExceptionCodeStr() + "\r\n";
	
	// Use snprintf because its safer, and add the line number.
	_snprintf(sBuffer, 20, "%d", mLine);
	mDetailed += "Line    : " + std::string( sBuffer )  + "\r\n";

	// Print the name of the function from which the exception was thrown.
	mDetailed += "Function: " + mFunction + "\r\n";

	// Print the name of the file in which the offending code is found.
	mDetailed += "File    : " + mFile + "\r\n";

	// Print our detailed error message.
	mDetailed += "Message : " + mMessage;

	return mDetailed;
}

////////////////////////////////////////////////////////////////////////////////////////////////

std::string EFException::GetExceptionCodeStr() throw()
{
	switch( mCode )
	{
	case EC_FATAL_ERROR: return "EC_FATAL_ERROR";
	case EC_GENERAL_ERROR: return "EC_GENERAL_ERROR";
	case EC_INVALID_PARAMETERS: return "EC_INVALID_PARAMETERS";
	default: return "UNKNOWN CODE";
	}
}
						

    And there you have it, a generic exception which gives enough information to be used as a replacement for assert. While I did not mention this as one of the main goals, it should be apparent now that not only can this exception give us more information than assert, but it is a more object oriented approach to the problem. Hopefully this article has helped many of you budding emulator developers, and hopefully you can employ it to better facilitate error-handling in your own emulators. In the next article I'll deepen our discussion of error handling when I explain how to write a flexible logging framework which you can reuse in all your emulators both past, present, and future.