PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Memory Read Access Violation verhindern. Performante Lösung?


Demirug
2005-11-12, 17:42:13
Bevor ich jetzt wieder anfange mehrere Lösungen auszuprobieren und zu Benchen frage ich doch erst mal nach.

Problembeschreibung:
Über ein COM Interface wird ein anderes COM Interface übergeben. Wird nun ein dabei ein ungültiger Zeiger übergeben kommt es zu einer Read Access Violation. Der damit verbundene Absturz muss verhindert werden

Der reale Fall:
Bei einer DirectX 6.1 Emulation werden alle relevanten Interfaces implementiert. Einiges dieser Interface Methode bekommen selbst wieder ein anderes DirectX Objekt als Zeiger auf ein entsprechendes Interface übergeben. Eines dieser Methode ist Blt aus dem IDirectDrawSurface4. Intern wird der Zeiger auf das Interface wieder in einen Zeiger auf das echte Objekt gewandelt. Dazu wird ein internes erweitertes IDirectDrawSurface4 Interface benutzt. Das ganze funktioniert auch ohne Probleme solange die übergebenen Interfacezeiger auch wirklich von einem solchen Objekt kommen. Leider gibt es nun mindestens ein Spiel von „Jane's Combat Simulations“ das an dieser Stelle plötzlich ohne erkennbaren Grund einen wilden Zeiger übergibt. DirectX 6.1 erkennt allerdings solche wilden Zeiger fängt sie ab und beendet die Methode mit einem Fehlercode. Das entsprechenden Spiel bleibt spielbar und es sind keine Beeinträchtigungen oder Fehldarstellungen zu sehen. Daraus ergibt sich nun das die Emulation sich entsprechend verhalten muss.

Aus diesem Grund habe ich die ursprüngliche Methode

DirectDrawSurface* GetDDSurface (D3D7::IDirectDrawSurface4* pSurface)
{
if (pSurface == NULL)
return NULL;

IDirectDrawSurface4Intern* pIntern = (IDirectDrawSurface4Intern*)pSurface;
return pIntern->GetOriginalObject ();
}

durch diese ersetzt

DirectDrawSurface* GetDDSurface (D3D7::IDirectDrawSurface4* pSurface , bool& rOK)
{
if (pSurface == NULL)
{
rOK = true;
return NULL;
}

try
{
IDirectDrawSurface4Intern* pIntern = (IDirectDrawSurface4Intern*)pSurface;
rOK = true;
return pIntern->GetOriginalObject ();
}
catch (...)
{
rOK = false;
return NULL;
}
}

Das Problem der Read Access Violation ist damit unterbunden und das Spiel läuft an dieser Stelle wie mit dem Original DirectX 6.1

Da diese Konvertierungsfunktion jedoch recht häufig ausgeführt werden muss bin ich an einer Lösung interessiert die performanter ist.

zeckensack
2005-11-12, 19:30:02
Kannst du nicht nachsehen, ob du den Zeiger kennst? Die Erzeugung und Zerstörung der ...Intern-Instanzen läuft doch auch in von dir kontrolliertem Code ab, wenn ich nicht irre.

//global
std::set<IDirectDrawSurface4Intern*> known_surfs;
bool check_surfs=false;

//Erzeugen
IDirectDrawSurface4Intern* rv=...
#ifdef _DEBUG
std::pair <std::set<IDirectDrawSurface4Intern*>::iterator,bool> ins_res=known_surfs.insert(rv);
assert(ins_res.second==true); //Doppelt erzeugte Zeiger sind Fehler!
#else
known_surfs.insert(rv);
#endif

//Löschen
...
known_surfs.erase(pSurface);

//deins
DirectDrawSurface* GetDDSurface (D3D7::IDirectDrawSurface4* pSurface , bool& rOK)
{
if (pSurface == NULL)
{
rOK = true;
return NULL;
}
IDirectDrawSurface4Intern* pIntern = (IDirectDrawSurface4Intern*)pSurface;
//NEU!
if (check_surfs)
{
if (known_surfs.find(pIntern)!=known_surfs.end())
{
rOK=true;
return pIntern->GetOriginalObject ();
}
else
{
rOK=false;
return NULL;
}
}
else
{
try
{
rOK = true;
return pIntern->GetOriginalObject ();
}
catch (...)
{
rOK = false;
return NULL;
check_surfs=true; //NEU! Auf Test ohne Exceptions umschalten.
}
}
}Ich habe mich dafür entschieden das Exception-Handling drinzulassen. Allerdings wird bei der ersten gefeuerten Exception auf einen (hoffentlich) effizienteren Test umgeschaltet.

Der Overhead beläuft sich auf das Aktuell-Halten des neu hinzugekommenen sets. Dieser fällt immer an, egal ob die Applikation böse ist oder nicht. std::set skaliert aber relativ gut (O(log(n))).

HTH.

Demirug
2005-11-12, 19:53:26
Ja, die Objekte erzeuge und zerstöre alle ich. DirectX ist zumindestens in dieser Beziehung COM konform.

Ich kann dir allerdings nicht so ganz folgen. Im Normalfall wird sich ein Spiel ja ordentlich verhalten und mir niemals einen ungültigen Zeiger geben. Das heist ich komme dann zwar immer in den TRY aber nie in den CATCH. Und selbst das besagte Spiel gibt mir ja bis auf ganz wenige Ausnahmen immer einen gültigen Zeiger. Ich kann mir nun ehrlich gesagt nicht vorstellen das std:set mit einigen hundert Objekten (IIRC sind es bei dem Spiel so 1800 rum) weniger Takte braucht als der Overhead durch einen TRY.

zeckensack
2005-11-12, 20:53:23
Ja, die Objekte erzeuge und zerstöre alle ich. DirectX ist zumindestens in dieser Beziehung COM konform.

Ich kann dir allerdings nicht so ganz folgen. Im Normalfall wird sich ein Spiel ja ordentlich verhalten und mir niemals einen ungültigen Zeiger geben. Das heist ich komme dann zwar immer in den TRY aber nie in den CATCH. Und selbst das besagte Spiel gibt mir ja bis auf ganz wenige Ausnahmen immer einen gültigen Zeiger. Ich kann mir nun ehrlich gesagt nicht vorstellen das std:set mit einigen hundert Objekten (IIRC sind es bei dem Spiel so 1800 rum) weniger Takte braucht als der Overhead durch einen TRY.Glaube ich auch nicht.
Ich hatte es so verstanden, dass du viele ungültige Zeiger zu sehen bekommst. Es stimmt natürlich dass try/catch nicht teuer ist. Was teuer ist ist die gefeuerte Exception selbst (einmal Kernel und zurück).

Wenn das pro Minute ein-, zweimal passiert, dann lass es einfach so wie es ist. Ausnahmebehandlung ist genau für sowas gemacht worden.


PS: GetOriginalObject() löst die Exception aus, oder? Das ist doch auch dein Code :|

Demirug
2005-11-12, 22:01:35
Das sind die restlichen relevanten Codestellen:

Das Interface:

class IDirectDrawSurface4Intern : public D3D7::IDirectDrawSurface4
{
public:
virtual DirectDrawSurface* GetOriginalObject () = 0;
};

und die Implementierung:

DirectDrawSurface* DirectDrawSurface::GetOriginalObject ()
{
return this;
}

Ich habe das ganze so implementiert weil "DirectDrawSurface" zum einen noch weitere DirectX Interface Implementieren muss (IDirectDrawSurface7, IDirect3DTexture2, ...) und es zum anderen ein paar spezialisierte Ableitungen gibt.

Die Variante über RTTI erschien mir irgendwie nicht wirklich performant und auf einen festen Aufbau der Objekte im Speicher wollte ich nicht vertrauen.

Die Exception wird beim Aufruf der virtuellen Methode ausgelösst weil. Entweder schon beim Versuch die VTable auszulesen oder wenn an die dort gefundene Adresse gesprungen werden soll. Ich werde wohl noch in die GetOriginalObject Methode einen Keycheck einbauen. Für den Fall das der wilde Zeiger auf eine gültige Speicheradresse zeigt und die dort gefundene Addresse wirklich auf ausfürbaren Code zeigt.

Also bei dem Spiel habe ich bisher nur zwei Stellen gefunden bei denen es passiert. Wenn man in einer bestimmten Reihenfolge durch die Menüs wechselt passiert es zwei mal und jedesmal am Ende nachdem man einen bestimmten Missiontype gespielt hat. Es ist also noch nicht mal im Minuten Takt. Ich muss mal testen ob die neuen (8 und 9) DirectX Interfaces das auch noch so machen.

Trotzdem bin ich mal wieder schockiert was für Fehler sich so alles in alter Software verstecken können und gleichzeitig wundere ich mich das Microsoft das DX 6.1 Interface gegen sowas gesichert hat.

zeckensack
2005-11-12, 22:16:09
OT: Libs sind das schlimmste.
Ich weiß noch wie ich aus der Wäsche geschaut habe, als ich sah dass MDK reproduzierbar beim zweiten Mal Speichern abstürzt. Das Teil hat zwecks Screenshot den Backbuffer gelockt ... und nie wieder freigegeben ... und nie den Rückgabewert überprüft ... und sowieso noch tausend andere Regeln verletzt.

Demirug
2005-11-12, 22:22:39
OT: Libs sind das schlimmste.
Ich weiß noch wie ich aus der Wäsche geschaut habe, als ich sah dass MDK reproduzierbar beim zweiten Mal Speichern abstürzt. Das Teil hat zwecks Screenshot den Backbuffer gelockt ... und nie wieder freigegeben ... und nie den Rückgabewert überprüft ... und sowieso noch tausend andere Regeln verletzt.

Ja, das kenne ich ich glaube ich könnte einen Blog zu dem Thema schreiben. Am schlimmsten sind Sachen die von EA programmiert wurden und die Unreal Engine ist auch so eine Katastrophe wobei man bei der wenigsten merkt das sie die Fehler rausmachen.