Buchempfehlung
Windows-Programmierung. Das Entwicklerhandbuch zur WIN32-API
Windows-Programmierung. Das Entwicklerhandbuch zur WIN32-API
"Der" Petzold, das über 1000 Seiten starke Standardwerk zum Win32-API - besonders nützlich u. a. bei der GUI-Programmierung in FreeBASIC! [Mehr Infos...]
FreeBASIC-Chat
Es sind Benutzer im FreeBASIC-Chat online.
(Stand:  )
FreeBASIC bei Twitter
Twitter FreeBASIC-Nachrichten jetzt auch über Twitter erhalten. Follow us!

Code-Beispiel

Code-Beispiele » Sonstiges

Primitive Maschinencodegenerierung

Lizenz:Erster Autor:Letzte Bearbeitung:
LGPLMitgliedtheta 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 Externer Link!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 Externer Link!"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 Mitgliedtheta angelegt.
  • Die aktuellste Version wurde am 09.05.2013 von Mitgliedtheta gespeichert.
  Bearbeiten Bearbeiten  

  Versionen Versionen