Home

Zurück zur Assemblerauswahlseite

8086 Assembler (MS-DOS):
Beispiel für einen Befehlsprozessor (Shell)

sh.asm ist ein Übungsprogramm - es ist wunderbar zum Spielen und Lernen geeignet, aber sonst lässt sich damit wenig anfangen. Inhaltlich ist das Übungsprogramm eine leicht zu erweiternde Bedieneroberfläche. Man kann das Programm von der Windows-Oberfläche aus unmittelbar starten und als Bedieneroberfläche benutzen. Jedoch ist bereits in MS-DOS mindestens ein besser geeignetere Befehlsprozessor als Bedieneroberfläche enthalten.

Das Programm zeigt beispielhaft, wie ein Kindsprozess geladen und aufgerufen werden kann. Solche Beispiele sind hilfreich, denn nur die Kenntnis der Funktionsweise von Funktion 4Bh des Interrupts 21h ist für die erfolgreiche Programmierung eines Kindsprozessaufrufes unzureichend.

Andere Bezeichnungen für Bedieneroberflächen in diesem Zusammenhang sind „Shell” (englisch Schale, z.B. die Schale einer Nuss oder die Eischale), Befehlsprozessor oder neudeutsch zusammengemischt „Kommandointerpreter”.

Unter MS-DOS kommuniziert der Benutzer mit einem Befehlsprozessor wie COMMAND, CMD oder beispielsweise 4DOS. COMMAND und CMD werden von Microsoft mit dem Betriebssystem geliefert, 4DOS ist hier stellvertretend für einen alternativen Befehlsprozessor genannt.

Beim Start von MS-DOS wird eine Umgebungsblock (Environment Block) gefüllt. Sein erster Eintrag beginnt mit der Zeichenfolge „COMSPEC=”. Es folgt der Name der Kommandoprozessors mit seinem vollständigen Pfad. Mit dem Kommando „set” lässt sich der Inhalt des Umgebungsblocks anzeigen.

Die Funktion des ursprünglichen Programmes SHELL

In seiner Urausführung unterstützt das Programm genau drei sichtbare parameterlose Kommandos: CLS, DOS und EXIT. Wird ein davon abweichendes Kommando eingetippt, wird dies Kommando an den im Umgebungsblock eingetragenen Befehlsprozessor durchgereicht. Dieser Befehlsprozessor versucht, das Kommando auszuführen und gibt danach die Steuerung wieder an das Programm SHELL zurück.

Durchgeführte Änderungen

Das Quellprogramm in seiner ursprünglichen Fassung[250,Seite 199ff] ist 329  Zeilen lang. Weniger als 150 Zeilen davon sind erhalten geblieben.

Quellprogrammteile aus dem ursprünglichen Programm sind im weiter unten dargestellten Listing in durchgängiger Großschrift gehalten. Aus dem Kapitel „Introduction” und der Beschreibung der Funktion 4Bh des Interrupts 21h des oben genannten Buches ergibt sich, dass die Verwendung des im Buch dargestellten ursprünglichen Programmes als Grundgerüst für eigene Programmentwicklungen zulässig ist (Seite XI: Each entry includes a brief assembly-language program example that you can use as a skeleton for setting up your own calls und Seite 359: Example: See Chapter 10). Die von mir durchgeführten Änderungen, Ergänzungen und Funktionserweiterungen sind daran zu erkennen, dass auch kleine Buchstaben verwendet werden.

Die Logik zum Auswerten der Kommandozeile ist gegenüber dem Original nur wenig erweitert worden: Schrägstriche (/ Slash) werden ein Rückstriche (\ Backslash) gewandelt. Auch die Fehlerbearbeitung hat sich - abgesehen von Erweiterungen - nur wenig geändert.

  • In der Literatur wird geschrieben, dass „Prozeduren” in einem Prozedurrahmen codiert werden sollen, z.B.
    get_env        proc        near
    
            ...Befehle...
    
    getenv        endp
    
    Das mag für Unterprogramme in anderen Segmenten (proc far) gelten. Wenn das Unterprogramm sich jedoch im gleichen Segment wie das Hauptprogramm befindet und wenn außerdem Haupt- und Unterprogramm gemeinsam assembliert werden, ist die Einkleidung in einen Prozedurrahmen für mich nicht einsichtig. Ich habe somit sämtliche Prozedurdeklarationen aus dem Quellprogramm entfernt.

  • Einige einfache Makros sind hinzugekommen.

  • Einige bedingte Sprünge habe ich umcodiert. Das Programm ist dadurch geringfügig kürzer geworden.

  • Die Befehle eines nur einmal verwendete Unterprogramm („Prozedur”) wurden unmittelbar ins Hauptprogramm verlegt. Das Unterprogramm wurde entfernt. Dies bewirkt eine geringfügige Verkleinerung des Programms.

  • Die Sprungmarken (Labels) im Vorspannteil und im Hauptteil des Programmes habe ich umbenannt. Im Vorspann beginnen die Labelnamen nun mit „v” im Hauptteil mit „h”. Im Original des Programmes wurden an diesen Stellen Labelnamen verwendet, die an die ausgeführte Aktion angelehnt waren. Meine Umbenennungen erleichtern das Editieren des Quellprogramms am Bildschirm: Man erahnt leichter, ob ein im Editor gerade nicht sichtbares Label ober- oder unterhalb der gerade bearbeiteten Zeile zu suchen ist.

  • Die über Interrupt 21h Funktion 40h programmierten Bildschirmanzeigen habe ich umgestellt auf Funktion 2. Daraus ergaben sich einige Vereinfachungen des Programmes und das ausführbare Programm wurde 118 Bytes kürzer. Allerdings werden nun Fehlermeldungen und sonstige Ausgaben gemeinsam über den gleichen Handle ausgegeben. Im ursprünglichen Programm wurden Fehlermeldungen über Handle 2 und sonstige Meldungen über Handle 1 ausgegeben.

  • Im Programm nicht verwendete, aber dennoch eingetragenen Labels, wurden entfernt.

  • Hinzugekommen -
    • - sind alternative Kommandos wie „BYE” oder ”QUIT” für „EXIT”.

    • - ist die Möglichkeit, das Programm mit Parametern aufzurufen. Die Aufrufparameter werden wie eine erste eingetippte Befehlszeile verarbeitet.

    • - ist die Einzelbefehlsausführung. Endet die Befehlszeile mit einem Ausrufezeichen, so wird nach Abarbeitung des Befehls der Befehlsprozessor beendet. Somit lässt sich gezielt nur ein einziger Verarbeitungsbefehl ausführen.

    • - ist eine kleine Hilfsanzeige.
      D:\dos\ML\Test>sh /?
      
      Befehle:
      ? oder /?   Hilfsanzeige wie hier
      !, BYE, EXIT oder QUIT beendet diese Shell
      ! am Schluss des Befehls beendet diese Shell nach seiner
              Ausfuehrung.
              Beispiel fuer Aufruf mit Standardausgabeumleitung:
              >hugo.txt sh ls d:\temp\!
      25   setzt Bildschirmhoehe auf 25 Zeilen
      43   setzt Bildschirmhoehe auf 43 Zeilen
      CD   wechselt Verzeichnis und Laufwerk, z.B.
              CD C:/TEMP\HUGO oder CD ..
      CLS  loescht den Bildschirminhalt
      DOS  DOS-Kommandooberflaeche, beenden mit EXIT
      EXPL startet Windows-Explorer auf aktuellem Pfad
      LS   Directory-Anzeige aehnlich DIR
      PWD  zeigt aktuelles Verzeichnis (Print Working Directory)
      sh:>
      
    • - sind die Kommandos 25 und 43. Damit lässt sich die Zeilenanzahl des DOS-Fensters anpassen. Für die Realisierung der Änderung der Zeilenanzahl werden spezielle Funktionsaufrufe des Interrupts 10h für EGA- bzw. VGA-Karten verwendet. Sie sind beschrieben z.B. in Günter Born:Das MS-DOS-Programmierhandbuch MS-DOS 1.x - 6.x. Das Buch kann von www.borncity.de herabgeladen werden (Stand Februar 2011).

    • - ist das Kommando CD zum Wechseln des Verzeichnisses (mit Unterstützung langer Dateinamen).

    • - ist das Kommando EXPL. Es startet den Windows-Explorer auf dem aktuellen Pfad und ermöglicht somit z.B. einen raschen Zugang zum mit der Dateinamenserweiterung assoziierten Programm.

    • - ist das Kommando LS zum Anzeigen des Inhaltes von Verzeichnissen (mit Unterstützung langer Dateinamen).

    • - ist das Kommando PWD (Anzeige des aktuellen Pfades mit Unterstützung langer Dateinamen).


title 'sh kleine MS-DOS shell'
       .286c
comment #
----------------------------------------
Die Auswertung der angewaehlten Funtion basiert auf dem Programm SHELL.ASM
in Ray Duncan: Advanced MS-DOS'- The Microsoft guide for Assembly Language
and C programmers, Microsoft Press, (c)1986 by Ray Duncan. Das Programm
SHELL.ASM ist dort auf den Seiten 199 - 205 abgedruckt.

Nur die aus SHELL.ASM uebernommene Bestandteile sind in Grosschrift
gehalten.

Fredrik Matthaei, 28.Feb.2011
---------------------------------------- #

STDIN   EQU     0               ;Handle Standardeingabe

video   equ     10h
dos     equ     21h
lparm   equ     0080h   ;Parameterlaenge


false   equ     0
true    equ     not false

w       equ     word ptr
b       equ     byte ptr

tab     equ     9
spc     equ     ' '

cr      equ     0dh
lf      equ     0ah
escp    equ     1bh

int21   macro   intnr
        ifnb    <intnr>
        mov     ah,intnr
        endif
        int     dos
        endm

jmps    macro   to      ;;kurzer sprung
        jmp     short to
        endm


dbl     macro   text    ;;definiert eine zeile
        ifb     <text>
        db      cr
        else
        db      text,cr
        endif
        endm

dbz     macro   text    ;;definiert einen string mit 0 am ende
        ifb     <text>
        db      0
        else
        db      text,0
        endif
        endm

dblz     macro   text   ;;definiert eine zeile und 0 am ende
        ifb     <text>
        db      cr,0
        else
        db      text,cr,0
        endif
        endm


prstr   macro   texte    ;;print string
        call    prmsg
        dbz     <texte>
        endm

print_crlf macro
        call    $print_crlf     ;;ausgabe cr lf
        endm            ;;zum bildschirm

;----------------------------------------------

CSEG    SEGMENT PARA PUBLIC 'CODE'
        ASSUME  CS:CSEG,DS:DATA,SS:STACK
;==============================================
; Vorprogramm
;==============================================

_start:
        MOV     AX,DATA
        MOV     DS,AX

;--------------------------------
; Uebernahme von beim Programmstart mitgegebenen Parametern
; in den Eingabebereich inp_buf.
; Geschieht, solange Register ES noch auf den PSP-Bereich zeigt

        xor     ch,ch           ;im PSP auf Adr. 80h steht die Laenge
        mov     b cl,es:[0080h] ;der Programmaufrufparameter
        or      cl,ch
        jz      v10             ;kein Parameter wurde eingegeben

        mov     si,82h          ;ab Adr. 82h steht der Parametertext
        mov     bx,offset inp_buf
v05:
        mov     b al,es:[si]    ;Parameter zum Eingabepuffer
        inc     si
        mov     b ds:[bx],al
        inc     bx
        loopnz  v05

        mov     b merke,1       ;merken Parameteruebernahme

v10:
;--------------------------------
; Erst nach der Parameteruebernahme folgt die Hauptspeicherfreigabe
; und die weitere Programminitialisierung

        MOV     AX,ES:[002CH]   ;Segmentadr. des eigenen Environmentblockes
        MOV     ENV_SEG,AX
        MOV     BX,100H                 ;neue Blockgroesse 100h * 10h Bytes
        int21   04ah
        JNC     v15
        MOV     si,OFFSET MSG1          ;unable to de-allocate memory
        jmps    shell_abbruch

v15:                                    ;get the comspec
        MOV     SI,OFFSET COM_VAR
        CALL    GET_ENV
        JC      v25                     ;ggf. Fehlermeldung
        MOV     SI,OFFSET COM_SPEC_dos
v20:    MOV     AL,ES:[DI]              ;COMSPEC-Eintrag des Umgebungsblockes
        MOV     [SI],AL                 ;kopieren nach COM_SPEC_dos
        INC     SI
        INC     DI
        OR      AL,AL
        JNZ     v20
        jmps    v35

v25:    MOV     si,OFFSET MSG3          ;hier Fehlermeldung MSG3

;--------------------------------
; Programmabbruch mit Fehlermeldung

shell_abbruch:
        call    printa          ;Fehlermeldung zeigen
        MOV     AX,4C01H        ;Programmende mit Returncode 1
        int21

v35:    MOV     DX,OFFSET h00   ;Set CNTR-C vector
        MOV     AX,CS
        MOV     DS,AX
        MOV     AX,2523H        ;eigene Interruptroutine für Interrupt 23h
        int21                   ;Int 23h ist ein Unterprogramm(!) zur
        MOV     AX,DATA         ;Reaktion auf Break ist Lesen von
        MOV     DS,AX           ;Tastatur
        MOV     ES,AX

        print_crlf              ;Ausgabe cr einmalig bei Programmbeginn

        cmp     b merke,1       ;wurden Parameter uebernommen?
        je      h05             ;ja: erstes Kommando steht bereit
                                ;nein: lesen von Tastatur

;==============================================
; Hauptprogramm
;==============================================
; Verarbeitung der Kommandoeingabe. Die Kommandos werden
; von Tastatur eingelesen. Lediglich ein einziges Kommando
; kann (muss aber nicht) bei Progrtammstart als Parameter
; mitgegeben werden. Wurde beim Programmstart ein Parameter
; mitgegeben, so hat das Byte "merke" den Inhalt 1 (vergleiche
; die beiden letzten Befehle unmittelbar vor diesem Kommentar).
;
; Der Vektor fuer INT 23h wurde auf das Label h00 umgeleitet -
; somit laesst sich dies Programm nicht per CTRL-C - Tastenkombination
; abbrechen

h00:    cmp     merke_exit,'!'          ;forderte das letzte
        jne     h01                     ;Kommando EXIT an?
        JMP     exit_cmd                ;ja!

h01:    prstr   <CR,'sh:',3eh>
        MOV     DX,OFFSET INP_BUF
        MOV     CX,INP_BUF_LENGTH
        MOV     BX,STDIN                ;Lese von Tastatur
        int21   3fh
h05:    mov     schutzbyte,cr           ;Inhalt des Schutzbytes erneuern
        MOV     SI,OFFSET INP_BUF
        MOV     CX,INP_BUF_LENGTH
        CMP     b [SI],cr               ;cr kennezeichnet Zeilenende
        je      h00

;----------------------------------------------
; Verarbeitung des Kommandos. Buchstaben a bis z werden in Grossschrift
; gewandelt, / wird zu \

h10:
        cmp     b [si],cr
        je      h17             ;Vorzeitiges Abfrageende bei cr

        CMP     b [SI],'a'      ;Wandlung der Eingabe in Grossschrift
        jb      h12
        CMP     b [SI],'z'
        ja      h12
        SUB     b [SI],'a'-'A'
h12:    cmp     b[si],'/'
        jne     h15             ;/ wird \
        mov     b[si],'\'
h15:    INC     SI
        LOOP    h10

h17:
        dec     si             ;auf Zeichen vor dem cr
        cmp     b [si],'!'     ;wird EXIT angefordert?
        jne     h18            ;nein
        mov     merke_exit,'!' ;ja! Merken fuer naechste EXIT-Abfrage bei h00
        mov     b [si],cr      ;! im Eingabestring abschneiden

; Eingabestring in Kommandotabelle suchen
h18:    MOV     SI,OFFSET COMMANDS
h20:    CMP     b [SI],0                ;Eintragsende in Kommandotabelle
        JE      h50                     ;erreicht?
        MOV     DI,OFFSET INP_BUF       ;Ueberlesen fuehrende Blanks
h25:    CMP     b [DI],spc
        JNE     h30
        INC     DI
        jmps    h25
h30:    MOV     AL,[SI]         ;Eintragsende in Kommandotabelle erreicht?
        OR      AL,AL
        JZ      h35             ;ja
        CMP     AL,[DI]         ;nein. Dann Eingabezeichen=Kommandotabelle?
        JNZ     h45             ;nein
        INC     SI              ;ja. Dann ein Zeichen weiter
        INC     DI
        jmps    h30
h35:    CMP     b [DI],CR       ;Gleichheit. Ist hier Eingabeende?
        JE      h40             ;ja
        CMP     b [DI],spc      ;nein. Blank wuerde akzeptiert werden.
        JNE     h45
h40:    CALL    W [SI+1]    ;Aufruf des zum Kommando passenden Unterprogramms
        jmp     h00         ;Naechste Kommandoeingabe anfordern

h45:    LODSB
        OR      AL,AL           ;in Kommandotabelle auf naechsten Eintrag
        JNZ     h45
        inc     si
        inc     si
        jmps    h20

; Wenn Eintragsende in Kommandotabelle erreicht
h50:    CALL    ext     ;Send external command an Kommandointerpreter
        jmp     h00

STK_SEG DW      0       ;SS Sicherung Pointer auf Stacksegment
STK_PTR DW      0       ;SP Sicherung Stackpointer


;----------------------------------------------
; DOS Kommando zum Aufruf des Kommandointerpreters

DOS_CMD:
        MOV     PAR_CMD_dos,OFFSET NULTAIL
        MOV     DX,OFFSET COM_SPEC_dos  ;Pfad zum auszufuehrenden Programm
        MOV     BX,OFFSET PAR_BLK_dos
        jmps    ext5
;----------------------------------------------------
; Uebergabe der Kommandozeile an den Kommandointerpreter falls
; sh.exe das Kommando nicht verarbeiten kann.
;
ext:
        MOV     AL,CR                   ;sucht cr im String ES:DI
        MOV     CX,CMD_TAIL_dos_LENGTH
        MOV     DI,OFFSET CMD_TAIL_dos+1
        CLD
        REPNZ   SCASB
        MOV     AX,DI
        SUB     AX,OFFSET CMD_TAIL_dos+2
        MOV     CMD_TAIL_dos,AL
        MOV     PAR_CMD_dos,OFFSET CMD_TAIL_dos
ext5:
        MOV     DX,OFFSET COM_SPEC_dos  ;Pfad zum auszufuehrenden Programm
        MOV     BX,OFFSET PAR_BLK_dos
        call    execute
        JNC     ext9
        MOV     si,OFFSET MSG2          ;ggf. Fehlermeldung bzgl.
        call    printa                  ;Kommandointerpreter
ext9:
        ret

;----------------------------------------------
; MS-DOS EXEC-Aufruf zur Ausfuehrung des Kommandointerpreters
; als Kindprozess von SHELL.EXE
; DX und BX wurden vom aufrufenden Programm passend gefuellt.


execute:
        PUSH    DS
        PUSH    ES
        MOV     CS:STK_SEG,SS
        MOV     CS:STK_PTR,SP
        mov     ax,4b00h
        int21
        MOV     SS,CS:STK_SEG
        MOV     SP,CS:STK_PTR
        POP     ES
        POP     DS
RET

;----------------------------------------------
; Get the environment information

GET_ENV:
        MOV     ES,ENV_SEG
        XOR     DI,DI
GENV1:  MOV     BX,SI
        CMP     b ES:[DI],0
        JNE     genv3
        STC
genv2:  RET

genv3:  MOV     AL,[BX]
        OR      AL,AL
        JZ      genv2
        CMP     AL,ES:[DI]
        JNE     GENV4
        INC     BX
        INC     DI
        jmps    genv3


GENV4:  XOR     AL,AL
        MOV     CX,-1
        CLD
        REPNZ   SCASB
        jmps    GENV1

;==============================================
;Unterprogramme zum Abarbeiten der Kommandos
;==============================================

;----------------------------------------------
;CD Kommando wechselt aktuelles Verzeichnis
;Beim Aufruf zeigt DI auf das Blank hinter CD im Eingabebereich.
;Das Eingabeende wird durch CR markiert.
cd_cmd: mov     merke_pwd,0     ;noch keine PWD-Anzeige
cd02:   CMP     b [DI],spc      ;fuehrende blanks ueberlesen
        jne     cd10
        inc     di
        jmps    cd02
cd10:   mov     b AL,[DI]
        cmp     AL,cr   ;wurde ein Ziel angegeben?
        jne     cd15
        mov     si,offset msg5  ;Fehlerhinweis weil keine Zielangabe
        jmp     printa

;Pruefen auf Laufwerksangabe
cd15:   cmp     al,'A'  ;Buchstabe  gefolgt von : ?
        jb      cd20    ;nein
        cmp     al,'Z'
        ja      cd20    ;nein
        cmp     b [DI+1],':'    ;folgt : ?
        jne     cd20            ;nein

        and     al,1fh          ;ja. Dann Laufwerksbuchstaben
        sub     al,1            ;wandeln make binary
        mov     dl,al
        int21   0eh             ;Funktion select drive
        int     dos
        mov     merke_pwd,1
        inc     di              ;Zeiger auf :
        inc     di              ;Zeiger hinter :
cd18:   mov     al,[di]
cd20:   cmp     al,cr
        jne     cd21
        jmp     cd90            ;es war nur Laufwerkswechsel angefordert

;Pruefen auf ein Verzeichnis zurueck mit ..\
cd21:   cmp     al,'.'
        jne     cd50
        cmp     b [di+1],'.'
        jne     cd50
        cmp     b [di+2],'\'
        jne     cd23
        inc     di              ;Directorwechsel mit ..\ gewuenscht
cd23:   inc     di              ;Directorywechsel mit .. gewuenscht
        inc     di              ;Zeiger hinter ..\

        push    di
        mov     dx,offset cd_back      ;CD mit .. oder ..\ eine Stufe zurueck
        stc
        mov     ax,713bh        ;Funktion change Directory
        int     dos
        mov     merke_pwd,1
        jnc     cd25
        call    cd80            ;ggf. Fehlerhinweis

cd25:
        pop     di
        jmps    cd18            ;nochmal das gleiche?

;Fuer den Aufruf des Vezeichniswechsels muss der Eingabestring mit 0
;statt cr abgeschlossen werden.
cd50:   push    di
        cmp     b [di],cr       ;hier Stringende?
        je      cd90            ;dann fertig!

        cld                     ;Suchrichtung aufwaerts
        mov     cx,inp_buf_length-4     ;-4 wegen a:\ und einmal cr
        mov     al,cr
;       push    ds              ;diese push/pop-Kombination ist nicht
;       pop     es              ;erforderlich. Registe es steht bereits
        repne   scasb           ;passend


                                ;falls gefunden ist di um 1 Byte zu weit
        cmp     b es:[di-1],cr  ;cx auf 0 oder cr gefunden?
        mov     b es:[di-1],0   ;Befehlsabfolge wegen Stackausgleich
        pop     dx              ;anfang des Strings fuer CD-Kommando
        jne     cd80            ;cx auf 0 => Fehlermeldung

        stc
        mov     ax,713bh        ;Funktion change Directory
        int     dos
        mov     merke_pwd,1
        jnc     cd90

cd80:   mov     si,offset msg6          ;Fehlerhinweis CD konnte nicht
        call    printa                  ;ausgeführt werden



cd90:   cmp     merke_pwd,0     ;PWD-Anzeige nach CD-Ausfuehrung
        je      cd99
        call    pwd_cmd         ;Anzeige aktuelles Directory
cd99:   ret

;----------------------------------------------
;CLS Kommando loescht den Bildschirminhalt (ANSI.SYS erforderlich)

CLS_CMD:
        mov     si, offset cls_str
        jmp     printa

;----------------------------------------------
;VGA-Karte umschalten auf 25 oder 43 Textzeilen

;Die mit ;### auskommentierten Zeilen betreffen die sogenannte
;Cursoremulation im BIOS. Die Cursoremulation ermoeglicht eine
;unveranderte Darstellung des Cursors auch bei Aenderungen der
;Zeichenmatrix. Die Cursoremulation wird durch Bit 0 des
;Video Display Data Area Info Bytes (0040:0087) ein- oder
;ausgeschaltet. Fuer EGA und VGA ist die Cursoremulation bei
;Systemstart standardmaessig eingeschaltet. Fuer VGA soll dies Bit
;ueber INT 10h Funktion 12h geschaltet werden. Fuer EGA muss es
;durch direkten Zugriff geschaltet werden.

;Unter WINDOWS XP und VISTA scheint dies Bit keinen Einfluss mehr
;zu haben. Deshalb sind die betreffenden Befehle hier
;auskommentiert.


ega25:  mov     ax,0040h        ;25 Textzeilen
;###    push    ds
;###    mov     ds,ax
        mov     ax,3            ;EGA 80 * 25 Text, 16 Farben
        int     video           ;Modus setzen
;###    mov     bx,0087h        ;0040:0087 ist Video-Controller,
;###    xor     b ds:[bx],01h   ;umschalten cursor emulation
        mov     cx,0d0eh        ;Monochromcursor
        jmps    ega439

ega43:
        mov     ax,1202h        ;Video Funktion 12, Unterfunktion 2
        mov     bl,30h          ;(400 Rasterzeilen setzen)
        int     video

        mov     ax,0040h
;###    push    ds
;###    mov     ds,ax
        mov     ax,3            ;80 * 25 color text
        int     video           ;Modus setzen
        mov     ax,1112h        ;Zeichengenerator setzen
        xor     bx,bx
        int     video
;###    mov     bx,0087h        ;Video-Controller, Status-Byte 1
;###    or      b ds:[bx],01h   ;enable cursor emulation

        mov     ax,1200h        ;EGA/VGA use alternate print screen
        mov     bh,20h
        int     video
        mov     cx,0708h        ;Monochromcursor
ega439: mov     ah,1            ;Cursorgroesse setzen
        int     video
;###    pop     ds
        ret

;----------------------------------------------
; HELP Kommando - zeigt Hilfstext an.

help_cmd:
        mov     si,offset msg4
        jmp     printa

;----------------------------------------------
; EXPL Kommando zum Aufruf des Windows-Explorers auf das
; aktuelle Verzeichnis

expl_cmd:
        mov     par_cmd_expl, offset cmd_tail_expl
        MOV     DX,OFFSET COM_SPEC_expl ;Pfad zu explorer.exe
        MOV     BX,OFFSET PAR_BLK_expl
        call    execute
        JNC     expl_cmd1
        jmp     fehler
expl_cmd1:
        ret

;----------------------------------------------
; PWD Kommando  "Print Current Directory"

pwd_cmd:
        int21   19h              ;Funktion Get Current Disk
        add     al,41h           ;Laufwerknr. in ASCII wandeln
        mov     si,offset curlfn-3
        mov     b ds:[si],al
        inc     si
        mov     w ds:[si],5c3ah ;':\'
        add     si,2             ;si zeigt nun auf curlfn
        xor     dl,dl            ;aktuelles Laufwerk
        stc
        mov     ax,7147h         ;get current directory
        int21

        mov     si,offset curlfn-3      ;Anzeige aktuellen Pfad
        jmp     printa

;----------------------------------------------
;LS Kommando  Verzeichnisanzeige aehnlich DIR

;Beim Aufruf zeigt DI auf das Blank hinter dem Kommandowort
;LS im Eingabebereich. Das Eingabeende ist durch CR markiert.
ls_cmd: ;
        cmp     b [di],spc      ;fuehrende blanks ueberlesen
        jne     ls02
        inc     di
        jmps    ls_cmd

ls02:   mov     b AL,[DI]
        cmp     AL,cr           ;wurde ein Ziel angegeben?
        jne     ls05            ;ja

        mov     w es:[di],002ah ;Programmaufruf erfolgte ohne
        push    di              ;Parameter: Wir denken uns einen *
        jmps    ls20            ;Anfangsadresse der Suchmaske

;di zeigt nun auf den Anfang des eingetippten Parameters
ls05:   push    di              ;Anfangsadresse der Suchmaske
ls06:   cmp     b [di],cr       ;Abfrage auf Parameterende
        je      ls10
        inc     di
        jmps    ls06

;Parameterende erreicht. cr wird 0
ls10:   mov     b [di],0
        DEC     di
;wenn als letztes Zeichen ein Backslash steht, dann kommt ein * hinzu!
ls15:   cmp     b [di],'\'
        jne     ls20
        inc     di
        mov     w es:[di],002ah

ls20:   mov     cl,5eh          ;moegliche Attribute
        mov     ch,0            ;erforderliche Attribute

        pop     DX              ;Suchmaske ab di
        mov     si,1            ;altes Datumsformat
        mov     di,offset suchsatz

        stc                     ;Fehlermeldung falls MS-DOS die
        mov     ax,714eh        ;Funktion 71xx nicht unterstuetzt
        int21
        jnc     ls22
        jmps    fehler

ls22:   mov     handle,ax       ;handle sichern
        call    ls50

nochmal:
        mov     si,1            ;altes Datumsformat
        mov     di,offset suchsatz
        mov     bx,handle
        stc                     ;Fehlermeldung falls MS-DOS die
        mov     ax,714fh        ;Funktion 71xx nicht unterstuetzt
        int21
        jnc     ls24
        jmps    fehler

ls24:   call    ls50
        jmps    nochmal

;--------------------------------
;Fehlerbearbeitung und normales Programmende

fehler: cmp     ax,0012h        ;keine weitere Datei gefunden
        jne     fehler2
        ret

fehler2: cmp    ax,0002
        jne     fehler3
        mov     si, offset msg02
        jmps    hinweis

fehler3: cmp    ax,03
        jnz     fehler5
        mov     si,offset msg03
        jmps    hinweis

fehler5: cmp    ax,05
        jnz     fehler7b
        mov     si,offset msg05
        jmps    hinweis

fehler7b:
        cmp     ax,7bh
        jnz     fehler7100
        mov     si, offset msg7b
        jmps    hinweis

fehler7100:
        cmp     ax,7100h
        jnz     fehlerxx

        mov     si,offset msg7100
        jmps    hinweis

fehlerxx:
        push    ax
        mov     al,ah
        call    hex_out
        pop     ax
        call    hex_out
        mov     si, offset msgxx

fehler99:
        call    printa                  ;Fehlermeldung zeigen
        prstr   <' Programmabbruch!',cr>
        MOV     AX,4C01H                ;Programmende mit Returncode 1
        int21
        jmp     EXIt_CMD

hinweis:
        call    printa                  ;Fehlermeldung zeigen
        print_crlf                      ;und weitermachen!
        ret

;------------------------
; Unterprogramm Aufbereiten und Zeigen der Anzeigezeile
;------------------------
; Unterprogramm Wandeln und Abspeichern 8 bit Zahl (in al) in hexa
bin_hex:
        xchg    al,ah
        push    ax
        shr     al,4
        call    binhex5
        pop     ax
binhex5:
        and     al,0fh
        daa
        add     al,0f0h
        adc     al,40h
        mov     ds:[di],al
        inc     di
        ret

;------------------------------------
; Unterprogramm erzeugt aus Binaerzahl in dx:ax Dezimalzahl
; und baut sie rueckwaerts ab di im Speicher auf
; Verfahren: Division mit 10d, Rest -> Speicher
int2asc:
        xchg    bp,dx
        mov     bx,0ah
        mov     cl,30h  ;'0'

int2asc2:
        or      bp,bp
        jz      int2asc4
        xchg    bp,ax
        xor     dx,dx
        div     bx
        xchg    bp,ax
        div     bx
        or      dl,cl
        mov     [di],dl
        dec     di
        jmps    int2asc2

int2asc4:
        xor     dx,dx           ;kurze zahl (dx war 0)
        div     bx
        or      dl,cl
        mov     [di],dl
        dec     di
        or      ax,ax
        jnz     int2asc4
        ret

;------------------------
; Attribut zum Anzeigetext
ls50:
        mov     cx,6            ;6-mal schieben

        mov     si, offset gef00h
        mov     b al,ds:[si]
        mov     di,offset msg_attrs+4

ls55:   cmp     cx,3            ;Attribut Volume Label ignorieren
        jne     ls60
        rcr     al,1
        jmps    ls70

ls60:   rcr     al,1
        jc      ls65            ;Attrubut gesetzt

        mov     b ds:[di],'-'
ls65:   dec     di

ls70:   loop    ls55

;------------------------
; Tag letzte Aenderung zum Anzeigetext
        mov     si,offset gef14h+2
        mov     w ax,ds:[si]
        push    ax
        and     ax,001fh        ;alles ausser Tag ausmaskieren
        mov     bl,10
        div     bl
        or      ax,3030h        ;Wandlung in ASCII
        mov     w msg_ttmmjj,ax

        pop     ax
        push    ax
        sar     ax,9
        and     ax,007fh        ;alles ausser Jahreszahl ausmaskieren
        add     ax,1980         ;Jahreszahl korrigieren
        xor     dx,dx

        mov     di,offset msg_ttmmjj+7
        call    int2asc

        pop     ax
        sar     ax,5
        and     ax,000fh        ;alles ausser Monat ausmaskieren
        div     bl
        or      ax,3030h        ;Wandlung in ASCII
        mov     w msg_ttmmjj+3,ax       ;Monat ueberschreibt Jahrtausend
        mov     b msg_ttmmjj+5,'.'      ;Punkt ueberschreibt Jahrhundert
;       mov     si,offset attrs
;       call    printa

;------------------------
; Uhrzeit letzte Aenderung zum Anzeigetext

        mov     si,offset gef14h
        mov     w ax,ds:[si]
        push    ax
        sar     ax,5
        and     ax,003fh        ;alles ausser Minuten ausmaskieren
        mov     bl,10
        div     bl
        or      ax,3030h        ;Wandlung in ASCII
        mov     w msg_hhmm+3,ax

        pop     ax
        sar     ax,11
        and     ax,001fh        ;alles ausser Stunden ausmaskieren
        mov     bl,10
        div     bl
        or      ax,3030h        ;Wandlung in ASCII
        mov     w msg_hhmm,ax

;------------------------
; Dateilaenge hexadezimal zum Anzeigetext. Maximal werden 4 GB
; Dateilaenge erkannt.

        mov     di,offset msg_len
        mov     si,offset gef1ch+6      ;Dateilaenge High Word
        mov     ax,ds:[si]
        call    bin_hex
        mov     al,ah
        call    bin_hex
        dec     si
        dec     si
        mov     ax,ds:[si]              ;Dateilaenge Low Word
        call    bin_hex
        mov     al,ah
        call    bin_hex

        mov     si,offset msg_satz +47
        mov     b ds:[si],0             ;Schutzbyte, markiert etwa Zeilenende
        sub     si,47
        call    printa

; langen Dateinamen anzeigen
        mov     si,offset curlfn
        call    printa

        print_crlf

; Inhalt von msg_attrs wieder herstellen
        mov     si,offset attrs
        mov     di,offset msg_attrs
        mov     cx,5            ;5 Bytes Laenge u
ls80:   mov     b al,ds:[si]
        mov     b ds:[di],al
        inc     si
        inc     di
        loop    ls80
        ret

;----------------------------------------------
;BYE, EXIT,QUIT Kommando(s) oder ! zum Beenden von sh.exe

EXIT_CMD:
        MOV     AX,04C00H
        int21

;======
;Unterprogrammsammlung (modifiziert uebernommenn aus firm.mac)
;======
;print message following call prmsg and ending with 0
prmsg:  pop     si
        push    ds              ;sichern ds
        push    cs
        pop     ds              ;ds zeigt nun auf das Codesegment
        call    printa
        pop     ds              ;nach Ausgabe ds wieder heretellen
        push    si      ;returnadr.
        ret

;druckt die mit si adressierte zeichenkette
;die zeichenkette darf keine steuerzeichen außer cr enthalten
;sie endet mit 0h
printa:
        push    ax
        cld

printa5:
        lodsb           ;zeichen
        or      al,al   ;ob fertig ?
        jz      printa7 ;ja

        call    printchr
        jmps    printa5
printa7:
        pop     ax
        ret

;----------------------------------------------
;Auskommentierte Testhilfe
;----------------------------------------------
comment #
Testhilfe Hexaanzeige Register cx
        push    dx
        push    cx
        mov     al,ch
        call    hex_out
        pop     cx
        push    cx
        mov     al,cl
        call    hex_out
        pop     cx
        pop     dx
#

;ausgabe 8 bit zahl (in al) in hexa
hex_out: push   ax
        shr     al,4
        call    conv
        pop     ax
conv:   and     al,0fh
        daa
        add     al,0f0h
        adc     al,40h

;ausgabe zeichen in al zum bildschirm
printchr:
        cmp     al,cr   ;cr?
        je      $print_crlf
printchr1:
        push    cx
        push    bx
        mov     dl,al   ;Zeichen nach dl
        int21   2

        pop     bx
        pop     cx
        ret

$print_crlf:
        mov     dl,cr
        int21   2
        mov     dl,lf
        int21   2
        ret

CSEG    ENDS

;==============================================
;Stacksegment
;==============================================

STACK   SEGMENT  PARA STACK 'STACK'
        DW      64 DUP(?)
STACK   ENDS

;==============================================
;Datensegment
;==============================================

DATA    SEGMENT PARA PUBLIC 'DATA'

COMMANDS:                       ;Kommandotabelle fuer
        dbz     '?'             ;diesen Kommandointerpreter
        dw      help_cmd
        dbz     '\?'
        dw      help_cmd
        dbz     '25'
        dw      ega25
        dbz     '43'
        dw      ega43
        dbz     'BYE'
        DW      EXIT_CMD
        dbz     'CLS'
        DW      CLS_CMD
        dbz     'CD'
        dw      cd_cmd
        dbz     'DOS'
        DW      DOS_CMD
        dbz     'EXIT'
        DW      EXIT_CMD
        dbz     'EXPL'
        dw      expl_cmd
        dbz     'LS'
        dw      ls_cmd
        dbz     'PWD'
        dw      pwd_cmd
        dbz     'QUIT'
        DW      EXIT_CMD
        DB      0


merke           db      0       ;Merker Parameteruebername (0=nein)
merke_exit      db      0       ;Merker Exit-Anforderung
merke_pwd       db      0       ;Merker PWD-Anzeige (bei CD-Abarbeitung)

COM_VAR         DB      'COMSPEC=',0
COM_SPEC_dos    DB      80 DUP(0)
NULTAIL         DB      0,CR

CMD_TAIL_dos    DB      0,' /C '
INP_BUF         DB      80 DUP(0)
INP_BUF_LENGTH  EQU     $-INP_BUF
CMD_TAIL_dos_LENGTH EQU         $-CMD_TAIL_dos-1
Schutzbyte      db      cr      ;Soll vor Ueberlauf sachuetzen

;Besondere Directories
cd_home    db      '\',0   ;Grundbibliothek
cd_back    db      '..',0  ;eine Stufe zurueck


;curdrv  db     0       ;Current Drive (Buchstabe), einzusetzen vor curlfn
;       db      ':\'    ;an der Position curlfn - 3


attrs   db      "ADSHR"
msg_satz        equ     $
msg_ttmmjj      db      "tt.mm.jj "
msg_hhmm        db      "hh:mm "
msg_attrs       db      "ADSHR "
msg_len         db      "12341234 "
        db      0               ;hier Ausgabeende

;Beschreibung des Suchsatzes. Die Bytemuster erleichtern das Testen
suchsatz equ    $
gef00h  dd      0               ;Attribut lh 00
gef04h  dq      0               ;wann erstellt lh lh 00 00
gef0ch  dq      0               ;wann zuletzt zugegriffen lh lh 00 00
gef14h  dq      0               ;wann zuletzt geaendert lh lh 00 00
gef1ch  dd      0,0             ;Dateigroesse High :Low  00 00 : lh lh
gefres  db      '12345678'      ;reserviert
curlfn  db      260 dup ('0')   ;langer Name
cussfn  db      14 dup ('1')    ;kurzer Name 1111111111111

ENV_SEG DW      0
handle  dw      0


;----------------------------------------------
;Bildschirmausgaben zur Fehler- und Hilfsanzeige

msg1:   dblz    'Speicherfreigabe nicht moeglich.'
MSG2:   dblz    'Befehlsinterpreter konnte nicht aufgerufen werden.'
MSG3:   dblz    'Keine COMSPEC-Variable im Umgebungsblock.'
msg4:
 dbl  'Befehle:'
 dbl  '? oder /?   Hilfsanzeige wie hier'
 dbl  '!, BYE, EXIT oder QUIT beendet diese Shell'
 dbl  '! am Schluss des Befehls beendet diese Shell nach seiner Ausfuehrung.'
 dbl  '        Beispiel fuer Aufruf mit Standardausgabeumleitung: '
 db   '        ',3eh
 dbl  'hugo.txt sh ls d:\temp\!'
 dbl  '25   setzt Bildschirmhoehe auf 25 Zeilen'
 dbl  '43   setzt Bildschirmhoehe auf 43 Zeilen'
 dbl  'CD   wechselt Verzeichnis und Laufwerk, z.B. CD C:/TEMP\HUGO  oder CD ..'
 dbl  'CLS  loescht den Bildschirminhalt'
 dbl  'DOS  DOS-Kommandooberflaeche, beenden mit EXIT'
 dbl  'EXPL startet Windows-Explorer auf aktuellem Pfad'
 dbl  'LS   Directory-Anzeige aehnlich DIR'
 dblz 'PWD  zeigt aktuelles Verzeichnis (Print Working Directory)'
msg5:   dblz    'Keine Zielangabe fuer CD!'
msg6:   dblz    'CD konnte nicht ausgefuehrt werden!'

msg02:  dbz     'Datei nicht gefunden.'
msg03:  dbz     'Pfad nicht gefunden.'
msg05:  dbz     'Zugriff verweigert.'
msg7b:  dbz     'Die Syntax fuer den Datei- oder Verzeichnisnamen ist falsch.'
msg7100: dbz    'Funktion 71xx wird nicht unterstuetzt.'
msgxx:  dbz     '=Nicht aufgeschluesselter Fehler.'

CLS_STR db      escp,'[2J',0            ;ANSI.SYS clear screen string.

PAR_BLK_dos:                            ;Parameterblock fuer DOS-Aufruf (COMSPEC)
                DW      0               ;Int 21h Funktion 4BH (execute)
PAR_CMD_dos     DW      OFFSET CMD_TAIL_dos
                DW      SEG CMD_TAIL_dos
                DD      -1
                DD      -1

par_blk_expl:                           ;Parameterblock fuer Explorer-Aufruf
                DW      0               ;Int 21h Funktion 4BH (execute)
par_cmd_expl    DW      offset cmd_tail_expl
                DW      seg cmd_tail_expl
                DD      -1
                DD      -1

com_spec_expl:  dbz     'C:\WINDOWS\EXPLORER.EXE'
cmd_tail_expl   db      8,' /n /e,.',cr
                DD      -1
                DD      -1

DATA    ENDS
        END     _start

Letztes Upload: 30.03.2017 um 16:33:24