2. Beispiele
Nachdem du die Funktion zu deinem stdlib Ordner hinzugefügt hast kannst du anfangen die ersten Funktionen zu Programmieren.
Der Beispiel Code des Compilers ist dieser:
Wie ich bereits gesagt habe gibt die Funktion immer nur 42 zurück.
Wenn man nun den Create Machine Code Button drückt wird MCode wie dieser entstehen:
Code: Select all
MyFunction := MCode("2,x86:uCoAAADD,x64:uCoAAADD")
Du kannst diesen Code zu deinem Script hinzufügen:
Benutz DllCall um die Funktion auszuführen.
Code: Select all
MyFunction := MCode("2,x86:uCoAAADD,x64:uCoAAADD")
Msgbox % DllCall(MyFunction,"cdecl")
Man muss die cdecl Aufrufsonvention verwenden da diese standard bei C++ ist.
Jetzt aber mal eine Kompliziertere Funktion
Sie sucht nach dem Ende eines Strings: Dem 0-Zeichen:
Code: Select all
int stringlen(char *str)
{
int i=0;
for (; str[i]!=0; i++);
return i;
}
Das Ergebnis ist der folgende Code:
Code: Select all
stringlen := MCode("2,x86:i0wkBDPAOAF0B0CAPAgAdfnD,x64:M8A4AXQKSP/B/8CAOQB19vPD")
MsgBox, % DllCall(stringlen, "astr", "test","cdecl")
Jetzt eine noch kompliziertere Funktion:
Code: Select all
unsigned int MyFunction(unsigned int a,unsigned int b)
{
if (a>0)
return MyFunction(a-1,b*a);
else
return b;
}
Diese Funktion berechnet die Fakultät der Nummer a.
Um dies zu erreichen ruft sich die Funktion immer wieder selber auf.
Jetzt der AHK Code:
Code: Select all
MyFunction := MCode("2,x86:i0wkBItEJAiFyXQKjWQkAA+vwUl1+sM=,x64:hcl0Bw+v0f/JdfmLwsM=")
Msgbox % DllCall(MyFunction,"int",3,"int",1,"cdecl")
Das Ergebnis von 3 Fakultät ist 6 wie erwatrtet.
Aber man muss zuviel tippen. Eigentlich braucht man diesen Teil garnicht:
,"int",1,"cdecl") Wir können uns ja mal in MCode eine Caller funktion schreiben die diesen Teil übernimmt.
Um die stdcall Konvention zu benutzen (Welche die standart Konvention für DllCall ist) muss man ein _stdcall vor der Funktion schreiben
Danach wird der code so aussehen:
Code: Select all
unsigned int MyFunction(unsigned int a,unsigned int b)
{
if (a>0)
return MyFunction(a-1,b*a);
else
return b;
}
unsigned int _stdcall MyFunctionCaller(unsigned int a)
{
return MyFunction(a,1);
}
Compiliert ist es das folgende:
Code: Select all
MyFunction := MCode("2,x86:i0wkBItEJAiFyXQKjWQkAA+vwUl1+sM=,x64:hcl0Bw+v0f/JdfmLwsM=")
MyFunctionCaller := MCode("
(LTrim Join
2,x86:i0QkBIXAdA5QSFDoAAAAAIPECMIEALgBAAAAwgQA,x64:i9GFyXQH/8npAAAAALgBAAAAww==
)")
Msgbox % DllCall(MyFunctionCaller,"uint",3)
Aber die Messagebox ist leer.
Nein wir haben keinen Fehler gemacht. Das Problem heißt MCode:
OK Stellen wir uns vor du hättest ein AHK script, musst aber alle funktionen über ihre Position aufrufen: (e.g. call Zeichen nr. 144.).
Hier startet deine Funktion: bei Zeichen 144.
Jetzt stellen wir uns vor was passieren würde wenn man vor Zeichen 144 noch mehr Zeichen hinzufügt:
Deine Funktion würde sich nach hinten verschieben z.B. um 20 Zeichen.
Wenn man nicht die start zeichen nummer der Funktion ändert wird man ein anderes Ergebnis haben als das erwartete Ergebnis.
Funktions aufrufe in kompiliertem C++ sind ähnlich:
Eine Funktion wird aufgerufen in dem man die Startadresse des Speicherbereichs angibt in dem die Funtkion liegt.
Aber die Funktion wird erst hinzugefügt nach dem ganzen AHK Zeug hinzugefügt.
D.h. ruft man etwas völlig anderes auf, was dann auch zu Fehlern führt.
Du kannst dir auch die Addresse anzeigen lassen die der Compiler erwartet hat:
(Note: Nicht für 64 bit PCs)
Code: Select all
/*
unsigned int MyFunction(unsigned int a,unsigned int b)
{
if (a>0)
return MyFunction(a-1,b*a);
else
return b;
}
unsigned int MyFunctionaddress()
{
return (unsigned int)(&MyFunction);
}
*/
MyFunction := MCode("2,x86:i0wkBItEJAiFyXQKjWQkAA+vwUl1+sM=,x64:hcl0Bw+v0f/JdfmLwsM=")
MyFunctionaddress := MCode("2,x86:uAAAAADD,x64:SI0FAAAAAMM=")
Msgbox % "The expected Address of the function is :" DllCall(MyFunctionaddress)
Msgbox % "The actual address of the function is :" MyFunction
Also kann man auf normalen Wege keine Funktionen aufrufen
Also was machen wir jetzt
Die Antwort ist Funktionspointer.
...
Funktionspointer sind hier Dokumentiert:
http://www.cplusplus.com/doc/tutorial/pointers/
Bitte lest es euch durch bevor ihr weiterlest.
Um unsere Funktion in MCode also ordentlich aufzurufen müssen wir den Pointer zu der Startadresse an die aufrufende Funktion übergeben:
Code: Select all
unsigned int MyFunction(unsigned int a,unsigned int b)
{
if (a>0)
return MyFunction(a-1,b*a);
else
return b;
}
unsigned int _stdcall MyFunctionCaller(unsigned int a,unsigned int(*MyFunction)(unsigned int,unsigned int) )
{
return (*MyFunction)(a,1);
}
Results in:
Code: Select all
MyFunction := MCode("2,x86:i0wkBItEJAiFyXQKjWQkAA+vwUl1+sM=,x64:hcl0Bw+v0f/JdfmLwsM=")
MyFunctionCaller := MCode("2,x86:i0QkBGoBUP9UJBCDxAjCCAA=,x64:SIvCugEAAABI/+A=")
Wenn wir die neue Funktion jetzt aufrufen müssen wir als zusätzlichen Parameter den Funktionspointer übergeben.
Code: Select all
MyFunction := MCode("2,x86:i0wkBItEJAiFyXQKjWQkAA+vwUl1+sM=,x64:hcl0Bw+v0f/JdfmLwsM=")
MyFunctionCaller := MCode("2,x86:i0QkBGoBUP9UJBCDxAjCCAA=,x64:SIvCugEAAABI/+A=")
Msgbox % DllCall(MyFunctionCaller,"uint", 3,"UPtr",MyFunction)
MyFunction enthält exakt diesen Wert.
Und jetzt gibt die Message box auch den richtigen Wert zurück.
Ausserdem gibt es andere Dinge die in MCode nicht funktionieren werden:
- Statische Variablen
- Und Objekt Funktionen aufrufen(thiscall konvention)
- Manchmal auch der aufruf der eigenen Funktion
- Auch vorher festhelegte Floats können Probleme verursachen, da sie eher an einer bestimmten adresse gespeichert, als im code gespeichert werden.
3. Wie erstelle ich einen MCode generator?
MCode also Machine Code ist an sich nichts anderes als eine compilierte Funktion. Bei .exe sowie .dll Dateien findet man meistens jede Menge Zusatzinfos wie z.B. wo die Funtionen starten ihre Namen Querverweise etc..
Beim MCode fehlen diese Infos aber völlig. Es sind wirklich nur Instruktionen die vom Prozessor direkt ausgeführt werden. Daher auch die oben genannten Limitationen.
Um aus normalen Code zu erhalten gibt es verschiedenste Vorgehensweisen. Was sie alle verbindet ist der Fakt, dass man einen Compiler braucht, welcher einem die Instruktionen erstellt.
Abhängig vom Compiler geht dies auf Unterschiedlichem Wege:
Für C++:
gcc/MS_VS:
Die beiden bekanntesten Compiler für C/C++ sind garantiert der Microsoft Visual Studio Compiler und die kostenfreie, aber nicht schlechtere, Alternative dem GNU compiler.
Bei beiden kann man netter Weise eine ähnliche Vorgehensweise. Man kann diese garantiert für viele Compiler benutzen.
Man sagt dafür dem compiler über eine Flag (die ich grade nicht parat habe), dass er ein sogenanntes Assembly listing erzeugen soll.
Dabei entsteht eine Datei welche die Assembly Instruktionen, ihr Offset, den Namen eines Labels, sowie ihre Übersetzung im Binärcode der CPU im Hexadezimalformat enthält.
Das Offset ist für uns unwichtig. Die Assembly Instruktion im Textformat auch. Was wir brauchen sind die Labels sowie der Binärcode.
Die Labels markieren den Anfang sowie das Ende einer Funktion. So findet man ein Label, welches den Namen einer Funktion die wir aus dem Listing extrahieren wollen enthält.
Alle Instruktionen von diesem Label bis zum nächsten Label gehören zu unserer Funktion.
Wir müsen nun nur noch alle Binärcodes der Instruktionen unserer Funktion auslesen und wir haben die Daten die wir benötigen um den MCode dieser Funktion zu erzeugen.
Dazu müssen wir dann den Hexadezimalcode zusammenfügen und mit einer Funktion dafür sorgen ,dass er geladen wird.