From : Julian Schmidt                                          1:273/911
Subj : Command-line Params (1)                                               


 Q: How do I access command-line parameters from assembly language?
 
 A: When you run a program, DOS takes care of a few preliminaries before
 it transfers control to your program.  One of the things that DOS does
 is to build a Program Segment Prefix (PSP) for your program. The PSP is
 a 256-byte block of data that contains information for the program to
 use, as well as information that DOS needs to maintain.  One field of
 the PSP contains a count of the number of characters in the command-
 tail.  Another field contains a copy of the actual command-tail,
 including the terminating carriage return character.  The command-tail
 count is stored at offset 80h of the PSP, and the command-tail itself
 starts at offset 81h.  The terminating carriage-return character is not
 included in the command-tail count.  Since there is only a single byte
 set aside in the PSP for the length of the command-tail, it is obvious
 that the command-tail can not exceed 255 characters in length. In
 reality, the maximum length of the command-tail is 128 bytes.
 
 In order to access these command-line parameters, our program must
 first find its PSP.  Remember that .COM programs are entirely contained
 within a single, 64K segment.  That is, all data, code, and stack space
 is referenced with near pointers, or offsets from the same segment
 register.  Also recall that .COM programs start at offset 100h within
 this program segment (in assembly language, the ORG directive tells the
 assembler to begin placing data at a particular offset within the
 program segment).  The reason for leaving the first 100h (256d) bytes
 of the program segment untouched is that this is where DOS constructs
 the PSP.  A memory-map of a typical .COM program would look like this:
 
 Program Section  -  PSP       Program Code and Data     Stack  (grows
 Offset Address   -  :0000h    :0100h                    :FFFFh   down)
 
 If we neglected to issue an ORG 0100h directive in our .COM program's
 source code, our code and/or data would overwrite the PSP and probably
 cause problems during the execution of the program.
 
 .EXE programs, on the other hand, can use many different code, data,
 and stack segments.  How, then, does our typical .EXE program locate
 its copy of the PSP?  There are two methods that we can use to find
 the PSP from within an .EXE program.  The first method is to record
 the address in the ES register when the program is starting up.  This
 is the segment base address of the PSP.  It is good programming
 practice to store this address in memory in case you need to retrieve
 it later on in your program.  The second method of locating the segment
 base address of our program's PSP is to issue a DOS call.  Function 51h
 of DOS Int 21h will return the PSP's segment base address in BX.  This
 function will not work with DOS v1.  This function is generally of use
 only to TSR programs, and I won't dwell upon it here.
 
 Now we know that the command-line parameter list is located in the
 PSP, and we know how to locate the PSP itself.  The only thing left
 to learn is how to design programs that make good use of the command-
 tail.  One trick that will make our programming easier is to convert
 the entire command-tail to uppercase (unless, of course, you need to
 be able to distinguish lowercase characters from uppercase characters).
 This can be accomplished with a section of code similar to this:
 
 ;--------------------cut here--------------------
 ;This code fragment will convert every lowercase character in the
 ;PSP command-tail into its equivalent uppercase character.
 ;Assume that ES is pointing to the PSP segment base address.
 cld                    ;Clear the direction flag.
 mov   bx,80h           ;Point BX to the command-tail count.
 xor   ch,ch            ;This makes CH zero.   
 mov   cl,byte ptr[bx]  ;Now, CX holds the length of the command-tail.
 mov   si,81h           ;Point SI to the command-tail.
 mov   di,si            ;Now, DI and SI point to the same character.
 push  ds               ;Save the value in DS.
 mov   ax,es
 mov   ds,ax            ;Now, ES and DS are equal.
                        ;Now, DS:SI and ES:DI each point to offset 81h
                        ;  of the Program Segment Prefix (PSP)
 UpCase:
 lodsb                  ;Load the byte at DS:SI into AL.
 cmp   al,'a'           ;Determine if this is a lowercase character...
 jb    KeepIt           ;If not, jump to skip the conversion process.
 cmp   al,'z'
 ja    KeepIt
 sub   al,32            ;Convert lowercase to uppercase (ASCII).
 KeepIt:
 stosb                  ;Store the resulting byte back to ES:DI.
 loop  UpCase           ;Continue until CX = zero.
 pop   ds               ;Restore the DS register that we PUSHed earlier.
 ;--------------------cut here--------------------
 
Since, when using certain outdated DOS functions, the command-tail
 buffer in the PSP can be overwritten by other information, it may be
 useful to copy the individual parameters to another buffer that your
 program creates.  This process can be a simple string-copy if you are
 only expecting a single command-line parameter, or it can take the
 form of a heavy-duty C-style command-tail parser.
 
 The following is an example of a simple procedure that simply checks
 the command-tail for the presence of a single argument, and if it
 exists, copies it to a buffer and converts it to an ASCIIZ string:
 
 ;--------------------cut here--------------------
 Comment*
 ;----------------------------------------------------------------------
     Parse procedure (near)
     Written by Julian H. Schmidt - public domain code.  MASM code.
     Entry:  Assume ES and DS both point to the data segment.
     Exit:   The Parse Buffer (Parse_Buffer) will contain the command-
             line argument converted into an ASCIIZ string.
             If no useable command-line argument was found, CF is set.
     Changes: AX,BX,CX,DX,SI,DI
     FileName: parse.asm
 ;----------------------------------------------------------------------
 Psuedocode:
     Check command-line length
     If command-line length != 0
     {
         scasb for 1st non-space character
         if 1st non-space character != carriage-return character
         {
              copy command-line to the Parse_Buffer
              convert it to ASCIIZ by tacking on a NULL
              return (the carry flag is clear)
         }
     }
     return (set the carry flag to indicate NO command-line argument.
 ;----------------------------------------------------------------------
 End_Of_Comment*
 extrn PSP_Address : word
 _data Segment Public 'DATA'
 public Parse_Buffer
 Parse_Buffer   db  129 dup(0)     ;Stores command-line and arguments.
                                   ;Room for a 128-character argument.
 _data Ends
 _code Segment 'CODE'
 public parse                      ;Procedure.
 assume cs:_code,ds:_data,es:nothing,ss:_stack
 parse     proc   near
     clc                     ;Clear the carry flag.
     push    ds
     push    es              ;Save some registers.
     push    ds              ;Yes, I wanna push it twice
 
     mov     ax,PSP_Address  ;Sector base address of the PSP.
     mov     es,ax           ;ES points to PSP segment.
     mov     bx,80h          ;Pointer to length of command-line.
     mov     cl,byte ptr es:[bx]
     cmp     cl,0            ;Is CL zero (NO arguments)?
     jz      p_1             ;If so, jump.
     xor     ch,ch           ;Set CH = 0 for loop control.
     mov     di,81h          ;Start of argument(s).
 ;----------------------------------------------------------------------
 ;   Now ES:DI points to beginning of the command-line arguments.
 ;----------------------------------------------------------------------
     mov     al,20h          ;Search for spaces (20h).
 repe        scasb           ;Scan for the 1st non-space char.
     dec     di              ;Points DI to the 1st non-space.
     inc     cx              ;Keep the count accurate.
     cmp     byte ptr es:[di],0dh    ;Is it a <CR>?
     je      p_1             ;No useable comm-line parameters.
     mov     ax,di           ;Xchg the SI and DI registers.
     mov     si,ax
     mov     ax,es           ;Xchg the ES and DS registers.
     mov     ds,ax
 ;----------------------------------------------------------------------
 ;   Now, DS:SI is pointing to the beginning of the comm-line argument.
 ;----------------------------------------------------------------------
     pop     es              ;ES is pointing to the Data Seg.
     mov     di,offset Parse_Buffer
 ;----------------------------------------------------------------------
 ;   Now, ES:DI is pointing to the 1st byte of the Parse Buffer.
 ;----------------------------------------------------------------------
 rep         movsb           ;Copy comm-line args to parse buffer.
     mov     byte ptr es:[di],0    ;Create an ASCIIZ string.
     jmp     short p_2
 p_1:                        ;No arguments were found.
     pop     ds
     stc                     ;Set Carry Flag to indicate error.
 p_2:
     pop     es
     pop     ds
     ret                     ;Return.
 parse    endp
 _code    Ends
     END
 ;--------------------cut here--------------------
 
Now you know how DOS gives us access to command-line parameters.  You
 can now write a module that handles the command-tail in whichever way
 you prefer.
 
 As a final demonstration, here's a simple program that plays around with
 the command-tail:
 
 ;--------------------cut here--------------------
 Comment*
 CMD.COM  -  Demonstrates use of command-line arguments.
 Written by Julian H. Schmidt.  Public domain code.
 This program accepts command-line input and displays it
 in reverse order.
 MASM code.
 End of Comment*
 
 ;--------------------------------------------------
 ;Equates:
 cmd_num        equ    80h        ;Number of chars. entered on command
                                  ;  line is stored here by DOS.
 cmd_begin      equ    81h        ;Comm-line parameters start here.
 ;--------------------------------------------------
 
 _code   segment 'CODE'
         org 100h                 ;This is a .COM program.
         assume cs:_code, ds:_code
 Start:
         jmp        Program       ;This is the program entry point.
 
 Msg db "Usage:     CMD <string>",0ah,0dh    ;22 bytes long.
 
 Program:
         mov        bx,cmd_num      ;BX is a pointer into the PSP.
         cmp        byte ptr[bx],0  ;Was ANYTHING entered on comm.-line?
         jz         Info            ;If not, print message and quit.
         xor        cx,cx           ;Zero out the CX register.
         mov        cl,byte ptr[bx] ;Number of characters entered is
                                    ;  stored in CX to be used later.
         mov        bx,cmd_begin    ;Could have just used: inc  bx.
         add        bx,cx       ;Now BX points to END of command tail.
         mov        ah,02h          ;Request function 02h (dos).
 PrintIt:
         mov        dl,byte ptr[bx] ;Gets the byte to be printed into DL.
         int        21h             ;Dos's Display Output function.
         dec        bx              ;Point to previous character.
         loop       PrintIt         ;Keep printing until CX=0!
         jmp        short   Exit
 Info:
         mov        ah,40h          ;Request DOS function 40h.
         mov        bx,0001h        ;Standard Output handle.
         mov        cx,22           ;Number (decimal) of bytes to write.
         mov        dx,offset Msg   ;Pointer to Information Message.
         int        21h             ;Dos's Write to Device (handle).
 Exit:
         mov        ax,4c00h        ;Request function 4Ch (DOS).
         int        21h             ;Exit to DOS
 _code ends
         END        Start
 ;--------------------cut here--------------------
 
