PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [C] Seltsamkeit im RtCW-Code ...


Ganon
2010-08-22, 16:42:04
Hiho.

Ich und ein Kumpel basteln gerade daran, die freigegebene RtCW-Engine nach OS X x86 zu portieren. Viel OS X Code ist ja schon enthalten und musste nur aktualisiert werden. Das Spiel startet auch schon und man kann auch schon spielen. Aber leider stürzt das Spiel recht schnell wieder ab, mit einem BAD_ACCESS.

Wir haben das jetzt mal eingegrenzt und ich bin dabei auf etwas ganz seltsames gestoßen und ich weiß ehrlich gesagt nicht, wie ich an die Problemsuche rangehen soll.

Hier mal der Ausschnitt aus der tr_animation.c


...
if ( parentBone != NULL ) {

if ( fullTorso ) {
sh = (short *)cTBonePtr->ofsAngles;
sh2 = (short *)cOldTBonePtr->ofsAngles;
} else {
sh = (short *)cBonePtr->ofsAngles;
sh2 = (short *)cOldBonePtr->ofsAngles;
}

pf = angles;
*( pf++ ) = SHORT2ANGLE( *( sh++ ) );
*( pf++ ) = SHORT2ANGLE( *( sh++ ) );
*( pf++ ) = 0;
LocalAngleVector( angles, v2 ); // new

pf = angles;
*( pf++ ) = SHORT2ANGLE( *( sh2++ ) );
*( pf++ ) = SHORT2ANGLE( *( sh2++ ) );
*( pf++ ) = 0;
LocalAngleVector( angles, vec ); // old

// blend the angles together
if ( fullTorso ) {
SLerp_Normal( vec, v2, torsoFrontlerp, dir );
} else {
SLerp_Normal( vec, v2, frontlerp, dir );
}

// translation
if ( !fullTorso && isTorso ) { // partial legs/torso, need to lerp according to torsoWeight

// calc the torso frame
sh = (short *)cTBonePtr->ofsAngles;
sh2 = (short *)cOldTBonePtr->ofsAngles;

pf = angles;
*( pf++ ) = SHORT2ANGLE( *( sh++ ) );
*( pf++ ) = SHORT2ANGLE( *( sh++ ) );
*( pf++ ) = 0;
LocalAngleVector( angles, v2 ); // new

pf = angles;
*( pf++ ) = SHORT2ANGLE( *( sh2++ ) );
*( pf++ ) = SHORT2ANGLE( *( sh2++ ) );
*( pf++ ) = 0;
LocalAngleVector( angles, vec ); // old

// blend the angles together
SLerp_Normal( vec, v2, torsoFrontlerp, v2 );

// blend the torso/legs together
SLerp_Normal( dir, v2, thisBoneInfo->torsoWeight, dir );

}

if(parentBone == NULL) {
Com_Printf("GNAAAAA");
}

LocalVectorMA( parentBone->translation, thisBoneInfo->parentDist, dir, bonePtr->translation );

} else { // just interpolate the frame positions
bonePtr->translation[0] = frontlerp * frame->parentOffset[0] + backlerp * oldFrame->parentOffset[0];
bonePtr->translation[1] = frontlerp * frame->parentOffset[1] + backlerp * oldFrame->parentOffset[1];
bonePtr->translation[2] = frontlerp * frame->parentOffset[2] + backlerp * oldFrame->parentOffset[2];
}
...


Jetzt wird sehr oft "GNAAAA" ausgegeben und irgendwann stürzt er in LocalVectorMA ab.

D.h. mitten im Ablauf scheint der Pointer einfach mal sein "Ziel" zu verlieren. Ich hab jetzt von C nicht so die megamäßige Ahnung, aber hat jemand eine Idee, wonach ich noch suchen könnte dabei?

RMC
2010-08-22, 16:49:06
Erstmal würd ich mir anschauen, welche Sichtbarkeit dieser Pointer hat und welche Klassen und Funktionen Zugriff drauf haben und diesen Pointer verändern. Es kann nämlich sein, dass der Pointer zwischen den von dir markierten Stellen manipuliert wird.

EDIT: Um rauszufinden wo das passiert, bieten diverse Entwicklungsumgebungen auch verschiedene Debuggingmöglichkeiten, zB kann man eben Adresse/Inhalt auf Änderung überwachen lassen.

Berni
2010-08-22, 17:39:55
Evtl. gibts Probleme mit Nebenläufigkeit/Threads oder einen kleinen BufferOverflow der "versehentlich" den Pointer überschreibt.

Tiamat
2010-08-22, 17:57:18
Also was mir auffällt..
ParentBone ist eine Struktur und es wird im Fall parentBone == NULL dann einfach auf eine Zeigervariable zugegriffen, also parentBone->translation. Deswegen auch BAD_ACCESS, da parentBone NULL ist.

Gruß
Tiamat

Ganon
2010-08-22, 18:12:15
Also parentBone ist eine globale statische Variable.... ganz toll.... naja, mal schauen. Ich schaue dann noch mal durch. parentBone wird aber nur in der einen Datei "angefasst". Jetzt mal schauen was von außerhalb aufgerufen wird. Vllt. kann man das irgendwie "lock"-en.

@Tiamat

Ja, dass der Absturz durch diesen NULL-Pointer kommt, weiß ich.

Tiamat
2010-08-22, 18:54:05
Ok,
kannst ja mal versuchen, was geschieht, wenn du einfach den letzten Methodenaufruf in einen != NULL Zweig packst.

if(parentBone == NULL) {
Com_Printf("GNAAAAA");
} else {
LocalVectorMA( parentBone->translation, thisBoneInfo->parentDist, dir, bonePtr->translation );
}

Ganon
2010-08-22, 19:12:33
Naja, das ist nicht Sinn der Sache, aber wenn ich es mache, flackern die Modelle wie verrückt. Er stürzt dann woanders ab, wo parentBone benutzt wird...

Scheint also wirklich daran zu liegen, dass irgend ein anderer Ablauf/Aufruf parentBone einfach ändert...

Gnafoo
2010-08-23, 00:18:31
Hm mit einem guten Debugger kannst du evtl. beim "if ( fullTorso )" anhalten und ab der Stelle die Adresse von parentBone überwachen. Dann laufen lassen, bis parentBone überschrieben wird und nachsehen, wer verantwortlich ist (RMC hat das ja im Prinzip auch schon vorgeschlagen). Alternativ kann man auch einfach durch den Code steppen (dabei über Funktionen springen) und nach jeder Zeile den Wert von parentBone prüfen. Wenn parentBone in einer Funktion überschrieben wird, sucht man dort eben weiter.

Ich hätte jetzt vermutet, dass irgendwo ein Überlauf stattfindet (z. B. dass "angles" zu klein ist und "*( pf++ ) = 0" parentBone überschreibt). Aber mit den Informationen kann man nur spekulieren. Von daher: wenn möglich den Debugger anwerfen und nicht unnötig raten.

Ganon
2010-08-23, 09:22:56
Ich habe bisher nur GDB von GNU... weiß nicht wie gut der ist. In Xcode zeigt mir der gdb beim Crash zu parentBone total korrekte Werte. Gibt es da noch irgendwelche Optionen? Leider ist clang/LLVM mit deren Debugger noch nicht soweit. Zumindest clang 1.5 compiliert den Code zwar, aber der läuft trotzdem noch nicht, daher bleibt mir erst mal nur der GDB bis Xcode 4 raus kommt.

Hier, um mal zu verdeutlichen was ich meine. Das ist der Debugger-Auszug vom Crash. Also kein manueller Breakpoint:
Hier im ersten Bild sieht man, dass parentBone einen korrekten Wert hat, den gdb auch normal anzeigen kann. Im zweiten Bild sieht man, dass parentBone wohl NULL war, denn parentBone-translation hat die Adresse 0x24... daher der Crash. Beide Bilder sind vom gleichen Crash.


http://xenon.42degreesoffreedom.com/~matti/RtCW1.jpg
http://xenon.42degreesoffreedom.com/~matti/RtCW2.jpg


Gibt's irgendwelche erweiterten GDB Parameter oder sieht jemand etwas was ich noch nicht gesehen habe? ^^ Wie gesagt, mein Wissen rund um den ganzen Spaß eigne ich mir hiermit erst an :D

pest
2010-08-23, 10:54:04
die Trivial-Variante:

mach nach jedem Methodenaufruf ein
Com_Printf(Methodenname+Inhalt von parentBone);

Gnafoo
2010-08-23, 16:14:57
Hm was der Debugger da ausgibt kommt mir etwas spanisch vor. Was steht denn in %eax, wenn er abstürzt? Dasselbe, was er bei "org" als Adresse angibt?
Da wäre die Frage, wo die überhaupt herkommt.

Edit: sind da noch irgendwelche Optimierungen aktiv? Die sollte man beim Debuggen dann evtl. mal abschalten.

DR.ZEISSLER
2010-08-23, 19:59:13
osx nativ, prima sache, scheiss rosetta.

Ganon
2010-08-23, 21:32:27
Da wäre die Frage, wo die überhaupt herkommt.

0x0 (NULL) + ->translation = 0x24. Und ja, in eax steht 0x24.

Edit: sind da noch irgendwelche Optimierungen aktiv? Die sollte man beim Debuggen dann evtl. mal abschalten.

Nein, keinerlei Optimierungen sind aktiv.

Ich bau jetzt mal das ein was pest vorgeschlagen hat.

Ganon
2010-08-23, 22:19:01
Wenn ich das Com_Printf einbaue, dann bringt ihn das wohl irgendwie aus dem Tritt, da die Modelle dann nur noch wild am Rumflackern sind... abgestützt ist er auch nicht... wahrscheinlich weil die Threads sich nicht in die Quere kommen...

Ist doch alles scheiße... *grummel* Ich guck mal ob da irgendwas im Code ist, um die Threads zu locken...

pest
2010-08-23, 22:30:55
das ist auf jeden Fall ein BufferOverflow in einer Methode da die Rücksprungadresse nicht mehr zu stimmen scheint
du kannst nun testweise die COM_Print rausnehmen und schauen wann er wieder abstürzt

Gnafoo
2010-08-23, 23:38:25
0x0 (NULL) + ->translation = 0x24. Und ja, in eax steht 0x24.

Okay ich denke einfach mal laut:

Was mich irritiert ist die Tatsache, dass parentBone im ersten Screenshot gut aussieht. Adresse != 0, der Vektor hat sinnvolle Werte etc. Damit sollte auch parentBone->translation problemlos dereferenzierbar sein, C macht eine implizite Konvertierung auf einen Pointer und übergibt ihn der Funktion. Dort wird drauf zugegriffen und zwischendrin läuft kein anderer Code. Eigentlich sollte das problemlos gehen.

Wenn mich meine Assembler-Kenntnisse nicht im Stich lassen, kommt der Wert ursprünglich von der Instruktion 0x0006108f. LEA berechnet die Adresse 0x24 + eax und speichert sie in esi. Das landet dann über fünf Ecken als eax bei der movss-Instruktion. Dort wird die Adresse dereferenziert und das scheitert, weil 0x24 drin steht. Das heißt: in 0x0006108f muss vermutlich bereits eax=0 gewesen sein.

Die 0x24 sind wohl das Offset von translation in der Struktur (matrix ist ein 3x3-Array von float, also 9*4=36=0x24. translation kommt direkt dahinter). In eax sollte daher wohl eigentlich die Adresse von parentBone stehen, aber tatsächlich steht dort 0. Gleichzeitig bescheinigt der Debugger, dass parentBone=0x6270e0 ist. Wie dem auch sei, dass Problem scheint irgendwo vor LocalVectorMA zu stehen und zumindest im Falle des Absturzes sind sich Debugger und Programm nicht so ganz einig, was parentBone denn nun ist.

So richtig weiterhelfen tut das allerdings auch nicht. Das Wahrscheinlichste ist wohl, dass parentBone vor dem LocalVectorMA-Aufruf überschrieben wird und der Debugger im ersten Screenshot noch einen alten Wert für parentBone anzeigt (wieso auch immer). Kann man da nicht einfach mal durch den Code steppen und beobachten, was sich bei parentBone tut bzw. was vor der LEA-Instruktion in den Registern passiert? Oder ist es dafür zu schlecht reproduzierbar?

Marscel
2010-08-24, 00:40:28
Hm, später, bei 0x0006109e holt er sich aber was vom Stack und schreibt es in EAX. Kann das nicht offensichtlich 0 sein? Ich geh jetzt aber erstmal ins Bett

Ganon
2010-08-24, 00:42:31
Oder ist es dafür zu schlecht reproduzierbar?

Reproduzierbar ist er recht gut, aber erst nach mehreren tausend durchläufen. Das mit dem Debugger durchzugehen dürfte eine recht aufwändige Angelegenheit sein.

So, wie dem auch sei, es scheint eher was mit dem Multithreading zu tun zu haben. Ich habe ich mal im Quelltext umgesehen und bin auf die Methoden Sys_EnterCriticalSection gestoßen. Die scheinen das zu tun was ich versuchen wollte, mit der Synchronisierung.

Das habe ich jetzt mal in die Berechnungsroutine eingebaut.

Also der Bug mit dem NULL-Pointer ist damit weg. Dieser wird nicht mehr einfach mitten im Ablauf überschrieben.

Jetzt blinken die Modelle zwar trotzdem ab und zu noch mal auf und verzerren sich, aber das scheint dann wohl jetzt ein Problem mit der Reihenfolge zu sein, nehme ich mal an... Es hört einfach nicht auf xD

Coda
2010-08-24, 01:08:49
Anstatt im Blinden rumzustochern: http://valgrind.org/

Gnafoo
2010-08-24, 01:19:36
Hm, später, bei 0x0006109e holt er sich aber was vom Stack und schreibt es in EAX. Kann das nicht offensichtlich 0 sein? Ich geh jetzt aber erstmal ins Bett

Afaik dürfte das GNU Assembler sein. Da steht das Zielregister rechts (während es bei Intel-Assembler links steht), d. h. dort wird eax auf den Stack geschrieben. Die vom LEA erzeugte Adresse steht in esi und wird in 0x000610a6 für den Funktionsaufruf auf den Stack gepackt. Die mov-Instruktion über dem movss holt den Wert wieder dort ab.

Codas Vorschlag mit Valgrind ist im Prinzip auch sehr gut.

Sind da wirklich verschiedene Threads zu Gange? Das würde mich schon etwas wundern, beim Animationscode.

Ganon
2010-08-24, 01:26:21
OK. Valgrind werde ich heute Abend noch mal probieren.

Ich habe das mit der Synchronisierung jetzt mal ausgeweitet und die Grafikfehler sind jetzt auf ein Minimum reduziert... kommt kaum noch vor. Ist zwar, glaube ich, ein rumdoktorn an der falschen Stelle, aber nunja ;)

@Gnafoo

Nicht der Animationscode ist threaded, sondern der Renderthread usw. rufen den Animationscode auf.

Coda
2010-08-24, 01:36:59
Irgendwo Locks reinsetzen ist nicht gerade zielführend. Ich würde echt mal versuchen das im Ganzen zu verstehen und dann richtig zu synchronisieren, bzw. die Threads einfach rauszunehmen. Heutige CPUs sollten damit eh keine Probleme haben.

Gnafoo
2010-08-24, 02:33:20
Nicht der Animationscode ist threaded, sondern der Renderthread usw. rufen den Animationscode auf.

Naja mag ja sein, aber mehrere Render-Threads wird es ja wohl eher nicht geben. Und solange nur ein Thread den Code verwendet, dürfte es auch keine Probleme mit den globalen Variablen geben. Die Ausnahme wären wohl solche Variablen, die zum Datenaustausch zwischen Threads verwendet werden. Das dürfte dann aber eher ein paar Ebenen weiter oben stattfinden. Das da irgendein anderer Thread in parentBone und co. rumschreibt glaube ich eher nicht.

Ich stimme da Coda zu. Versuch erst einmal herauszukriegen, ob – oder inwiefern – der Threading-Kram hier überhaupt relevant ist, bevor du Locks einfügst.

Ganon
2010-08-24, 09:30:16
Naja mag ja sein, aber mehrere Render-Threads wird es ja wohl eher nicht geben. Und solange nur ein Thread den Code verwendet, dürfte es auch keine Probleme mit den globalen Variablen geben.
Die Ausnahme wären wohl solche Variablen, die zum Datenaustausch zwischen Threads verwendet werden. Das dürfte dann aber eher ein paar Ebenen weiter oben stattfinden. Das da irgendein anderer Thread in parentBone und co. rumschreibt glaube ich eher nicht.

Es müssen ja mehrere sein. Wie erklärst du dir sonst, dass es mit dem Lock keine Absturz-Probleme mehr gibt? Wäre es nur ein Thread hätte der Lock ja keinerlei Auswirkung. Wäre es ein Buffer-Overflow hätte der Lock es ja ebenfalls nicht verhindert.

Ich stimme da Coda zu. Versuch erst einmal herauszukriegen, ob – oder inwiefern – der Threading-Kram hier überhaupt relevant ist, bevor du Locks einfügst.

Den "Threadkram einfach rausnehmen" ist leichter gesagt als getan. Der Code ist, freundlich ausgedrückt, Mist. ;)

Achja, setzt man r_smp auf 1, verstärkt sich das Problem mit den Modellen.

Gnafoo
2010-08-24, 10:25:29
Es müssen ja mehrere sein. Wie erklärst du dir sonst, dass es mit dem Lock keine Absturz-Probleme mehr gibt? Wäre es nur ein Thread hätte der Lock ja keinerlei Auswirkung. Wäre es ein Buffer-Overflow hätte der Lock es ja ebenfalls nicht verhindert.

Nicht notwendigerweise. Das Einfügen des Locks macht sich ja auch auf dem Stack bemerkbar. Ein Funktionsaufruf pusht Parameter auf den Stack, ggf. braucht man neue lokale Variablen, etc. Vielleicht wird der Aufruf des Locks geinlined und dadurch eine Variable der Lock-Methode anstatt parentBone überschrieben? Der Lock geht dann zwar kaputt, aber der restliche Code funktioniert auf einmal. Es könnte auch sein, dass der Compiler die lokalen Variablen plötzlich anders anordnet und dadurch etwas anderes überschrieben wird. Schwer zu sagen, aber ich könnte mir schon vorstellen, dass einige Änderungen am Code das Problem auf den ersten Blick verschwinden lassen können, ohne dass es wirklich behoben wurde.

Das ist ja auch das Gefährliche an undefined Behaviour: es kann vorkommen, dass es gar nicht auffällt, bis sich plötzlich die Umstände ändern und es knallt.

Edit: du hast ja auch gesagt die Modelle verzerren und blinken teilweise noch mit dem Lock. Vielleicht deshalb, weil jetzt etwas anderes überschrieben wird, z. B. ein Vektor oder eine Matrix. So ganz scheint das Problem mit dem Lock ja auch nicht weg zu sein.

Funky Bob
2010-08-24, 18:36:09
Hallo,
was sagen denn die Adressen vor und hinter dem ParentBonezeiger? Vllt. lässt sich ein Muster erkennen (bspw. davor und oder dahinter wird alles genullt => Irgendwas wird vermutlich initialisiert und mit 0 vorbelegt).

MfG Rene
PS: Wenns schon getestet wurde, sorry, habe nicht alles gelesen...