Visual Basic und Assembler - Seite 4
von Udo Schmidt
Kleine Helferlein
MASM ermöglicht das Erzeugen von Makros ebenso wie das Ersetzen von Programmcode durch Konstanten. Ersetzungen können in eine Datei ausgelagert und in Programme per INCLUDE-Anweisung eingebunden werden - das erspart viel Schreibarbeit!
Programmein- und ausstieg (Beispielmakro)
Programmeinstieg und -ausstieg sind stets gleich. Es werden Register gesichert und wiederhergestellt sowie zum aufrufenden Programm zurückgekehrt, wobei die von CallWindowProc übergebenen 4 Parameter vom Stapel zu löschen sind. Um dies nicht immer wieder schreiben zu müssen, ist es sinnvoll, dafür Makros anzulegen.
mac MACRO MacName:req, INT3:=<1> IF @Instr(1,<MacName>,<SAVE>) GT 0 IF INT3 int 3 ENDIF push ebp mov ebp, esp push ebx push esi push edi ELSEIF @Instr(1,<MacName>,<RESTSP>) GT 0 lea esp,[ebp-12] sbb eax,eax pop edi pop esi pop ebx pop ebp ret 16 ELSEIF @Instr(1,<MacName>,<REST>) GT 0 sbb eax,eax pop edi pop esi pop ebx pop ebp ret 16 ELSE .err <Unknown command <MacName>> ENDIF ENDM
Eine ausführliche Erläuterung zu den einzelnen Codeabschnitten ist im Worddokument enthalten (siehe Download).
Parameterdefinition/Lokale Variablen
Da die von CallWindowProc übergebenen vier Parameter stets an derselben Stelle zu finden sind, bietet es sich der besseren Lesbarkeit wegen an, für [EBP+x] Code-Ersetzungen zu benutzen:
PA1 EQU <[ebp+8]> PA2 EQU <[ebp+12]> PA3 EQU <[ebp+16]> PA4 EQU <[ebp+20]>
Das EQU-Statement besagt: PA1/PA2 ... equals to ... Der nachfolgende Text ist von spitzen Klammern umgeben, wird also einfach als Ersetzungstext interpretiert und während der Assemblierung für PA1/PA2 ... eingesetzt. Im ASM-Code kann daher MOV EAX,PA1 anstelle von MOV EAX,[EBP+8] geschrieben werden. Von dieser Möglichkeit sollte viel Gebrauch gemacht werden, um den ASM-Text übersichtlich zu gestalten.
Dasselbe gilt für Lokale Variablen, also z.B. Integer, die im Programmverlauf erzeugt und bearbeitet werden: Wird ein Wert auf dem Stapel abgelegt, so steht er direkt unterhalb der gesicherten Register und kann daher per [EBP-x] angesprochen werden:
Off1 EQU 16 stack1 EQU <[ebp-Off1]> stack2 EQU <[ebp-Off1-4]> stack3 EQU <[ebp-Off1-8]> stack4 EQU <[ebp-Off1-12]> ... ...
Im ASM-Code kann dann stehen:
PUSH 10 ; stack1 => counter LoopAdr: ... ... DEC stack1 JNZ LoopAdr
Übernahme des Codes in VB
Am einfachsten kann Programmcode mit dem QEditor erstellt werden (gehört zum Lieferumfang von MASM), der zugleich menügesteuert EXE-Dateien generiert. Ein größeres Problem bereitet die aufrufbare Einbindung des EXE-Codes in ein VB-Programm. Um dies zu erleichtern, habe ich eine Routine geschrieben, die EXE-Dateien ausliest und VB-Code generiert, der die Binärwerte in einem Long-Array speichert.
Die Routine besteht aus zwei Funktionen:
EXE2ARR
Mit EXE2ARR werden das Auslesen der EXE-Datei und die Umwandlung in ein Long-Array bewerkstelligt (Beispiel 4).
EXE2ASM
Die Funktion EXE2ASM benutzt EXE2ARR, um das Long-Array zu erstellen, und erzeugt entsprechenden VB-Code, der in die Zwischenablage kopiert wird, von wo er an beliebiger Stelle im VB-Programm eingefügt werden kann (Beispiel 5).
Das Ergebnis sieht z.B. so aus:
Static asm(13) As Long ' c:\ \cpuwait.exe 13.09.03 23:00:08 (GMT) If asm(0) = 0 Then asm(0) = &HE0C1310F: asm(1) = &H244C8B03 asm(2) = &H89018904: asm(3) = &HC0330451 asm(4) = &H89184189: asm(5) = &H310F1C41 asm(6) = &H1184183: asm(7) = &H1C5183 asm(8) = &H511B012B: asm(9) = &H8418904 asm(10) = &H2B0C5189: asm(11) = &H511B1041 asm(12) = &HC2E37214: asm(13) = &H10 End If
In der Testphase eines Programms ist es natürlich lästig, nach jeder Änderung des ASM-Codes neuen VB-Code erzeugen und in den Programmtext einbinden zu müssen. Hier liegt der Grund für die Trennung oben gen. Funktionen. Während EXE2ASM geeignet ist, ausgetesteten Code in ein VB-Programm zu übernehmen, eignet sich EXE2ARR vor allem in der Testphase, weil die EXE-Datei bei jedem Aufruf erneut eingelesen wird, so dass Änderungen am ASM-Code sofort verfügbar sind:
Function Test() Dim asm() As Long If EXE2ARR("<filespec>", asm, , delInt3) = False Then Stop End Function
Hier wird bei jedem Aufruf der Funktion Test ein undimensioniertes Array vom Typ Long erzeugt und der Binärcode der Datei <filespec> in dieses Array geschrieben. Tritt beim Einlesen ein Fehler auf, stopt die Programmausführung, andernfalls kann mit CallWindowProc wie unten beschrieben verfahren werden.