Hi,
ich möchte mit dem bcc-compiler MSDOS-COM Programme erzeugen. Das
funktioniert so halb...
Testen tu ich das Ganze mit dosbox auf linux.
Dieses Programm hängt die dosbox in einer Endlosschleife auf, scheint
also geladen zu werden und zu laufen:
1
intmain(){
2
for(;;);
3
}
Disassembly:
1
00000000 55 push bp
2
00000001 89E5 mov bp,sp
3
00000003 57 push di
4
00000004 56 push si
5
00000005 EBFE jmp short 0x5
6
00000007 5E pop si
7
00000008 5F pop di
8
00000009 5D pop bp
9
0000000A C3 ret
10
0000000B 00 db 0x00
(Ich frage mich, warum für eine Endlosschleife so viel gesichert wird -
beim ret wird er auch nie ankommen, aber das ist wohl Compiler-Magie...)
Das jmp short dürfte nach JMP_ADDRESS + 2 + ZWEITES_BYTE = ZIEL wieder
bei 5h landen.
Diese Programme hier tun jedoch gar nichts, sie sollen einmal per BIOS
INT und einmal per DOS INT Zeichen ausgeben. Ich tippe test.com in der
dosbox und ich lande sofort wieder beim Prompt, ohne das sich was tat.
DOS INT:
1
intmain(){
2
asm("mov ah, 0x02");// ASCII 127 an Cursor schreiben
3
asm("mov dl, 127");
4
asm("int 0x21");
5
}
Disassembly:
1
00000000 8A260200 mov ah,[0x2]
2
00000004 8A167F00 mov dl,[0x7f]
3
00000008 CD21 int 0x21
4
0000000A C3 ret
5
0000000B 00 db 0x00
BIOS INT:
1
intmain(){
2
asm("mov ah, 0x07");
3
asm("mov al, 127");
4
asm("mov bh, 0");
5
asm("mov cx, 1");
6
asm("int 0x10");
7
}
1
00000000 8A260700 mov ah,[0x7]
2
00000004 A07F00 mov al,[0x7f]
3
00000007 8A3E0000 mov bh,[0x0]
4
0000000B 8B0E0100 mov cx,[0x1]
5
0000000F CD10 int 0x10
6
00000011 C3 ret
7
00000012 0000 add [bx+si],al
und hier frage ich mich, was die zwei 0-Bytes am Ende sollen.
So wie ich das verstanden habe, wird eine COM-Datei nach 0x100 geladen
und von dort ausgeführt, aber in welchem Code-Segment? Das
Compiler-Modell hab ich als tiny angegeben, sodass alles im selben
Segment liegt.
Wieso werden die INTs nicht ausgeführt?
Ich hoffe, da findet sich noch jemand, der sich ein wenig auskennt.
Nun bcc beziehungsweise der linker erzeugt eine exe keine com Datei.
Du mußt die exe deshalb konvertieren. Früher machte man das mit exe2bin
oder ähnlichen Programmen.
Thomas
Nils S. schrieb:> (Ich frage mich, warum für eine Endlosschleife so viel gesichert wird -> beim ret wird er auch nie ankommen, aber das ist wohl Compiler-Magie...)
Der Compiler wird einfach keine so gute Optimierung haben wie
das, was du von GCC oder Clang kennst.
> DOS INT:int main() {> asm("mov ah, 0x02"); // ASCII 127 an Cursor schreiben> asm("mov dl, 127");> asm("int 0x21");> }> Disassembly:00000000 8A260200 mov ah,[0x2]> 00000004 8A167F00 mov dl,[0x7f]
Da stehen im Disassembly Indirektklammern.
Kann es sein, dass du das so schreiben müsstest?
1
asm("mov ah, #0x02");// ASCII 127 an Cursor schreiben
Thomas, ich hab' vergessen, dazuzusagen, dass bcc nicht der Borland C
Compiler ist, sondern:
https://linux.die.net/man/1/bcc>-Md>alters the arguments for all passes to produce MSDOS executable COM files.>These are small model executables, use -i to get tiny model.>Wie bist du an dein disassembly gekommen.
$ ndisasm test.com
Im Anhang eine test.com mit diesem Code:
1
intmain(){
2
asm("mov ah, 0x07");
3
asm("mov al, 127");
4
asm("mov bh, 0");
5
asm("mov cx, 1");
6
asm("int 0x10");
7
}
>Normalerweise werden COM an Adresse CS:0100 geladen.
CS kommt woher? Lasse ich Code aus dem Bootsektor ausführen oder im
option-rom, ist das klar. Nur unter DOS... übersehe ich was? Denkfehler
oder noch nicht gefunden?
> Kann es sein, dass du das so schreiben müsstest?> asm("mov ah, #0x02"); // ASCII 127 an Cursor schreiben> asm("mov dl, #127");
Dann hab' ich das Ergebnis vom Screenshot. Disasm:
>00000000 B407 mov ah,0x7>00000002 B07F mov al,0x7f>00000004 B700 mov bh,0x0>00000006 B90100 mov cx,0x1>00000009 CD10 int 0x10>0000000B C3 ret
Nun scheint er aber was auszuführen und ich schau' mir das gleich mal
genauer an. Sieht auf jeden Fall schonmal sehr gut aus.
Schonmal vielen Dank!
Nils S. schrieb:> ich möchte mit dem bcc-compiler MSDOS-COM Programme erzeugen. Das> funktioniert so halb...> Testen tu ich das Ganze mit dosbox auf linux.> Dieses Programm hängt die dosbox in einer Endlosschleife auf, scheint> also geladen zu werden und zu laufen:>
1
>intmain(){
2
>for(;;);
3
>}
4
>
> Disassembly:>
1
> 00000000 55 push bp
2
...
3
> 0000000B 00 db 0x00
4
>
> (Ich frage mich, warum für eine Endlosschleife so viel gesichert wird -> beim ret wird er auch nie ankommen, aber das ist wohl Compiler-Magie...)> Das jmp short dürfte nach JMP_ADDRESS + 2 + ZWEITES_BYTE = ZIEL wieder> bei 5h landen.
Das liegt wohl an der C Calling Convention, der Stapel wird zur
Parameterübergabe verwendet und grundsätzlich eingerichtet.
> Diese Programme hier tun jedoch gar nichts, sie sollen einmal per BIOS> INT und einmal per DOS INT Zeichen ausgeben. Ich tippe test.com in der> dosbox und ich lande sofort wieder beim Prompt, ohne das sich was tat.> DOS INT:>
1
>intmain(){
2
>asm("mov ah, 0x02");// ASCII 127 an Cursor schreiben
3
>asm("mov dl, 127");
4
>asm("int 0x21");
5
>}
6
>
> Disassembly:>
1
> 00000000 8A260200 mov ah,[0x2]
2
...
3
> 0000000B 00 db 0x00
4
>
Das sollte eigentlich funtionieren, probier es doch lieber mit 0x41 dann
sollte ein 'a' ausgegeben werden.
> BIOS INT:>
1
>intmain(){
2
>asm("mov ah, 0x07");
3
>asm("mov al, 127");
4
>asm("mov bh, 0");
5
>asm("mov cx, 1");
6
>asm("int 0x10");
7
>}
8
>
>
1
> 00000000 8A260700 mov ah,[0x7]
2
...
3
> 00000011 C3 ret
4
> 00000012 0000 add [bx+si],al
5
>
> und hier frage ich mich, was die zwei 0-Bytes am Ende sollen.
Von den ehemals vielen VGA Interrupts (0x10) unterstützen moderne
Graphikkarten nur noch die die man immer braucht (Videomodus auswählen,
CLS, usw.) Interrupts zur Semigraphik im Textmodus und andere selten
gebräuchliche sind fortgefallen, das hat nix mit dem Betriebssystem zu
tun sondern mit der Graphikkarte.
> So wie ich das verstanden habe, wird eine COM-Datei nach 0x100 geladen> und von dort ausgeführt, aber in welchem Code-Segment? Das> Compiler-Modell hab ich als tiny angegeben, sodass alles im selben> Segment liegt.
Eine .com Datei beschränkt sich idR auf nur 64kB und Code und Daten
liegen im selben Segment CS = DS = ES = SS
> Wieso werden die INTs nicht ausgeführt?
Lässt die Einstellung der DOS Emulation unter Linux denn die Ausführung
der Interrupts zu?
Nun es fehlt dann immer noch ein int20 oder ähnliches um das Programm zu
beenden.
Ohne Prorammende kehrt das Programm nicht mehr zum Aufrufer
(command.com) zurück.
Ich kenn jetzt den Compiler nicht aber com werden immer ab Offset 100
geladen. Davor ist der PSP. Die Segmente stellt der Programm Loader ein.
Im PSP sind Aufruf und Rückkehr Parameter sowie Pfad und Prog Parameter
codiert.
Thomas
Erstens:
Deine Adressen sehen seltsam aus.
Die erste Instruktion sollte an Adresse 0x100 stehen, nicht bei Null.
Zweitens:
COM-Dateien bestehen nur aus einem Segment (CS=DS=ES=SS) und können
daher von jedem Segment aus ausgeführt werden. Das benutzte Segment wird
von DOS festgelegt.
Code vom BIOS wird hingegen an die absolute Adresse 0x7C00 geladen,
wobei dem BIOS die Wahl des Segments frei steht (0x0000:0x7C00,
0x07C0:0x0000 oder 0x0240:0x5800 sind alle zulässig).
Drittens:
Erzeugt der bcc irgendwelchen Startup-Code für DOS? Wenn nicht, darf
main() nicht zurückkehren, sondern dein Programm muss mit "INT 0x21,
AH=0x4C" (DOS 2.0+) oder INT 0x20 (CP/M-Kompatiblitätsinterrupt) enden.
Viertens:
Moderne Grafik-BIOSse unterstützen die meisten VGA-Interrupts aus
Kompatiblitätsgründen nach wie vor, denn sonst würden System-BIOS oder
Bootloader sich selbst nicht anzeigen können.
Fünftens:
Was Linux oder das Grafik-BIOS können, spielt schlicht keine Rolle, da
DOSBox sowohl CPU, Grafikkarte und auch das Betriebssystem emuliert (man
kann aber DOS booten).
Sechstens:
Beachte, dass DOSBox auf gemounteten Laufwerken die Verzeichnisinhalte
cacht. Wenn du also von Linux aus etwas änderst, solltest du vorher ein
RESCAN ausführen.
> Deine Adressen sehen seltsam aus.> Die erste Instruktion sollte an Adresse 0x100 stehen, nicht bei Null.
Aber eine com-Datei hab doch keine 256bytes 0-offset, sie wird nach 100
geladen.
> Zweitens:> COM-Dateien bestehen nur aus einem Segment (CS=DS=ES=SS) und können> daher von jedem Segment aus ausgeführt werden. Das benutzte Segment wird> von DOS festgelegt.
Das entspricht dem Tiny-Modell, woher nun CS kommt, war mir nicht ganz
klar. Danke.
> Drittens:> Erzeugt der bcc irgendwelchen Startup-Code für DOS? Wenn nicht, darf> main() nicht zurückkehren, sondern dein Programm muss mit "INT 0x21,> AH=0x4C" (DOS 2.0+) oder INT 0x20 (CP/M-Kompatiblitätsinterrupt) enden.
Nein, die Disassemblys sind die kompletten Binaries. Eine habe ich
angehangen.
Das Ziel des ganzen ist ein option ROM, welches ausschliesslich
BIOS-INTs verwenden soll. Testen will ich das ganze aber in der dosbox,
da ich auch von DOS aus da hin hüpfen können will. Gibt's eine
Möglichkeit zur Laufzeit ein laufendes/nicht laufendes DOS zu erkennen?
Das müsste in der init-routine des option-roms passieren, also kurz nach
POST. Demnach ist auch noch kein DOS geladen.
Signatur im Speicher suchen? int 0x21 ausführen und schauen ob es
klappte oder ein Fehler auftrat?
> Sechstens:> Beachte, dass DOSBox auf gemounteten Laufwerken die Verzeichnisinhalte> cacht. Wenn du also von Linux aus etwas änderst, solltest du vorher ein> RESCAN ausführen.
Zum testen geht jedes mal eine neue auf. F8 führt make aus, F5 make
test, was die dosbox öffnet und das Programm startet.
RESCAN kannte ich aber noch nicht, das ist auch praktisch.
Thomas schrieb:> Nun es fehlt dann immer noch ein int20 oder ähnliches um das Programm zu> beenden.> Ohne Prorammende kehrt das Programm nicht mehr zum Aufrufer> (command.com) zurück.
Ein simples Programm, was nur aus NOP und RET besteht, funktioniert und
kehrt auch zur command.com zurück.
Du hast Dos nicht verstanden. Sobald du Ints verwendest ist das eben
kein simples Programm mehr.der int20 macht auch Aufräumarbeiten. Oder
warum glaubst du dass es nicht funktioniert?
Setze einfach mal einen int20 an das Programm Ende.
Thomas.
Nils S. schrieb:>> Deine Adressen sehen seltsam aus.>> Die erste Instruktion sollte an Adresse 0x100 stehen, nicht bei Null.>> Aber eine com-Datei hab doch keine 256bytes 0-offset, sie wird nach 100> geladen.
Nun, dann solltest du das dem Disassembler auch mitteilen.
>> Drittens:>> Erzeugt der bcc irgendwelchen Startup-Code für DOS? Wenn nicht, darf>> main() nicht zurückkehren, sondern dein Programm muss mit "INT 0x21,>> AH=0x4C" (DOS 2.0+) oder INT 0x20 (CP/M-Kompatiblitätsinterrupt) enden.> Nein, die Disassemblys sind die kompletten Binaries. Eine habe ich> angehangen.
Faustregel: Wenn du ein DOS-Programm baust, dann sollte das auch ein
DOS-Programm sein und wie ein DOS-Programm enden.
Der 8086 hat keinen Speicherschutz, also darfst du dein Programm auch
gerne als Loader für dein "Option ROM" entwerfen. Der darf auch gerne
mit "INT 0x21 / AH=4Ch" enden, wenn das Option ROM fertig ist.
Option ROMs enden übrigens mit "INT 0x18" oder "INT 0x19", je nachdem,
ob die Bootkette weiterlaufen oder enden soll. Der DOS-Loader darf
diesen Interrupt gerne abfangen, denn so funktionieren TSRs.
> Das Ziel des ganzen ist ein option ROM, welches ausschliesslich> BIOS-INTs verwenden soll. Testen will ich das ganze aber in der dosbox,> da ich auch von DOS aus da hin hüpfen können will.
Dann implementiere ein Option ROM und nimm einen Emulator, der Option
ROMs ausführen kann (z.B. Qemu). Oder implementiere ein DOS-Programm,
welches das Option ROM in einer Umgebung ausführen kann, die der realen
Umgebung ungefähr entspricht. DOS ist erstmal keine solche.
> Gibt's eine> Möglichkeit zur Laufzeit ein laufendes/nicht laufendes DOS zu erkennen?
Wozu? Wenn du das Option ROM von DOS aus aufrufst, dann weißt du, dass
DOS läuft und kannst deine eigene Signatur hinterlegen. Einfach eine
DOS-Funktion aufrufen wird crashen, wenn DOS nicht da ist.
> Ein simples Programm, was nur aus NOP und RET besteht, funktioniert und> kehrt auch zur command.com zurück.
Ja, weil die Entwickler vom DOSBox-DOS nett waren und dir einen Sprung
nach CS:0000 auf den Stack gelegt haben (am Anfang des PSP steht ein
"INT 0x20").
Das komplette Programm
S. R. schrieb:> Dann implementiere ein Option ROM und nimm einen Emulator, der Option> ROMs ausführen kann (z.B. Qemu). Oder implementiere ein DOS-Programm,> welches das Option ROM in einer Umgebung ausführen kann, die der realen> Umgebung ungefähr entspricht. DOS ist erstmal keine solche.
Das mache ich auch mit qemu. Jedoch brauche ich auch einen Loader dafür,
der von DOS aus rennt. Das meiste vom Code sollte in das ROM wandern. So
stell ich mir das ungefähr vor:
1
rom_init_while_post:
2
...
3
; tastendruck? => jmp anwendung_entry
4
; boot prozess gleich fortsetzen
5
int 0x18/0x19
6
ret
7
8
9
anwendung_entry:
10
; läuft dos? flag setzen
11
; kein dos? dann anwendung in ram laden
12
...
13
; kein dos? free ram, zustand für bootprozess wiederherstellen
14
; ende mit 0x21 wenn dos
15
ret
Thomas schrieb:> Du hast Dos nicht verstanden. Sobald du Ints verwendest ist das eben> kein simples Programm mehr.der int20 macht auch Aufräumarbeiten. Oder> warum glaubst du dass es nicht funktioniert?> Setze einfach mal einen int20 an das Programm Ende.
Ein int 0x20 und int 0x24/AH=4C ändert nichts am Fehlverhalten wie auf
dem screenshot zu sehen.
Der int 10 wird ja nach dem Beenden ausgeführt, ist vom BIOS und
zumindest bis zum Zeichen ausgeben müsste alles gehen. Der Fehler liegt
also noch woanders.
z.B. Fehler wie im Screenshot (disasm origin mitgeteilt diesmal):
>00000100 B407 mov ah,0x7>00000102 B07F mov al,0x7f>00000104 B700 mov bh,0x0>00000106 B90100 mov cx,0x1>00000109 CD10 int 0x10>0000010B B44C mov ah,0x4c>0000010D CD21 int 0x21>0000010F C3 retS. R. schrieb:> Einfach eine> DOS-Funktion aufrufen wird crashen, wenn DOS nicht da ist.
An den Crash dachte ich und daran, den Fehler abzufangen. z.B. wie auf
nicht vorhandene OpCodes zu testen (z.B. wie bei 286/386 Unterscheidung
anhand des Befehlssatzes).
Aber selbst die Signatur zu hinterlegen oder einen zweiten
Einsprungpunkt zu bauen, scheint einfacher zu sein.
S. R. schrieb:> Ja, weil die Entwickler vom DOSBox-DOS nett waren und dir einen Sprung> nach CS:0000 auf den Stack gelegt haben (am Anfang des PSP steht ein> "INT 0x20").
Was würde denn sonst passieren? (auch @Thomas)
Das ret läuft ins Nichts, weil er die falsche Rücksprungadresse bekam?
Ich denke, ich werde mir da jetzt erstmal was mit Borland C oder tasm
bauen, dann versuche ich das auf einer anderen Plattform hinzubekommen.
Wurmt aber schon ein bisschen...
Nils S. schrieb:> Jedoch brauche ich auch einen Loader dafür, der von DOS aus rennt.> Das meiste vom Code sollte in das ROM wandern. So> stell ich mir das ungefähr vor:
Warum soll das Option-ROM irgendwelche Erkennungen machen? Baue doch
lieber den Aufruf des BIOS nach:
- Loader: 0x100 bis 0x400 (also eine normale COM-Datei)
- OptionROM: 0x400 bis (whatever), aber mit ORG 0x0000 verziert.
Ablauf:
- (optional: Magic Number und Checksumme prüfen und evtl. abbrechen)
- Hook auf INT 0x18/0x19 platzieren
- FAR CALL nach (CS+0x40):0x0003
- bei Rückkehr Fehlermeldung ausgeben
Wenn ein INT 0x18/0x19 ankommt:
- Hook wieder entfernen
- INT 0x21/0x4C
Fertig. Damit bildet der Loader einfach das BIOS nach und muss über die
Struktur des ROMs kein weiteres Wissen haben.
Das Option ROM kann anhand seiner eigenen Adresse unterscheiden, ob es
vom Loader geladen wurde (CS < 0xC800) oder nicht.
>> Einfach eine DOS-Funktion aufrufen wird crashen, wenn DOS nicht da ist.> An den Crash dachte ich und daran, den Fehler abzufangen.
Schlechte Idee. Ein 8086/8088 hat kein "Invalid Opcode".
> S. R. schrieb:>> Ja, weil die Entwickler vom DOSBox-DOS nett waren und dir einen Sprung>> nach CS:0000 auf den Stack gelegt haben (am Anfang des PSP steht ein>> "INT 0x20").> Was würde denn sonst passieren? (auch @Thomas)> Das ret läuft ins Nichts, weil er die falsche Rücksprungadresse bekam?
Ja. Dass der Stack immer auf einen INT 0x20 zeigt, ist ein
Kompatiblitätsartefakt von CP/M und funktioniert nur, wenn CS seit dem
Programmstart nicht verändert wurde. Unter DOS 2.0 und höher empfiehlt
Microsoft den INT 0x21/0x4C.
Ich baue sowas gerade. BIOS Extension wird via int 19 geladen.In meinem
Fall ab 0xe000:0
Das BIOS checked die Quersumme der ersten 4 Paras und startet dann mit
einem Far Ret
Mein Programm ab E002:0 Dort steht mein modifizierter startupcode mit
dem Label arcuse.
Ab diesem Zeitpunkt kann dann in C programmiert werden. Ich verwende
allerdings die Borland Tools. Der einzige Unterschied ist dass ich
meinen Code nach dem linken durch einen selbstgeschriebenen locator
schicke. Dieser macht aus der Exe ein binary indem er den exeheader
auflöst. Als Debugger verwende bochs bzw TD wenn ich int 3 exceptions
bekomme.
Mein Ziel ist es auf einem NEC V40 nativ dem MFA Monitor / Assembler zu
starten.
MFA ist ein altes 8085 Lernsystem. Die NEC V Serie kann nativ 8085 Code
ausführen.
Thomas
Laut Dokumentation sollte, wenn dein ROM ab E000:0 steht, der
Einsprungpunkt E000:2 sein. Außerdem sollte das BIOS, wenn die Größe im
Header korrekt angegeben ist, die Checksumme über das gesamte ROM prüfen
und nicht nur über die ersten Parameter.
Laut Dokumentation kann der V40 nur 8080-Code nativ ausführen, nicht
8085-Code (die scheinen aber nahezu identisch zu sein). Ansonsten ist
das irgendwie auch eine coole Idee, einen Hypervisor für CP/M zu bauen.
:-)