Materials: Working complete PC Blank Diskette Source Code Diskette (created in the Introduction to TASM module) Student Diskette, "New Boot A Ver 2.0+" Student CD-ROM, "QBOOT" (any version) Objectives: The student will become familiar with the Borland Turbo Assembly language compiler including: Using a blank source code file, Building a source file in stages, Building internal procedures, Opening, reading, and determining a file's size, Getting passed commandline parameters, Building the object file, Building the executable. Competency: The student will learn how to design executable assembly language programs and how to compile and link the source code text file into an executable. The student will become familiar with some basic compiler "dot" directives and some simple machine language instructions. The student will learn how to call DOS to open a file, read the contents of a file and how to determine the file's size. |
Preparation
The student will need a copy of Borland's Turbo Assembly Lanugage software, preferably version 5.0.
After launching QBOOT by pressing [F8] say yes to all CONFIG.SYS prompts and all AUTOEXEC.BAT prompts except the last one to launch Windows 3.11.
Change to the K: drive and insert the source code diskette created in the Introduction to TASM module. Copy BLANK.ASM and DEV.BAT from the diskette to the root of the K: drive and then run "edit blank.asm" All assembly language source files should have the file extention .ASM so that the compilers can easily recognize them.
Procedures
In EDIT's main field, the following source code should be present:
.model tiny .stack 200h .data .code main proc mov ax, @data mov ds, ax terminate: mov ax, 4C00h int 21h main endp end main
Press [Alt]+[F] to open the File menu and then press [A] to save the file with a new name. Name the file XTYPE.ASM. Make the following changes:
.model tiny .stack 200h .data msg db "Press a key to continue...$" fopenmsg db "Cannot open specified file.",0Dh,0Ah,'$' freadmsg db "Error occured while reading the file.",0Dh,0Ah,'$' crlf db 0Dh, 0Ah, '$' fhandle dw 0FFFFh ;invalid file handle fname db "TEST.TXT",0 fbuf db 2000 dup(0) crlfcount db 0 bufsize dw 0 fsizehi dw 0 fsizelo dw 0 fopenerr db 0 freaderr db 0 fsizeeven db 0 fdone db 0 lastread db 0 .code main proc mov ax, @data mov ds, ax start: ;open the file, check an error flag quit if set otherwise continue ;check the file size and if it is evenly divisible by 2000 (the buffer size main1: ;read the file and set a file done flag, if done then quit main2: mov cx, bufsize ;set loop counter mov bx, offset fbuf ;set base indexed addressing xor si, si ;set offset pointer to zero main3: mov dl, [bx+si] ;read character from file buffer in RAM mov ah, 2 ;DOS function 2 display character int 21h cmp dl, 0Ah ;watch for a line feed je inccrlf ;if it is jump to the counter otherwize continue main4: inc si ;point to the next char in the file buffer loop main3 ;continue the loop displaying chars on screen jmp main1 ;when the buffer is done, go read more info from the file inccrlf: inc crlfcount ;add 1 to the line feed count cmp crlfcount, 23 ;have there been 24 line feeds displayed (screen is full) ja main5 ;yes: jump out to the pause code jmp main4 ;no: continue displaying chars on screen main5: ;set crlfcount back to 0 push cx ;save current cx (loop counter) on the stack xor cx, cx ;clear it mov crlfcount, cl ;clear line feed counter variable pop cx ;restore loop counter from the stack mov ah, 9 ;function 9: display a string mov dx, offset msg ;the pause message int 21h ;call DOS to show the string mov ah, 8 ;function 8 = get a key w/o echo int 21h ;this waits for the user to press any key mov ah, 9 ;these three lines display a carriage return/line feed mov dx, offset crlf int 21h jmp main3 ;continue displaying chars from the file buffer terminate: mov ax, 4C00h ;function 4C = quit program int 21h main endp end main
Press [Alt]+[F] then [S] to save the changes. The highlighted lines are the ones that were added to the original BLANK.ASM. The first change creates the new variables and the second change starts creating the executable code. This is going to be a long project so it should be developed in stages starting with a few executable lines and then notes on what needs to be added later.
At this point because there is going to be a lot of repitition of commands to copy the file to the floppy, then compile, link and execute it quit EDIT and run dev xtype. This batch file will open the file in EDIT. Press [Alt]+[F] then [X] to quit. At the continue prompt type [Y] to save the file to the floppy, then press [Y] to compile, [Y] to link, and [N] to execute.
Now run dev xtype again and in EDIT add the following code:
.model tiny .stack 200h .data msg db "Press a key to continue...$" fopenmsg db "Cannot open specified file.",0Dh,0Ah,'$' freadmsg db "Error occured while reading the file.",0Dh,0Ah,'$' crlf db 0Dh, 0Ah, '$' fhandle dw 0FFFFh ;invalid file handle fname db "TEST.TXT",0 fbuf db 2000 dup(0) crlfcount db 0 bufsize dw 0 fsizehi dw 0 fsizelo dw 0 fopenerr db 0 freaderr db 0 fsizeeven db 0 fdone db 0 lastread db 0 .code main proc mov ax, @data mov ds, ax start: ;open the file, check an error flag quit if set otherwise continue call fopen cmp fopenerr, 0 jne terminate ;check the file size and if it is evenly divisible by 2000 (the buffer size main1: ;read the file and set a file done flag, if done then quit main2: mov cx, bufsize ;set loop counter mov bx, offset fbuf ;set base indexed addressing xor si, si ;set offset pointer to zero main3: mov dl, [bx+si] ;read character from file buffer in RAM mov ah, 2 ;DOS function 2 display character int 21h cmp dl, 0Ah ;watch for a line feed je inccrlf ;if it is jump to the counter otherwize continue main4: inc si ;point to the next char in the file buffer loop main3 ;continue the loop displaying chars on screen jmp main1 ;when the buffer is done, go read more info from the file inccrlf: inc crlfcount ;add 1 to the line feed count cmp crlfcount, 23 ;have there been 24 line feeds displayed (screen is full) ja main5 ;yes: jump out to the pause code jmp main4 ;no: continue displaying chars on screen main5: ;set crlfcount back to 0 push cx ;save current cx (loop counter) on the stack xor cx, cx ;clear it mov crlfcount, cl ;clear line feed counter variable pop cx ;restore loop counter from the stack mov ah, 9 ;function 9: display a string mov dx, offset msg ;the pause message int 21h ;call DOS to show the string mov ah, 8 ;function 8 = get a key w/o echo int 21h ;this waits for the user to press any key mov ah, 9 ;these three lines display a carriage return/line feed mov dx, offset crlf int 21h jmp main3 ;continue displaying chars from the file buffer terminate: mov ax, 4C00h ;function 4C = quit program int 21h main endp fopen proc mov ah, 3Dh ;function 3D = open file w/handle mov al, 0 ;access byte: 0000b:readonly,0001=writeonly,0010:R/W mov dx, offset fname ;must point to the ASCII filename and end with a zero ;known as an ASCIIZ string int 21h jc fopen1 ;if DOS fails to open the file it will return with the carry flag mov fhandle, ax ;otherwise the AX holds the file handle which must be saved jmp fopenex ;then quit the procedure fopen1: mov ah, 9 ;if here then there was an error, show the message and... mov dx, offset fopenmsg int 21h inc fopenerr ;set the error flag, then quit fopenex: ret fopen endp end main
Save the changes, then [Y] to copy, [Y] to compile, [Y] to link and [N] to execute. Run dev xtype again and make these changes:
.model tiny .stack 200h .data msg db "Press a key to continue...$" fopenmsg db "Cannot open specified file.",0Dh,0Ah,'$' freadmsg db "Error occured while reading the file.",0Dh,0Ah,'$' crlf db 0Dh, 0Ah, '$' fhandle dw 0FFFFh ;invalid file handle fname db "TEST.TXT",0 fbuf db 2000 dup(0) crlfcount db 0 bufsize dw 0 fsizehi dw 0 fsizelo dw 0 fopenerr db 0 freaderr db 0 fsizeeven db 0 fdone db 0 lastread db 0 .code main proc mov ax, @data mov ds, ax start: ;open the file, check an error flag quit if set otherwise continue call fopen cmp fopenerr, 0 jne terminate ;check the file size and if it is evenly divisible by 2000 (the buffer size call fsize main1: ;read the file and set a file done flag, if done then quit main2: mov cx, bufsize ;set loop counter mov bx, offset fbuf ;set base indexed addressing xor si, si ;set offset pointer to zero main3: mov dl, [bx+si] ;read character from file buffer in RAM mov ah, 2 ;DOS function 2 display character int 21h cmp dl, 0Ah ;watch for a line feed je inccrlf ;if it is jump to the counter otherwize continue main4: inc si ;point to the next char in the file buffer loop main3 ;continue the loop displaying chars on screen jmp main1 ;when the buffer is done, go read more info from the file inccrlf: inc crlfcount ;add 1 to the line feed count cmp crlfcount, 23 ;have there been 24 line feeds displayed (screen is full) ja main5 ;yes: jump out to the pause code jmp main4 ;no: continue displaying chars on screen main5: ;set crlfcount back to 0 push cx ;save current cx (loop counter) on the stack xor cx, cx ;clear it mov crlfcount, cl ;clear line feed counter variable pop cx ;restore loop counter from the stack mov ah, 9 ;function 9: display a string mov dx, offset msg ;the pause message int 21h ;call DOS to show the string mov ah, 8 ;function 8 = get a key w/o echo int 21h ;this waits for the user to press any key mov ah, 9 ;these three lines display a carriage return/line feed mov dx, offset crlf int 21h jmp main3 ;continue displaying chars from the file buffer terminate: mov ax, 4C00h ;function 4C = quit program int 21h main endp fopen proc mov ah, 3Dh ;function 3D = open file w/handle mov al, 0 ;access byte: 0000b:readonly,0001=writeonly,0010:R/W mov dx, offset fname ;must point to the ASCII filename and end with a zero ;known as an ASCIIZ string int 21h jc fopen1 ;if DOS fails to open the file it will return with the carry flag mov fhandle, ax ;otherwise the AX holds the file handle which must be saved jmp fopenex ;then quit the procedure fopen1: mov ah, 9 ;if here then there was an error, show the message and... mov dx, offset fopenmsg int 21h inc fopenerr ;set the error flag, then quit fopenex: ret fopen endp fsize proc mov ax, 4202h ;move file position pointer ;method 2 in AL means reckon from end of file mov bx, fhandle ;specify an open file by its handle number xor cx, cx ;start position is start of file xor dx, dx ;held as CX:DX = 0 int 21h mov fsizelo, ax ;position will be returned as a 32-bit value stored as DX:AX mov fsizehi, dx ;test for divisible by 2000 mov cx, 2000 div cx ;divides implicit ax by cx, quotient (number of times to ;loop) left in ax, remainder left in dx (if zero it is even ;divisible by 2000) cmp dx, 0 ;evenly divisible? jne fsizeex ;yes: jump over the adjustment inc fsizeeven ;set warning flag fsizeex: mov ax, 4200h ;reset file pointer to beginning of the file mov bx, fhandle xor cx, cx xor dx, dx int 21h ret fsize endp end main
The DOS call in fsize that starts with the instruction "mov ax, 4202" is a programmer's trick to determine the size of the file by moving the current DOS file pointer from the beginning of the file (where it is when it is opened) to the end which is what this call does. by doing this, the current file pointer position is returned in DX:AX and since it points to the last byte in the file it is the same as its size in bytes. Once this is done, the pointer must be returned to the beginning of the file so that the sequential reads can begin in the next part of the program. Save these changes and exit EDIT. Press [Y] to copy the source file to the floppy, [Y] to compile, [Y] to link and [N] to execute. Run dev xtype again and make these changes:
.model tiny .stack 200h .data msg db "Press a key to continue...$" fopenmsg db "Cannot open specified file.",0Dh,0Ah,'$' freadmsg db "Error occured while reading the file.",0Dh,0Ah,'$' crlf db 0Dh, 0Ah, '$' fhandle dw 0FFFFh ;invalid file handle fname db "TEST.TXT",0 fbuf db 2000 dup(0) crlfcount db 0 bufsize dw 0 fsizehi dw 0 fsizelo dw 0 fopenerr db 0 freaderr db 0 fsizeeven db 0 fdone db 0 lastread db 0 .code main proc mov ax, @data mov ds, ax start: ;open the file, check an error flag quit if set otherwise continue call fopen cmp fopenerr, 0 jne terminate ;check the file size and if it is evenly divisible by 2000 (the buffer size call fsize main1: ;read the file and set a file done flag, if done then quit call fread cmp fdone, 0 jne terminate cmp freaderr, 0 jne terminate main2: mov cx, bufsize ;set loop counter mov bx, offset fbuf ;set base indexed addressing xor si, si ;set offset pointer to zero main3: mov dl, [bx+si] ;read character from file buffer in RAM mov ah, 2 ;DOS function 2 display character int 21h cmp dl, 0Ah ;watch for a line feed je inccrlf ;if it is jump to the counter otherwize continue main4: inc si ;point to the next char in the file buffer loop main3 ;continue the loop displaying chars on screen jmp main1 ;when the buffer is done, go read more info from the file inccrlf: inc crlfcount ;add 1 to the line feed count cmp crlfcount, 23 ;have there been 24 line feeds displayed (screen is full) ja main5 ;yes: jump out to the pause code jmp main4 ;no: continue displaying chars on screen main5: ;set crlfcount back to 0 push cx ;save current cx (loop counter) on the stack xor cx, cx ;clear it mov crlfcount, cl ;clear line feed counter variable pop cx ;restore loop counter from the stack mov ah, 9 ;function 9: display a string mov dx, offset msg ;the pause message int 21h ;call DOS to show the string mov ah, 8 ;function 8 = get a key w/o echo int 21h ;this waits for the user to press any key mov ah, 9 ;these three lines display a carriage return/line feed mov dx, offset crlf int 21h jmp main3 ;continue displaying chars from the file buffer terminate: mov ax, 4C00h ;function 4C = quit program int 21h main endp fopen proc mov ah, 3Dh ;function 3D = open file w/handle mov al, 0 ;access byte: 0000b:readonly,0001=writeonly,0010:R/W mov dx, offset fname ;must point to the ASCII filename and end with a zero ;known as an ASCIIZ string int 21h jc fopen1 ;if DOS fails to open the file it will return with the carry flag mov fhandle, ax ;otherwise the AX holds the file handle which must be saved jmp fopenex ;then quit the procedure fopen1: mov ah, 9 ;if here then there was an error, show the message and... mov dx, offset fopenmsg int 21h inc fopenerr ;set the error flag, then quit fopenex: ret fopen endp fsize proc mov ax, 4202h ;move file position pointer ;method 2 in AL means reckon from end of file mov bx, fhandle ;specify an open file by its handle number xor cx, cx ;start position is start of file xor dx, dx ;held as CX:DX = 0 int 21h mov fsizelo, ax ;position will be returned as a 32-bit value stored as DX:AX mov fsizehi, dx ;test for divisible by 2000 mov cx, 2000 div cx ;divides implicit ax by cx, quotient (number of times to ;loop) left in ax, remainder left in dx (if zero it is even ;divisible by 2000) cmp dx, 0 ;evenly divisible? jne fsizeex ;yes: jump over the adjustment inc fsizeeven ;set warning flag fsizeex: mov ax, 4200h ;reset file pointer to beginning of the file mov bx, fhandle xor cx, cx xor dx, dx int 21h ret fsize endp fread proc cmp lastread, 0 ;check for a last read ja fread3 ;yes if greater than zero, else continue mov ah, 3Fh ;function 3F = read from file using handle mov bx, fhandle ;refers to the file using its handle mov cx, 2000 ;number of bytes to read mov dx, offset fbuf ;address of the buffer to be filled with file data int 21h ;call DOS to read from the file jc fread1 ;carry: there was a problem reading the file, else cmp ax, 2000 ;number of bytes read good? jne fread2 ;no then set for last read jmp freadex fread1: mov ah, 9 mov dx, offset freadmsg int 21h inc freaderr ;we are here if there was a problem reading the file fread2: inc lastread ;this prevents another read attempt cmp fsizeeven, 0 ;if even multiple of 2000 then jne fread3 ;indicate that the file is done jmp freadex ;otherwise quit so program can continue fread3: inc fdone ;this notifies the program to quit immediately freadex: mov bufsize, ax ;save number of valid bytes read ret fread endp end main
Save the changes and exit EDIT, [Y] to copy, [Y] to compile, [Y] to link, [N] to execute. At this point the program works, in that it will open the file named "TEST.TXT" and display one screen at a time. Test it by copying any text file to the root of the K: drive and then renaming the file TEST.TXT. You should test it with a large file and then with one smaller than one screen to be sure that it can handle it right. You should then create a test file that is exactly 2000 bytes in size to be sure that it handles this one correctly and another that is 4000 bytes in size.
Once these tests are complete run dev xtype and make the following changes:
.model tiny .stack 200h .data msg db "Press a key to continue...$" fopenmsg db "Cannot open specified file.",0Dh,0Ah,'$' freadmsg db "Error occured while reading the file.",0Dh,0Ah,'$' crlf db 0Dh, 0Ah, '$' fhandle dw 0FFFFh ;invalid file handle fname db 129 dup(0) fbuf db 2000 dup(0) crlfcount db 0 bufsize dw 0 fsizehi dw 0 fsizelo dw 0 fopenerr db 0 freaderr db 0 fsizeeven db 0 fdone db 0 lastread db 0 .code main proc mov ax, @data mov ds, ax start: ;get the commandline passed parameter call getparams ;open the file, check an error flag quit if set otherwise continue call fopen cmp fopenerr, 0 jne terminate ;check the file size and if it is evenly divisible by 2000 (the buffer size call fsize main1: ;read the file and set a file done flag, if done then quit call fread cmp fdone, 0 jne terminate cmp freaderr, 0 jne terminate main2: mov cx, bufsize ;set loop counter mov bx, offset fbuf ;set base indexed addressing xor si, si ;set offset pointer to zero main3: mov dl, [bx+si] ;read character from file buffer in RAM mov ah, 2 ;DOS function 2 display character int 21h cmp dl, 0Ah ;watch for a line feed je inccrlf ;if it is jump to the counter otherwize continue main4: inc si ;point to the next char in the file buffer loop main3 ;continue the loop displaying chars on screen jmp main1 ;when the buffer is done, go read more info from the file inccrlf: inc crlfcount ;add 1 to the line feed count cmp crlfcount, 23 ;have there been 24 line feeds displayed (screen is full) ja main5 ;yes: jump out to the pause code jmp main4 ;no: continue displaying chars on screen main5: ;set crlfcount back to 0 push cx ;save current cx (loop counter) on the stack xor cx, cx ;clear it mov crlfcount, cl ;clear line feed counter variable pop cx ;restore loop counter from the stack mov ah, 9 ;function 9: display a string mov dx, offset msg ;the pause message int 21h ;call DOS to show the string mov ah, 8 ;function 8 = get a key w/o echo int 21h ;this waits for the user to press any key mov ah, 9 ;these three lines display a carriage return/line feed mov dx, offset crlf int 21h jmp main3 ;continue displaying chars from the file buffer terminate: cmp fhandle, 0FFFFh ;same as original value? je term2 ;yes: then just quit, otherwise... mov ah, 3Eh ;function 3Eh, close open file mov bx, fhandle int 21h term2: mov ax, 4C00h ;function 4C = quit program int 21h main endp fopen proc mov ah, 3Dh ;function 3D = open file w/handle mov al, 0 ;access byte: 0000b:readonly,0001=writeonly,0010:R/W mov dx, offset fname ;must point to the ASCII filename and end with a zero ;known as an ASCIIZ string int 21h jc fopen1 ;if DOS fails to open the file it will return with the carry flag mov fhandle, ax ;otherwise the AX holds the file handle which must be saved jmp fopenex ;then quit the procedure fopen1: mov ah, 9 ;if here then there was an error, show the message and... mov dx, offset fopenmsg int 21h inc fopenerr ;set the error flag, then quit fopenex: ret fopen endp fsize proc mov ax, 4202h ;move file position pointer ;method 2 in AL means reckon from end of file mov bx, fhandle ;specify an open file by its handle number xor cx, cx ;start position is start of file xor dx, dx ;held as CX:DX = 0 int 21h mov fsizelo, ax ;position will be returned as a 32-bit value stored as DX:AX mov fsizehi, dx ;test for divisible by 2000 mov cx, 2000 div cx ;divides implicit ax by cx, quotient (number of times to ;loop) left in ax, remainder left in dx (if zero it is even ;divisible by 2000) cmp dx, 0 ;evenly divisible? jne fsizeex ;yes: jump over the adjustment inc fsizeeven ;set warning flag fsizeex: mov ax, 4200h ;reset file pointer to beginning of the file mov bx, fhandle xor cx, cx xor dx, dx int 21h ret fsize endp fread proc cmp lastread, 0 ;check for a last read ja fread3 ;yes if greater than zero, else continue mov ah, 3Fh ;function 3F = read from file using handle mov bx, fhandle ;refers to the file using its handle mov cx, 2000 ;number of bytes to read mov dx, offset fbuf ;address of the buffer to be filled with file data int 21h ;call DOS to read from the file jc fread1 ;carry: there was a problem reading the file, else cmp ax, 2000 ;number of bytes read good? jne fread2 ;no then set for last read jmp freadex fread1: mov ah, 9 mov dx, offset freadmsg int 21h inc freaderr ;we are here if there was a problem reading the file fread2: inc lastread ;this prevents another read attempt cmp fsizeeven, 0 ;if even multiple of 2000 then jne fread3 ;indicate that the file is done jmp freadex ;otherwise quit so program can continue fread3: inc fdone ;this notifies the program to quit immediately freadex: mov bufsize, ax ;save number of valid bytes read ret fread endp getparams proc mov di, offset fname ;now 129 zeros mov ah, 51h ;function 51h: get PSP address (Program Segment Prefix) ;this is where DOS is holding the commandline parameters int 21h mov es, bx ;segment is returned in BX, move it to the DS segment mov si, 80h ;offset to start of passed parameters in the PSP xor cx, cx ;CX=0 mov cl, es:[si] ;segment override: read the byte located at es:si ;this has set the loop counter to the length of the parameters entered by the user add si, 2 ;offset 80h holds the length, the next byte holds the space ;between the exe and the start of the parameters gp1: mov al, es:[si] ;get the byte cmp al, 0Dh ;is it the [Enter] key? je gpexit ;yes: then end of desired data has been reached mov [di], al ;put it into fname variable inc si inc di ;advance both pointers loop gp1 gpexit: ret getparams endp end main
Also included is the proper operation of closing the open file while quitting the program. DOS has been handling this automatically for us, but it is not good programming practice to depend on DOS for the clean up. Save the changes and exit EDIT. [Y] to copy it to the floppy, [Y] to compile, [Y] to link and [N] to execute. Now execute XTYPE and follow it with a valid filename. The string held in the PSP has been upper cased by DOS and any valid relative or absolute path may be included in the file name:
K:\>xtype q:\dos\country.txt
...
This concludes the simple execution of a program that can use passed parameters from the command line. In a future exercise a program will be developed that can take user input and save it to a file named in the commandline.
Copyright©2000-2006 Brian Robinson ALL RIGHTS RESERVED