QBasic: CALL ABSOLUTE

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.

📧 📋 🐘 | 🔗 🔔
 

Meine Dokus über:
 
Weitere externe Links zu:
Alle extern verlinkten Webseiten stehen nicht in Zusammenhang mit opengate.at.
Für deren Inhalt wird keine Haftung übernommen.



Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen