commial/ttd-bindings

Using an invalid trace file path crashes with an access violation

kweatherman opened this issue · 1 comments

If you try to load an invalid trace file path, or anything that triggers an internal error message it will result in an access violation crash.
This is because of a missing "TTD::ErrorReporting" class with callbacks we need to add.

To fix this we need to first define the class.
It's actually pretty simple to locate if you have symbols.
The totally virtual base class is "TTD::ErrorReporting" ??_7ErrorReporting@TTD@@6B@
The actual derived instance is "TtdTargetInfo::DbgErrorReporting" ??_7DbgErrorReporting@TtdTargetInfo@@6B@

Recreated class:

namespace TTD
{
	// Error output callbacks for internal TTD errors
	class ErrorReporting
	{			
	public:
		virtual ~ErrorReporting() {}
		virtual void PrintError(const std::string &error) {}
		virtual void VPrintError(LPCSTR format, ...) {}	
	};
	static_assert(sizeof(ErrorReporting) == sizeof(ULONG_PTR), "Should be equal the size of a vftable");
}	

By declaring all the members virtual in their original order, we will end up with virtual class with a single vftable (no need for wrappers or other tricks). Thus the size of the class will be sizeof(ULONG_PTR).

Then down in the "ReplayEngine" class we can now define "RegisterDebugModeAndLogging":

namespace TTD
{
	namespace Replay
	{
		enum DebugModeType
		{
			DefaultMode = 0,
		};

		class ReplayEngine
		{
		public:
		    ...
		    // [46]: public: virtual void TTD::Replay::ReplayEngine::RegisterDebugModeAndLogging(enum TTD::Replay::DebugModeType,class TTD::ErrorReporting *)
		    virtual void RegisterDebugModeAndLogging(TTD::Replay::DebugModeType, class TTD::ErrorReporting*);
		    ...
		};
	}
}

Don't know if there is any more "DebugModeType", in code seen is only a '0' type which I name "DefaultMode".

Now after creating a class instance, somewhere around here ReplayEngine::ReplayEngine() we can add this handler before returning.

// Set internal ErrorReporting callbacks to avoid crash when trace file path is invalid, etc.
this->engine->IReplayEngine->RegisterDebugModeAndLogging(TTD::Replay::DefaultMode, new TTD::ErrorReporting());

Now after a failed ->Initialize(wchar_t const*) call it will not crash, and we can do a HRESULT_FROM_WIN32(GetLastError()) and we'll will get: a 0x80070002 "The system cannot find the file specified.".

Now if you want to see the debug output to a console version you can instead of just blanks one can do:

class ErrorReporting
{			
public:
	virtual ~ErrorReporting() {}
	virtual void PrintError(const std::string &error)
	{
		puts(error.c_str());
	}
	virtual void VPrintError(__in LPCSTR format, ...)
	{
		va_list args;
		va_start(args, format);
		vprintf_s(format, args);
		va_end(args);
	}	
};

One caveat though it will crash in the "PrintError" function while debugging because the class fields (the data members) in "std::string" are different between the debug and the release versions. Of course the binary code we have is always release.

Now.. to get around this one can do a simple recreation that is always the release size:

struct std_string
{
	union
	{
		LPSTR ptr;		// 00
		char buffer[16];	// 08
	};
	size_t size;			// 10
	size_t allocated;		// 18

	__forceinline const char* c_str()
	{
		if (allocated > 10)
			return ptr;
		else
			return buffer;
	}
};

Just replace the "const std::string &error" with "std_string &error".

Interesting, but didn't find the internal debug output particularly useful.