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 ? 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 ",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--------------------