Code-Beispiel
Primitive Maschinencodegenerierung
Lizenz: | Erster Autor: | Letzte Bearbeitung: |
LGPL | theta | 09.05.2013 |
Zwei Beispiele für Maschinencodegenerierung
Mit + wird der Wert der Speicherzelle inkrementiert, mit - dekrementiert. Enter führt alle (je) eingegebenen Befehle aus.
Die Funktionsweise ist zwar sehr einfach, aber es lassen sich viele (bzw. im Grunde unendlich viele) Zahlenreihen darstellen. (Einfach nach einem bestimmten System + und - Zeichen eingeben und dann z.B. die resultierenden Zahlen bei oeis.org eingeben)
Eingabebeispiel:
+
++
+++
++++
...
Ergibt die Reihe: 0, 1, 4, 10, 20, ...
Funktionsweise:
Es wird geschaut, wo sich die beiden von Labels umgebenen Assemblerbefehle inc und dec befinden.
In einen dynamischen Speicherbereich werden die auszuführenden Befehle - inc und dec - kopiert, je nachdem was gebraucht wird.
Außerdem wird ein Rücksprungbefehl angehängt, der dafür sorgt, dass das Programm danach normal weiterläuft.
In diesen Speicherbereich wird nun mittels eines Assemblerbefehls hineingesprungen, so dass der Speicherbereich ausgeführt wird.
#Include "crt/string.bi" 'für memcpy
'Beispiel für sehr einfache Maschinencodegenerierung (FB 0.23)
Const MaxAnBefehle = 128 'z.B. 128
Dim As UInteger Ptr _inc,_dec,_ret 'Die Positionen im Speicher, wo die Befehle liegen
Dim As UInteger inc_l,dec_l,ret_l 'Die Länge der Befehle inc,dec,ret in der Maschinensprache (in Bytes)
Dim As Integer zelle
Declare Sub ende()
#Macro offsetUndLaengeLaden(StartASMLabel,EndASMLabel,OffsetVar,LaengeVar)
Asm
mov eax,offset StartASMLabel
mov [OffsetVar],eax 'OffsetVar = offset von StartASMLabel
mov ebx,offset EndASMLabel
'eax enthält ja noch StartASMLabel
Sub ebx,eax 'Die Länge des Codes zwischen Start und Ende ist Ende - Start
mov [LaengeVar], ebx 'LaengeVar = offset von EndASMLabel - offset von StartASMLabel
End Asm
#EndMacro
'Als erstes müssen die Adressen der Befehle in die entsprechenden Variablen geladen werden
offsetUndLaengeLaden(__dec,end__dec,_dec,dec_l)
offsetUndLaengeLaden(__inc,end__inc,_inc,inc_l)
offsetUndLaengeLaden(__ret,end__ret,_ret,ret_l)
Dim As UByte Ptr zeiger,programm = Callocate(MaxAnBefehle*inc_l+ret_l) 'inc und dec haben die gleiche Codelänge
zeiger=programm
Dim As String eingabe
#if defined(__FB_WIN32__)
? WStr("+: Zellenwert um Einen erhöhen")
#else
? "+: Zellenwert um Einen erhöhen"
#endif
? "-: Zellenwert um Einen senken"
Do
Line Input eingabe
eingabe=Left(eingabe,MaxAnBefehle) 'zurechtstutzen
If LCase(eingabe)="exit" Or LCase(eingabe)="quit" Then ? "Programm beendet." : ende()
'Befehle nach Anweisung des eingegebenen "Programms" an die richtigen Stellen kopieren
For i As Integer=0 To Len(eingabe)-1
Select Case eingabe[i]
Case Asc("+")
memcpy(zeiger,_inc,inc_l)
zeiger += inc_l
Case Asc("-")
memcpy(zeiger,_dec,dec_l)
zeiger += dec_l
End Select
Next
memcpy(zeiger,_ret,ret_l) 'return setzen
'da der Zeiger nicht nach vorne bewegt wird, wird das ret beim nächsten Durchgang überschrieben, sofern Maschinencode dazukommt
Asm
Call dword Ptr [programm] 'Das Maschinencodeprogramm ausführen
End Asm
? zelle
Loop Until zeiger-programm >= MaxAnBefehle*3 + 1
Print "Programmspeicher voll."
ende()' - der Code hiernach soll nicht ausgeführt werden
Asm
__inc:
inc dword Ptr [zelle]
end__inc:
__dec:
dec dword Ptr [zelle]
end__dec:
__ret:
ret
end__ret:
End Asm
Sub ende()
#If Defined(__FB_WIN32__)
Print "Beliebige Taste zum Beenden..."
GetKey
#EndIf
end
End Sub
Das nächste Beispiel für Maschinencodegenerierung ist ein Forth für Arme... Man kann nämlich keine eigenen Wörter erstellen und es existieren auch keine Kontrollstrukturen. Wer Forth nicht kennt, kann bei Wikipedia unter "Forth (Informatik)" fündig werden.
Die zusammengefasste Funktionsweise:
Die Eingabe wird von überflüssigen Zeichen bereinigt und als einzelne Wörter mit den bekannten Befehlen verglichen. Ist die Eingabe ein bekanntes Wort, wird das entsprechende Stück Assemblercode, bzw. Maschinensprache, an einen UByte-Programmpuffer angehängt.
Dieser Programmpuffer wird dann ausgeführt.
Dafür muss der Anfangspunkt des Maschinencodestückens und das Ende, bzw. dessen Länge, bekannt sein.
Die Assemblercodestücke(add,drop,swap,...) sind von Labels umrahmt, die diese Positionsangaben liefern können.
An das Ende des Programmpuffer wird ein Rücksprungbefehl gehängt, damit das Programm nach der Ausführung kontrolliert weiterlaufen kann.
Eingegebene Zahlen und Rückgabewerte werden auf den Systemstack gelegt.
Programmbeispiel: Codestückchen, die eine Negierung vornehmen.
42 dup 2 * - .
oder:
42 0 swap - .
erzeugen beide die Ausgabe -42
#Include "crt/string.bi" 'für memcpy
#Include "crt/stdio.bi" 'für printf
'Weiteres Beispiel für Maschinencodegenerierung (FB 0.23)
Const MaxAnBefehle = 128 'maximale Anzahl der Befehle einer Eingabe in Bytes
Dim As UInteger Ptr _add,_sub,_mul,_div,_ret,_print,_drop,_swap,_dup
Dim As UInteger add_l,sub_l,mul_l,div_l,ret_l,print_l,drop_l,swap_l,dup_l 'die Länge der internen Befehle add,sub,ret in der Maschinensprache (in Bytes)
Dim temp As Integer
Declare Sub _ende()
Declare Sub loescheDoppelteLeerzeichen(text As String) 'entfernt doppelten Whitespace (Leerzeichen, Tabs)
Declare function nToken(text As String,n As UInteger) As String ' gibt den n-ten Token von text zurück. Split point ist Leerzeichen.
Declare function numerisch(s As String) As Integer ' ist s eine Zahl?
Declare Sub HEXausgabe(p As UByte Ptr, length As UInteger) 'den Inhalt eines Speicherbereiches von der Länge "length" als Hexadzimalzahlen in Bytepäckchen ausgeben
#Macro offsetUndLaengeLaden(StartASMLabel,EndASMLabel,OffsetVar,LaengeVar)
Asm
mov eax,offset StartASMLabel
mov [OffsetVar],eax 'OffsetVar = offset von StartASMLabel
mov ebx,offset EndASMLabel
'eax enthält ja noch StartASMLabel
Sub ebx,eax 'Die Länge des Codes zwischen Start und Ende ist Ende - Start
mov [LaengeVar], ebx 'LaengeVar = offset von EndASMLabel - offset von StartASMLabel
End Asm
#EndMacro
'Als erstes müssen die Adressen der Befehle in die entsprechenden Variablen geladen werden
offsetUndLaengeLaden(__sub,end__sub,_sub,sub_l) 'subtrahiert die beiden Werte, die oben auf dem Stack liegen; vorzeichenbehaftet. Beispiel: 3 5 + . => 8
offsetUndLaengeLaden(__add,end__add,_add,add_l) 'addiert die beiden Werte, die oben auf dem Stack liegen; vorzeichenbehaftet. Beispiel: 3 5 - . => -2
offsetUndLaengeLaden(__mul,end__mul,_mul,mul_l) 'mulitpiliziert, vorzeichenbehaftet. Beispiel: 3 4 * . => 12
offsetUndLaengeLaden(__div,end__div,_div,div_l) 'dividiert, vorzeichenbehaftet. Beispiel: 90 3 / . => 30
offsetUndLaengeLaden(__ret,end__ret,_ret,ret_l) 'zum Zurückspringen
offsetUndLaengeLaden(__print,end__print,_print,print_l) 'Ausgabe des obersten Wertes auf dem Stack. Beispiel: 12 . => 12
offsetUndLaengeLaden(__drop,end__drop,_drop,drop_l) 'Löscht den obersten Wert des Stacks.Beispiel: 4 5 drop . => 4
offsetUndLaengeLaden(__swap,end__swap,_swap,swap_l) 'tauscht die beiden obersten Stackwerte. Beispiel: 4 5 swap . . => 4 5
offsetUndLaengeLaden(__dup,end__dup,_dup,dup_l) 'dupliziert den obersten Stackwert. Beispiel: 42 dup . . => 42 42
Dim As UByte Ptr zeiger,programm = Callocate(MaxAnBefehle)
zeiger=programm
Dim As String eingabe,token,temp_str
Dim As UInteger n
Do
Line Input ; eingabe
loescheDoppelteLeerzeichen(eingabe)
'Befehle nach Anweisung des eingegebenen Programms an die richtigen Stellen kopieren
n=0
Do
n+=1
token=nToken(eingabe,n)
If token="" Then Exit Do
If LCase(token)="exit" Or LCase(token)="quit" Or LCase(token)="bye" Then _ende()
If zeiger-programm > MaxAnBefehle Then printf(!" Too many commands in one line.\n") : zeiger=programm : Exit Do
Select Case LCase(token)
Case "+"
memcpy(zeiger,_add,add_l)
zeiger += add_l
Case "-"
memcpy(zeiger,_sub,sub_l)
zeiger += sub_l
Case "*"
memcpy(zeiger,_mul,mul_l)
zeiger += mul_l
Case "/"
memcpy(zeiger,_div,div_l)
zeiger += div_l
Case "."
memcpy(zeiger,_print,print_l)
zeiger += print_l
Case "drop"
memcpy(zeiger,_drop,drop_l)
zeiger += drop_l
Case "swap"
memcpy(zeiger,_swap,swap_l)
zeiger += swap_l
Case "dup"
memcpy(zeiger,_dup,dup_l)
zeiger += dup_l
Case Else 'kein interner Befehl, also ...
If numerisch(token) Then
temp_str =!"\104"+mki(Val(token))
memcpy(zeiger,StrPtr(temp_str),1+4) '\104 ist der Maschinenbefehl für "push 32 Bit Zahl"
zeiger += 1+4
Else 'Wort ist unbekannt
printf(" %s ?",StrPtr(token))
zeiger=programm
Exit Do
EndIf
End Select
Loop
memcpy(zeiger,_ret,ret_l) 'return setzen
'HEXausgabe(programm,zeiger-programm) 'wenn dies "einkommentiert" wird, kann man den erzeugten Maschinencode betrachten
Asm
jmp dword Ptr [programm] 'Das Maschinencodeprogramm ausführen
weiter: 'hierhin wird zurückgesprungen
End Asm
zeiger=programm
printf(!" ok\n")
Loop
_ende()' - der Code hiernach soll nicht direkt ausgeführt werden
Asm
__add:
pop eax
pop ebx
Add eax,ebx
push eax
end__add:
__sub:
pop eax
pop ebx
Sub ebx,eax
push ebx
end__sub:
__mul:
pop eax
pop ebx
imul eax,ebx
push eax
end__mul:
__div:
pop ebx
pop eax
cdq
idiv ebx
push eax
end__div:
__print:
pop [temp]
mov eax,offset print_wrapper 'hier wird zuerst ein Wrapper mittels ABSOULUTER Adresse angesprungen, der dann print ausführt
'die absolute Adresse ist nötig, da eine relative Adresse beim Kopieren irgendwo ins "Nirvana" weist.
'der Wrapper ist nötig, damit printf aufgerufen werden kann,
'auch wenn in einer anderen Version von FB printf die Parameter anders erwartet oder das Label anders heißt
Call eax
end__print:
__drop:
Add esp,4 ' verschiebt den Stackpointer um 4 Bytes = 32 Bit = 1 * Integer, vom Effekt auf den Stackpointer mit pop identisch
end__drop:
__swap:
pop eax
pop ebx
push eax
push ebx
end__swap:
__dup:
pop eax
push eax
push eax
end__dup:
__ret:
mov eax,offset weiter
jmp eax
end__ret:
'---------------------------------------------------------PRINT-WRAPPER-------------------------------------------------
print_wrapper:
End Asm
printf(" %d",temp) 'temp haben wir ja vorhin mit pop einen Wert geholt
Asm ret
Sub _ende()
End
End Sub
Sub loescheDoppelteLeerzeichen(ByRef text As String)
'entfernt doppelte Leerzeichen und Tabs und ersetzt diese gegen einfache Leerzeichen
'Die Schleife ist beendet, wenn der String im Vergleich zur Länge vor dem letzten Durchlauf nicht geschrumpft ist
Dim As Integer alt,leng
text=Trim(text)
If text="" Then Exit Sub
Do
alt=leng
leng=Len(text)
If alt=leng Then Exit Sub
For i As Integer=0 To leng-1
'32 ist Leerzeichen, 9 ist Tab
If (text[i]=32 Or text[i]=9) And (text[i+1]=32 Or text[i+1]=9) Then
text=Mid(text,1,i)+" "+Mid(text,i+3)
EndIf
Next
Loop
End Sub
Function nToken(z As String, n As UInteger) As String
'gibt den n-ten Token wieder
'dabei dient das Leerzeichen als split point
Dim As Integer start=-1,ende,npos,leng=Len(z)
If n=1 Then
start=0
EndIf
ende=leng-1
For i As Integer=0 To leng-1
If z[i]=32 Then '32 ist das Leerzeichen
npos +=1
If npos=n-1 Then
start=i+1
ElseIf npos=n Then
ende=i-1
Exit For
EndIf
EndIf
Next
Return Mid(z,start+1,ende-start+1)
End Function
Sub HEXausgabe(p As UByte ptr,length As UInteger) 'den Inhalt eines Speicherbereiches von der Länge "length" als Hexadzimalzahlen ausgeben
For i As Integer=0 To length-1
printf(Hex(p[i]))
printf(" ")
Next
printf(!"\n")
End Sub
Function numerisch(s As String) As Integer 'ist s eine Zahl? +0 und -0 werden als ungültig angesehen
Return Val(s)<> 0 Or Asc(s)=Asc("0")
End Function
Zusätzliche Informationen und Funktionen |
- Das Code-Beispiel wurde am 30.06.2012 von theta angelegt.
- Die aktuellste Version wurde am 09.05.2013 von theta gespeichert.
|
|