QBasic: CALL ABSOLUTE
« | 26 Mar 2023 | »Als ich in den 90ern meine DOS und
QBASIC-Babyschritte machte, lernte ich,
dass “Systemroutinen” (auch als “Software-Interrupts” bekannt) mit QBasic
nicht möglich waren, jedoch in QuickBasic 4.5+ unterstützt wurden.
Es gab jedoch einen dunklen Hack, mit dem auch das kostenlose QBasic
“mächtiger” werden konnte … doch mir war das damals leider nicht bewusst.
Die Compiler-Sprachen von damals (wie Turbo-C oder
Turbo-Pascal)
ließen sowohl inline-Assembler
wie auch Interrupt-Aufrufe zu.
Doch dem QBasic-Interpreter fehlten solche Mechanismen.
Nur das kommerzielle QuickBasic bot eine Interrupt-Bibliothek an,
um Systemroutinen aufrufen zu können.
Doch QBasic und mache seiner Vorfahren gestatteten es, Bytes im Speicher
direkt zu setzen. Und so konnten kleine Programmteile manuell in den
Speicher geschrieben werden.
Und mit der Anweisung CALL ABSOLUTE konnte man eine beliebige
Speicheradresse einfach anspringen um den dortigen Code auszuführen.
X86-Assembly Code dynamisch mit QBASIC kompilieren
Wenn man weiß wie, ist es einfach:
Man allokiert ein Array in QBASIC, schreibt den korrektn Maschinencode
mit POKE Aufrufen in das Array, holt sich dan mit VARPTR und
VARSEG die Adresse und lässt CALL ABSOUTE den Code ausführen.
In meinem Beispiel im Classroom unter
basic/call_absolute/callabs.bas
sieht man aber, dass das in QBASIC recht mühsam sein kann.
In Assembler würde das Unterprogramm etwa so aussehen:
1push bp ; prolog 2mov bp, sp 3 4mov ax, ds ; DS segment auf stack sichern 5push ax 6mov ax, es ; ES segment auf stack sichern 7push ax 8mov ax, [my_ds] ; neues DS segment laden 9mov ds, ax 10mov ax, [my_es] ; neues ES segment laden 11mov es, ax 12 13mov ax, [my_ax] ; gewünschtes AX laden 14mov bx, [my_bx] ; gewünschtes BX laden 15mov cx, [my_cx] ; gewünschtes CX laden 16mov dx, [my_dx] ; gewünschtes DX laden 17mov si, [my_si] ; gewünschtes SI laden 18mov di, [my_di] ; gewünschtes DI laden 19 20int intNum ; interrupt aufrufen 21 22pop ax ; gesichertes ES segment zurückholen 23mov es, ax 24pop ax ; gesichertes DS segment zurückholen 25mov ds, ax 26 27pop bp ; epilog 28ret
Doch in QBASIC ist das Befüllen eines Arrays mit Byte-Werten ein Krampf:
1DIM op(1 TO 64) AS INTEGER 2DIM i AS INTEGER 3 4i = 1 5REM ---- jeder Menge Code davor ---- 6 7REM ---- MOV AX, (regs.ax) 8op(i) = &HB8: i = i + 1 9op(i) = regs.ax AND &HFF: i = i + 1 10op(i) = (regs.ax \ 256) AND &HFF: i = i + 1 11 12REM ---- MOV BX, (regs.bx) 13op(i) = &HBB: i = i + 1 14op(i) = regs.bx AND &HFF: i = i + 1 15op(i) = (regs.bx \ 256) AND &HFF: i = i + 1 16 17REM ---- MOV CX, (regs.cx) 18op(i) = &HB9: i = i + 1 19op(i) = regs.bx AND &HFF: i = i + 1 20op(i) = (regs.bx \ 256) AND &HFF: i = i + 1 21 22REM ---- MOV DX, (regs.dx) 23op(i) = &HBA: i = i + 1 24op(i) = regs.dx AND &HFF: i = i + 1 25op(i) = (regs.dx \ 256) AND &HFF: i = i + 1 26 27REM ---- INT num 28op(i) = &HCD: i = i + 1 29op(i) = intNum: i = i + 1 30 31REM ---- jede Menge Code danach ---- 32 33DIM asmFunc(1 TO 64) AS INTEGER 34asmFuncAddr = VARPTR(asmFunc(1)) 35DEF SEG = VARSEG(asmFunc(1)) 36 37REM ---- INT-Array-Werte zu echtem Bytecode übertragen 38FOR n = 0 TO i - 1 39 POKE (asmFuncAddr + n), op(n + 1) 40NEXT n 41 42CALL ABSOLUTE(VARPTR(asmFunc(1)))
Ich setze also einen Eintrag von op(i) auf eine CPU-Instruktion, und
dann folgt immer ein i = i + 1 …
Oh Mann, da lernt an C echt wieder lieben, wo es einfach heißt:
op[i++] = 0xab;
Vorsicht beim Hin- und Rücksprung
Es gibt ja mehrere ret Varianten, wir brauchen hier Opcode CB für
einen far-return.
Und auch CALL ABSOLUTE funktioniert nur dann, wenn in der Zeile
das ganze
CALL ABSOLUTE(VARPTR(asmFunc(1))) steht.
Mit dem vorherigen asmFuncAddr klappt es nicht.
Fazit
Genau so eine Funktion hatte mir damals gefehlt, um per DOS das Dateisystem
zu durchforsten oder per INT 10h und INT 13h Grafikkarten und
Disketten zu kontrollieren.
All das gelang mir am Ende mit Turbo Pascal etwas später.
Aber Schade, dass mir diese Welt nicht gleich in QBASIC offen stand.
Faszinierend:
Eine Programmidee von mir nach über 30 Jahren fertiggestellt.
Das kann nicht mal Duke Nukem toppen.