|
-
[Tool] ReaDat
Geändert von Gottfried (13.01.2012 um 21:34 Uhr)
-
Super Sache.
Wer verstehen will wie Daedalus in kompilierter Form aussieht, ist damit bestens bedient.
Ich kann jedem empfehlen mal die eigene GOTHIC.DAT zu laden, und irgendeine Funktion parallel kompiliert und unkompiliert anzusehen. An Beispielen versteht man die internen Abläufe recht schnell, wenn man weiß, dass der zCParser im Grunde eine Stackmaschine ist (also mit einem Stapel arbeitet auf den etwas draufgelegt und heruntergenommen werden kann).
Hier mal ein kommentiertes Beispiel um den Einstieg zu erleichtern. Der Code
wird beim kompilieren übersetzt zu:
Code:
1: pushv i
2: pushi 1
3: add
4: pushv i
5: is
Was passiert hier? In Zeile 1 bis 3 wird i + 1 ausgewertet. Dazu wird zunächst die Variable i auf den Stapel gelegt ("gepusht"), dann die Ganzzahl 1. Jetzt liegen zwei Werte auf dem Stapel. Nun wird "+" ausgeführt. "+" nimmt die obersten zwei Werte vom Stapel (also 1 und i), addiert sie und legt das Ergebnis auf den Stapel.
Danach wird das Ergebnis zurück in die Variable i geschrieben. Dazu wird zunächst i auf den Stapel geschoben (also auf das bereits berechnete Ergebnis drauf). Dann wird "is" ausgeführt, eine Operation, die zunächst eine Variable vom Stapel nimmt, dann noch einen Wert vom Stapel nimmt und den Wert in die Variable schreibt. (Für den Moment habe ich verschwiegen, dass es zwei verschiedene Arten gibt Dinge auf den Stapel zu schieben, nämlich als Wert (Konstante) und als Variable (zuweisbar), aber das ist erstmal nicht wichtig).
Die meisten anderen Operationen erfolgen analog. Aufruf von Funktionen funktioniert, indem alle Argumente auf dem Stapel gelegt werden, bevor die Funktion aufgerufen wird. Das erste was die Funktion dann entsprechend macht ist, diese Argumente in ihre Parametervariablen zu schreiben. Erst danach folgt der Code für den ersten eigentlichen Befehl in der Funktion.
Daher sieht man am Anfang von Funktionen oft ein oder mehrfach hintereinander (je nach Parameterzahl) folgende beiden Zeilen:
Code:
pushv parameterVariable
is
Hier passiert nichts anderes als das die Argumente, die ja auf dem Stapel liegen an die Parametervariablen zugewiesen werden.
Mit diesem Wissen kann man glaube ich den Parser Byte Code schon einigermaßen lesen.
Was mir grade noch dazu einfällt:
In Ikarus stehe ich gelegentlich vor der Aufgabe den Parser Bytecode zur Laufzeit zu verändern. In dem Zusammenhang ist (beim Implementieren von while Schleifen) folgende Funktion zu Debugzwecken entstanden, die ab Ikarus Version 1.2 lauffähig sein wird (also noch nicht mit der Version, die zum Erstellungszeitpunkt dieses Posts aktuell ist).
Ihre Ausgabe ist weitaus schlechter lesbar als die von Readat, aber besser als nichts, wenn man Bytecode von Hand geschrieben hat und irgendetwas nicht funktioniert.
Darunter ist die Ausgabe von MEM_PrintFunction(MEM_PrintFunction) (also das was MEM_PrintFunction ausgibt, wenn diese Funktion sich selbst analysiert).
Code:
func void MEM_PrintFunction(var func function) {
var int tokenArr; tokenArr = MEM_ArrayCreate();
var int paramArr; paramArr = MEM_ArrayCreate();
var int posArr; posArr = MEM_ArrayCreate();
MEMINT_TokenizeFunction(MEM_GetFuncID(function), tokenArr, paramArr, posArr);
var int size;
size = MEM_ArraySize(posArr); /* all have the same size */
var int i; i = 0;
MEM_InitLabels(); MEM_InitStatArrs();
var int loop; loop = MEM_StackPos.position;
if (i < size - 1) { /* last token is guardian token */
var int tok; tok = MEM_ArrayRead(tokenArr, i);
var string str; str = ConcatStrings(IntToString(MEM_ArrayRead(posArr, i) - currParserStackAddress), " : ");
str = ConcatStrings(str, MEM_ReadStatStringArr(PARSER_TOKEN_NAMES, tok));
/* parameter? */
if (tok == zPAR_TOK_CALL || tok == zPAR_TOK_CALLEXTERN)
|| (tok == zPAR_TOK_PUSHINT || tok == zPAR_TOK_PUSHVAR)
|| (tok == zPAR_TOK_PUSHINST || tok == zPAR_TOK_SETINSTANCE)
|| (tok == zPAR_TOK_JUMP || tok == zPAR_TOK_JUMPF)
|| (tok == zPAR_TOK_PUSH_ARRAYVAR) {
/* take one parameter */
str = ConcatStrings(str, ": ");
str = ConcatStrings(str, IntToString(MEM_ArrayRead(paramArr, i)));
};
MEM_Info(str);
i += 1;
MEM_StackPos.position = loop;
};
/* cleanup */
MEM_ArrayFree(tokenArr);
MEM_ArrayFree(paramArr);
MEM_ArrayFree(posArr);
};
Code:
34735 : zPAR_TOK_PUSHVAR : 6291
34740 : zPAR_TOK_ASSIGNFUNC
34741 : zPAR_TOK_CALL : 12551
34746 : zPAR_TOK_PUSHVAR : 6292
34751 : zPAR_OP_IS
34752 : zPAR_TOK_CALL : 12551
34757 : zPAR_TOK_PUSHVAR : 6293
34762 : zPAR_OP_IS
34763 : zPAR_TOK_CALL : 12551
34768 : zPAR_TOK_PUSHVAR : 6294
34773 : zPAR_OP_IS
34774 : zPAR_TOK_PUSHINT : 6291
34779 : zPAR_TOK_CALL : 6122
34784 : zPAR_TOK_PUSHVAR : 6292
34789 : zPAR_TOK_PUSHVAR : 6293
34794 : zPAR_TOK_PUSHVAR : 6294
34799 : zPAR_TOK_CALL : 20882
34804 : zPAR_TOK_PUSHVAR : 6294
34809 : zPAR_TOK_CALL : 12760
34814 : zPAR_TOK_PUSHVAR : 6295
34819 : zPAR_OP_IS
34820 : zPAR_TOK_PUSHINT : 0
34825 : zPAR_TOK_PUSHVAR : 6296
34830 : zPAR_OP_IS
34831 : zPAR_TOK_CALL : 6044
34836 : zPAR_TOK_CALL : 19345
34841 : zPAR_TOK_SETINSTANCE : 5270
34846 : zPAR_TOK_PUSHVAR : 5269
34851 : zPAR_TOK_PUSHVAR : 6297
34856 : zPAR_OP_IS
34857 : zPAR_TOK_PUSHINT : 1
34862 : zPAR_TOK_PUSHVAR : 6295
34867 : zPAR_OP_MINUS
34868 : zPAR_TOK_PUSHVAR : 6296
34873 : zPAR_OP_LOWER
34874 : zPAR_TOK_JUMPF : 35179
34879 : zPAR_TOK_PUSHVAR : 6292
34884 : zPAR_TOK_PUSHVAR : 6296
34889 : zPAR_TOK_CALL : 12952
34894 : zPAR_TOK_PUSHVAR : 6298
34899 : zPAR_OP_IS
34900 : zPAR_TOK_PUSHVAR : 5235
34905 : zPAR_TOK_PUSHVAR : 6294
34910 : zPAR_TOK_PUSHVAR : 6296
34915 : zPAR_TOK_CALL : 12952
34920 : zPAR_OP_MINUS
34921 : zPAR_TOK_CALLEXTERN : 1
34926 : zPAR_TOK_PUSHVAR : 63306
34931 : zPAR_TOK_CALLEXTERN : 9
34936 : zPAR_TOK_PUSHVAR : 6299
34941 : zPAR_TOK_ASSIGNSTR
34942 : zPAR_TOK_PUSHVAR : 6299
34947 : zPAR_TOK_PUSHVAR : 4424
34952 : zPAR_TOK_PUSHVAR : 6298
34957 : zPAR_TOK_CALL : 19322
34962 : zPAR_TOK_CALLEXTERN : 9
34967 : zPAR_TOK_PUSHVAR : 6299
34972 : zPAR_TOK_ASSIGNSTR
34973 : zPAR_TOK_PUSHVAR : 4423
34978 : zPAR_TOK_PUSHVAR : 6298
34983 : zPAR_OP_EQUAL
34984 : zPAR_TOK_PUSHVAR : 4411
34989 : zPAR_TOK_PUSHVAR : 6298
34994 : zPAR_OP_EQUAL
34995 : zPAR_TOK_PUSHVAR : 4410
35000 : zPAR_TOK_PUSHVAR : 6298
35005 : zPAR_OP_EQUAL
35006 : zPAR_OP_LOG_OR
35007 : zPAR_TOK_PUSHVAR : 4412
35012 : zPAR_TOK_PUSHVAR : 6298
35017 : zPAR_OP_EQUAL
35018 : zPAR_TOK_PUSHVAR : 4402
35023 : zPAR_TOK_PUSHVAR : 6298
35028 : zPAR_OP_EQUAL
35029 : zPAR_OP_LOG_OR
35030 : zPAR_TOK_PUSHVAR : 4400
35035 : zPAR_TOK_PUSHVAR : 6298
35040 : zPAR_OP_EQUAL
35041 : zPAR_TOK_PUSHVAR : 4399
35046 : zPAR_TOK_PUSHVAR : 6298
35051 : zPAR_OP_EQUAL
35052 : zPAR_OP_LOG_OR
35053 : zPAR_TOK_PUSHVAR : 4397
35058 : zPAR_TOK_PUSHVAR : 6298
35063 : zPAR_OP_EQUAL
35064 : zPAR_TOK_PUSHVAR : 4396
35069 : zPAR_TOK_PUSHVAR : 6298
35074 : zPAR_OP_EQUAL
35075 : zPAR_OP_LOG_OR
35076 : zPAR_OP_LOG_OR
35077 : zPAR_OP_LOG_OR
35078 : zPAR_OP_LOG_OR
35079 : zPAR_OP_LOG_OR
35080 : zPAR_TOK_JUMPF : 35142
35085 : zPAR_TOK_PUSHVAR : 6299
35090 : zPAR_TOK_PUSHVAR : 63307
35095 : zPAR_TOK_CALLEXTERN : 9
35100 : zPAR_TOK_PUSHVAR : 6299
35105 : zPAR_TOK_ASSIGNSTR
35106 : zPAR_TOK_PUSHVAR : 6299
35111 : zPAR_TOK_PUSHVAR : 6293
35116 : zPAR_TOK_PUSHVAR : 6296
35121 : zPAR_TOK_CALL : 12952
35126 : zPAR_TOK_CALLEXTERN : 1
35131 : zPAR_TOK_CALLEXTERN : 9
35136 : zPAR_TOK_PUSHVAR : 6299
35141 : zPAR_TOK_ASSIGNSTR
35142 : zPAR_TOK_PUSHVAR : 6299
35147 : zPAR_TOK_CALL : 3400
35152 : zPAR_TOK_PUSHINT : 1
35157 : zPAR_TOK_PUSHVAR : 6296
35162 : zPAR_OP_ISPLUS
35163 : zPAR_TOK_PUSHVAR : 6297
35168 : zPAR_TOK_SETINSTANCE : 5270
35173 : zPAR_TOK_PUSHVAR : 5269
35178 : zPAR_OP_IS
35179 : zPAR_TOK_PUSHVAR : 6292
35184 : zPAR_TOK_CALL : 12563
35189 : zPAR_TOK_PUSHVAR : 6293
35194 : zPAR_TOK_CALL : 12563
35199 : zPAR_TOK_PUSHVAR : 6294
35204 : zPAR_TOK_CALL : 12563
35209 : zPAR_TOK_RET
-
Zitat von Gottfried
Das (die?) GUI ist nicht optimal, das Programm ist also auch nicht gerade das Nutzerfreundlichste.
Ich finde Sektenspinner sollte nicht der Einzige sein, der seine Anerkennung ausdrückt. Auch von mir ein "Super Sache."
Das GU-Interface ist reduziert auf das Wesentliche um die Dat übersichtlich darzustellen, was für meine Begriffe einem Optimum entspricht. Es ist ein praktisches Programm, um einen kleinen Einblick in die praktische Umsetzung der Programmierung zu erhalten.
Was bei diesem Programm fehlt ist ein Editor, oder nicht?
Man könnte statt bei jedem *_Init bestimmte Funktionen zu initialisieren, direkt in die .Dat den nötigen Bytecode schreiben. Meinetwegen ohne Parser, sondern mit Dropdownmenüs. Erst den Befehl, dann den Parameter, wenn es einen gibt, aus der Liste der Zulässigen oder per Eingabe bei Literalen.
Auch wenn Sektenspinner begeistert ist, von der Möglichkeit den Bytecode zu betrachten, denke ich, wirklich interessant wäre es den Bytecode (einfach) verändern zu können.
EDIT: Das würde vielleicht auch die schwierige Darstellung von laufzeitverändertem Bytecode entspannen, da wohl die allermeisten Veränderungen nur notgedrungen zur Laufzeit vorgenommen werden.
EDIT2: Verstehe ich das richtig, dass jedes String-Literal einen Symbolindex besitzt?
Grüße,
Ingenieur
Geändert von Ingenieur (14.01.2012 um 20:26 Uhr)
-
Zitat von Ingenieur
Was bei diesem Programm fehlt ist ein Editor, oder nicht?
Ich kann mir keine sinnvolle Anwendung davon denken. Man will ja seinen Code mit Gothic kompilieren können ohne jedesmal ein zusätzliches externes Programm laufen lassen zu müssen. Und so oft passiert es nun auch nicht, dass man Bytecode verändern will. Tatsächlich glaub ich nicht, dass manuelle Bytecodebearbeitung irgendeinen Nutzen außerhalb von LeGo und Ikarus besitzt.
Zitat von Ingenieur
da wohl die allermeisten Veränderungen nur notgedrungen zur Laufzeit vorgenommen werden.
Manches ist prinzipiell nur zur Laufzeit möglich. Das neue Ikarus wird zum Beispiel Schleifen einführen:
Code:
var int i; i = 0;
while(i < 10);
if (cond(i)) {
break;
} else if (cond2(i)) {
continue;
};
i += 1;
end;
Abgesehen davon, dass ich Ikarus ohnehin als Quelltext ausliefere und nicht in einer nachbearbeiteten .DAT, ist die Aufgabe hier prinzipiell nur zur Laufzeit zu lösen (weil es um Code des Nutzers geht).
Zitat von Ingenieur
EDIT2: Verstehe ich das richtig, dass jedes String-Literal einen Symbolindex besitzt?
Ja.
-
Zitat von Sektenspinner
Abgesehen davon, dass ich Ikarus ohnehin als Quelltext ausliefere und nicht in einer nachbearbeiteten .DAT, ist die Aufgabe hier prinzipiell nur zur Laufzeit zu lösen (weil es um Code des Nutzers geht).Ja.
Das hätte ich nicht gemeint. Vermutlich hätte man sich etwas wie ein Patchsystem ausgedacht, bei dem im Quelltext ein Dummy steht, der im Nachgang zur Kompilierung durch den Patch funktional wird.
Zitat von Sektenspinner
Ich kann mir keine sinnvolle Anwendung davon denken.
Wenn eine Funktion auf den Stack gepusht wird, dann als Integer-Literal. Ich würde gerne verhindern, dass der Index in die Func-Var geschrieben wird und stattdessen, direkt in eine Int-Var speichern. Naja, eine Nischenanwendung.
Zitat von Sektenspinner
Und so oft passiert es nun auch nicht, dass man Bytecode verändern will. Tatsächlich glaub ich nicht, dass manuelle Bytecodebearbeitung irgendeinen Nutzen außerhalb von LeGo und Ikarus besitzt.
Manches ist prinzipiell nur zur Laufzeit möglich.
So wie ich die jmpf-Anweisungen lese, könnte man durch aus die Schleifen zur Übersetzungszeit einrichten. Ich sehe aber ein, dass es extrem unpraktisch wäre einen Patch und einen Dummy für jede Funktion, in der eine Schleife verwendet werden soll, aufzusetzen.
Ich kann halt nicht immer richtig liegen ;-)
Wo du sicher mit richtig liegst, sind die while-Schleifen
Grüße,
Ingenieur
-
Zitat von Ingenieur
Wenn eine Funktion auf den Stack gepusht wird, dann als Integer-Literal. Ich würde gerne verhindern, dass der Index in die Func-Var geschrieben wird und stattdessen, direkt in eine Int-Var speichern. Naja, eine Nischenanwendung.
In Ikarus gibt es bereits
Code:
func int MEM_GetFuncID(var func fnc)
für genau dieses Problem.
Zitat von Ingenieur
So wie ich die jmpf-Anweisungen lese, könnte man durch aus die Schleifen zur Übersetzungszeit einrichten. Ich sehe aber ein, dass es extrem unpraktisch wäre einen Patch und einen Dummy für jede Funktion, in der eine Schleife verwendet werden soll, aufzusetzen.
Sehe ich genauso.
-
Vielleicht ist das ja ein Vorschlag:
Eine Klasse, deren Member andere Offsets erhalten, als ihre natürlichen Offsets. Dazu noch ein Globales Objekt, das mit einer einfachen Adresse initialisiert wird z. B. 4 oder der des Parsers.
Die Offsets wählt man dann so, dass die wichtigen Speicherbereiche verfügbar werden.
Code:
class Verbogene { /* ... */ };
instance Verbogen (Verbogene);
Verbogen.Parserposition
Verbogen.SymbolTable
Verbogen.ShowDebug
Verbogen.LoadingTex
Zu aufwändig und zu wenig nützlich - aber gehen würde das doch?
Ingenieur
-
Wenn ich dich richtig verstehe, geht es dir darum den Zugriff auf bestimmte Daten zu ermöglichen ohne MEM_ReadInt und MEM_WriteInt zu benutzen. Das ist eine Idee, die mir gut gefällt. Ich könnte mir vorstellen etwas der folgenden Art in Ikarus einzubauen:
Code:
/* Diese Funktion würde ich einbauen */
func void MEM_Relink(var string variableName, var int dataAdr);
/* Dies sind einige Variablen */
var int ShowDebug;
var int HeroPosX; var int HeroPosY; var int HeroPosZ;
var string myViewText;
/* So könnte die Funktion benutzt werden */
{
MEM_Redirect("SHOWDEBUG", showDebugAddress);
var zCVob her; her = Hlp_GetNpc(hero);
MEM_Relink("HEROPOSX", _@(her.trafoObjToWorld[zCVob_trafoObjToWorld_X]));
MEM_Relink("HEROPOSY", _@(her.trafoObjToWorld[zCVob_trafoObjToWorld_Y]));
MEM_Relink("HEROPOSZ", _@(her.trafoObjToWorld[zCVob_trafoObjToWorld_Z]));
MEM_Relink("MYVIEWTEXT", _@s(myFunnyView.text));
}
/* Danach ist bequemer Zugriff auf bestimmte Werte möglich */
{
showDebug = 1; //wird auf showDebugAddress schreiben
myViewText = "Hello"; //wird direkt in einem view schreiben.
[...]
};
(Hierbei sind _@ und _@s Addressoperatoren, die in kommenden Ikarus Versionen enthalten sein werden)
Das könnte tatsächlich nützlich sein. Ich bin etwas von deinem Schema abgewichen und sehe keinen Zusammenhang zum ursprünglichen Thema aber die Idee gefällt mir gut und sie sollte auch gut umsetzbar sein!
Ist das so wie du es dir vorgestellt hast?
-
Ja, so habe ich das gemeint.
Und die Verknüpfung zum ursprünglichen Thema besteht darin, dass man das man diese Informationen, über Offsets und welche Symbol überschrieben werden sollen, in die Dat einbringen könnte, indem man sie mit dem von Gottfried vorgestellten (erweiterten) Programm hineinschreibt.
Ingenieur
-
Zitat von Ingenieur
Ja, so habe ich das gemeint.
Und die Verknüpfung zum ursprünglichen Thema besteht darin, dass man das man diese Informationen, über Offsets und welche Symbol überschrieben werden sollen, in die Dat einbringen könnte, indem man sie mit dem von Gottfried vorgestellten (erweiterten) Programm hineinschreibt.
Ingenieur
Das lässt sich aber ja relativ simpel auch zur Laufzeit machen.
-
Zitat von Ingenieur
Ja, so habe ich das gemeint.
Und die Verknüpfung zum ursprünglichen Thema besteht darin, dass man das man diese Informationen, über Offsets und welche Symbol überschrieben werden sollen, in die Dat einbringen könnte, indem man sie mit dem von Gottfried vorgestellten (erweiterten) Programm hineinschreibt.
Ingenieur
Nur begrenzt, weil du zum Beispiel nicht weißt wo im Speicher der Held landet oder die Daten der Symboltabelle um eins von deinen Beispielen zu nennen.
Dein LoadingTex hat zudem das Problem, dass dies ein C-String ist, Strings im Sinne des Parsers aber zString sind.
Geändert von Sektenspinner (15.01.2012 um 19:35 Uhr)
-
Einverstanden. Ihr habt mich davon überzeugt, dass derartige Änderungen nicht in die Dat-Datei gehören.
Ich mache dazu keine Vorschläge mehr.
Ingenieur
-
Zitat von Gottfried
ReaDat
Sehr gut, Danke, Danke, Danke!
(dann kann ich mein primitives Plugin endlich wegwerfen)
-
-
Berechtigungen
- Neue Themen erstellen: Nein
- Themen beantworten: Nein
- Anhänge hochladen: Nein
- Beiträge bearbeiten: Nein
|
|