Example Programs: DUMP.ASM and DUMP.C

The programs DUMP.ASM (Figure 8-10) and DUMP.C (Figure 8-11) are parallel examples of the use of the handle file and record functions. The assembly-language version, in particular, illustrates features of a well-behaved MS-DOS utility:

The program checks the version of MS-DOS to ensure that all the functions it is going to use are really available.

The program parses the drive, path, and filename from the command tail in the program segment prefix.

The program uses buffered I/O for speed.

The program sends error messages to the standard error device.

The program sends normal program output to the standard output device, so that the dump output appears by default on the system console but can be redirected to other character devices (such as the line printer) or to a file.

The same features are incorporated into the C version of the program, but some of them are taken care of behind the scenes by the C runtime library.

name dump

page 55,132

title DUMP--display file contents

;

; DUMP--Display contents of file in hex and ASCII

;

; Build: C>MASM DUMP;

; C>LINK DUMP;

;

; Usage: C>DUMP unit:\path\filename.exe [ >device ]

;

; Copyright Ó 1988 Ray Duncan

;

cr equ 0dh ; ASCII carriage return

lf equ 0ah ; ASCII line feed

tab equ 09h ; ASCII tab code

blank equ 20h ; ASCII space code

cmd equ 80h ; buffer for command tail

blksize equ 16 ; input file record size

stdin equ 0 ; standard input handle

stdout equ 1 ; standard output handle

stderr equ 2 ; standard error handle

_TEXT segment word public 'CODE'

assume cs:_TEXT,ds:_DATA,es:_DATA,ss:STACK

dump proc far ; entry point from MS-DOS

push ds ; save DS:0000 for final

xor ax,ax ; return to MS-DOS, in case

push ax ; function 4ch can't be used

mov ax,_DATA ; make our data segment

mov ds,ax ; addressable via DS register

; check MS-DOS version

mov ax,3000h ; function 30h = get version

int 21h ; transfer to MS-DOS

cmp al,2 ; major version 2 or later?

jae dump1 ; yes, proceed

; if MS-DOS 1.x, display

; error message and exit

mov dx,offset msg3 ; DS:DX = message address

mov ah,9 ; function 9 = print string

int 21h ; transfer to MS-DOS

ret ; then exit the old way

dump1: ; check if filename present

mov bx,offset cmd ; ES:BX = command tail

call argc ; count command arguments

cmp ax,2 ; are there 2 arguments?

je dump2 ; yes, proceed

; missing filename, display

; error message and exit

mov dx,offset msg2 ; DS:DX = message address

mov cx,msg2_len ; CX = message length

jmp dump9 ; go display it

dump2: ; get address of filename

mov ax,1 ; AX = argument number

; ES:BX still = command tail

call argv ; returns ES:BX = address,

; and AX = length

mov di,offset fname ; copy filename to buffer

mov cx,ax ; CX = length

dump3: mov al,es:[bx] ; copy one byte

mov [di],al

inc bx ; bump string pointers

inc di

loop dump3 ; loop until string done

mov byte ptr [di],0 ; add terminal null byte

mov ax,ds ; make our data segment

mov es,ax ; addressable by ES too

; now open the file

mov ax,3d00h ; function 3dh = open file

; mode 0 = read only

mov dx,offset fname ; DS:DX = filename

int 21h ; transfer to MS-DOS

jnc dump4 ; jump, open successful

; open failed, display

; error message and exit

mov dx,offset msg1 ; DS:DX = message address

mov cx,msg1_len ; CX = message length

jmp dump9 ; go display it

dump4: mov fhandle,ax ; save file handle

dump5: ; read block of file data

mov bx,fhandle ; BX = file handle

mov cx,blksize ; CX = record length

mov dx,offset fbuff ; DS:DX = buffer

mov ah,3fh ; function 3fh = read

int 21h ; transfer to MS-DOS

mov flen,ax ; save actual length

cmp ax,0 ; end of file reached?

jne dump6 ; no, proceed

cmp word ptr fptr,0 ; was this the first read?

jne dump8 ; no, exit normally

; display empty file

; message and exit

mov dx,offset msg4 ; DS:DX = message address

mov cx,msg4_len ; CX = length

jmp dump9 ; go display it

dump6: ; display heading at

; each 128-byte boundary

test fptr,07fh ; time for a heading?

jnz dump7 ; no, proceed

; display a heading

mov dx,offset hdg ; DS:DX = heading address

mov cx,hdg_len ; CX = heading length

mov bx,stdout ; BX = standard output

mov ah,40h ; function 40h = write

int 21h ; transfer to MS-DOS

dump7: call conv ; convert binary record

; to formatted ASCII

; display formatted output

mov dx,offset fout ; DX:DX = output address

mov cx,fout_len ; CX = output length

mov bx,stdout ; BX = standard output

mov ah,40h ; function 40h = write

int 21h ; transfer to MS-DOS

jmp dump5 ; go get another record

dump8: ; close input file

mov bx,fhandle ; BX = file handle

mov ah,3eh ; function 3eh = close

int 21h ; transfer to MS-DOS

mov ax,4c00h ; function 4ch = terminate,

; return code = 0

int 21h ; transfer to MS-DOS

dump9: ; display message on

; standard error device

; DS:DX = message address

; CX = message length

mov bx,stderr ; standard error handle

mov ah,40h ; function 40h = write

int 21h ; transfer to MS-DOS

mov ax,4c01h ; function 4ch = terminate,

; return code = 1

int 21h ; transfer to MS-DOS

dump endp

conv proc near ; convert block of data

; from input file

mov di,offset fout ; clear output format

mov cx,fout_len-2 ; area to blanks

mov al,blank

rep stosb

mov di,offset fout ; convert file offset

mov ax,fptr ; to ASCII for output

call w2a

mov bx,0 ; init buffer pointer

conv1: mov al,[fbuff+bx] ; fetch byte from buffer

mov di,offset foutb ; point to output area

; format ASCII part...

; store '.' as default

mov byte ptr [di+bx],'.'

cmp al,blank ; in range 20h-7eh?

jb conv2 ; jump, not alphanumeric

cmp al,7eh ; in range 20h-7eh?

ja conv2 ; jump, not alphanumeric

mov [di+bx],al ; store ASCII character

conv2: ; format hex part...

mov di,offset fouta ; point to output area

add di,bx ; base addr + (offset*3)

add di,bx

add di,bx

call b2a ; convert byte to hex

inc bx ; advance through record

cmp bx,flen ; entire record converted?

jne conv1 ; no, get another byte

; update file pointer

add word ptr fptr,blksize

ret

conv endp

w2a proc near ; convert word to hex ASCII

; call with AX = value

; DI = addr for string

; returns AX, DI, CX destroyed

push ax ; save copy of value

mov al,ah

call b2a ; convert upper byte

pop ax ; get back copy

call b2a ; convert lower byte

ret

w2a endp

b2a proc near ; convert byte to hex ASCII

; call with AL = binary value

; DI = addr for string

; returns AX, DI, CX modified

sub ah,ah ; clear upper byte

mov cl,16

div cl ; divide byte by 16

call ascii ; quotient becomes the first

stosb ; ASCII character

mov al,ah

call ascii ; remainder becomes the

stosb ; second ASCII character

ret

b2a endp

ascii proc near ; convert value 0-0fh in AL

; into "hex ASCII" character

add al,'0' ; offset to range 0-9

cmp al,'9' ; is it > 9?

jle ascii2 ; no, jump

add al,'A'-'9'-1 ; offset to range A-F,

ascii2: ret ; return AL = ASCII char

ascii endp

argc proc near ; count command-line arguments

; call with ES:BX = command line

; returns AX = argument count

push bx ; save original BX and CX

push cx ; for later

mov ax,1 ; force count >= 1

argc1: mov cx,-1 ; set flag = outside argument

argc2: inc bx ; point to next character

cmp byte ptr es:[bx],cr

je argc3 ; exit if carriage return

cmp byte ptr es:[bx],blank

je argc1 ; outside argument if ASCII blank

cmp byte ptr es:[bx],tab

je argc1 ; outside argument if ASCII tab

; otherwise not blank or tab,

jcxz argc2 ; jump if already inside argument

inc ax ; else found argument, count it

not cx ; set flag = inside argument

jmp argc2 ; and look at next character

argc3: pop cx ; restore original BX and CX

pop bx

ret ; return AX = argument count

argc endp

argv proc near ; get address & length of

; command line argument

; call with ES:BX = command line

; AX = argument #

; returns ES:BX = address

; AX = length

push cx ; save original CX and DI

push di

or ax,ax ; is it argument 0?

jz argv8 ; yes, jump to get program name

xor ah,ah ; initialize argument counter

argv1: mov cx,-1 ; set flag = outside argument

argv2: inc bx ; point to next character

cmp byte ptr es:[bx],cr

je argv7 ; exit if carriage return

cmp byte ptr es:[bx],blank

je argv1 ; outside argument if ASCII blank

cmp byte ptr es:[bx],tab

je argv1 ; outside argument if ASCII tab

; if not blank or tab...

jcxz argv2 ; jump if already inside argument

inc ah ; else count arguments found

cmp ah,al ; is this the one we're looking for?

je argv4 ; yes, go find its length

not cx ; no, set flag = inside argument

jmp argv2 ; and look at next character

argv4: ; found desired argument, now

; determine its length...

mov ax,bx ; save param starting address

argv5: inc bx ; point to next character

cmp byte ptr es:[bx],cr

je argv6 ; found end if carriage return

cmp byte ptr es:[bx],blank

je argv6 ; found end if ASCII blank

cmp byte ptr es:[bx],tab

jne argv5 ; found end if ASCII tab

argv6: xchg bx,ax ; set ES:BX = argument address

sub ax,bx ; and AX = argument length

jmp argvx ; return to caller

argv7: xor ax,ax ; set AX = 0, argument not found

jmp argvx ; return to caller

argv8: ; special handling for argv = 0

mov ax,3000h ; check if DOS 3.0 or later

int 21h ; (force AL = 0 in case DOS 1)

cmp al,3

jb argv7 ; DOS 1 or 2, return null param

mov es,es:[2ch] ; get environment segment from PSP

xor di,di ; find the program name by

xor al,al ; first skipping over all the

mov cx,-1 ; environment variables...

cld

argv9: repne scasb ; scan for double null (can't use

scasb ; SCASW since might be odd addr)

jne argv9 ; loop if it was a single null

add di,2 ; skip count word in environment

mov bx,di ; save program name address

mov cx,-1 ; now find its length...

repne scasb ; scan for another null byte

not cx ; convert CX to length

dec cx

mov ax,cx ; return length in AX

argvx: ; common exit point

pop di ; restore original CX and DI

pop cx

ret ; return to caller

argv endp

_TEXT ends

_DATA segment word public 'DATA'

fname db 64 dup (0) ; buffer for input filespec

fhandle dw 0 ; token from PCDOS for input file

flen dw 0 ; actual length read

fptr dw 0 ; relative address in file

fbuff db blksize dup (?) ; data from input file

fout db 'nnnn' ; formatted output area

db blank,blank

fouta db 16 dup ('nn',blank)

db blank

foutb db 16 dup (blank),cr,lf

fout_len equ $-fout

hdg db cr,lf ; heading for each 128 bytes

db 7 dup (blank) ; of formatted output

db '0 1 2 3 4 5 6 7 '

db '8 9 A B C D E F',cr,lf

hdg_len equ $-hdg

msg1 db cr,lf

db 'dump: file not found'

db cr,lf

msg1_len equ $-msg1

msg2 db cr,lf

db 'dump: missing file name'

db cr,lf

msg2_len equ $-msg2

msg3 db cr,lf

db 'dump: wrong MS-DOS version'

db cr,lf,'$'

msg4 db cr,lf

db 'dump: empty file'

db cr,lf

msg4_len equ $-msg4

_DATA ends

STACK segment para stack 'STACK'

db 64 dup (?)

STACK ends

end dump

Figure 8-10. The assembly-language version: DUMP.ASM.

/*

DUMP.C Displays the binary contents of a file in

hex and ASCII on the standard output device.

Compile: C>CL DUMP.C

Usage: C>DUMP unit:path\filename.ext

Copyright Ó 1988 Ray Duncan

*/

#include <stdio.h>

#include <io.h>

#include <fcntl.h>

#define REC_SIZE 16 /* input file record size */

main(int argc, char *argv[])

{

int fd; /* input file handle */

int status = 0; /* status from file read */

long fileptr = 0L; /* current file byte offset */

char filebuf[REC_SIZE]; /* data from file */

if(argc != 2) /* abort if missing filename */

{ fprintf(stderr,"\ndump: wrong number of parameters\n");

exit(1);

}

/* open file in binary mode,

abort if open fails */

if((fd = open(argv[1],O_RDONLY | O_BINARY) ) == -1)

{ fprintf(stderr, "\ndump: can't find file %s \n", argv[1]);

exit(1);

}

/* read and dump records

until end of file */

while((status = read(fd,filebuf,REC_SIZE) ) != 0)

{ dump_rec(filebuf, fileptr, status);

fileptr += REC_SIZE;

}

close(fd); /* close input file */

exit(0); /* return success code */

}

/*

Display record (16 bytes) in hex and ASCII on standard output

*/

dump_rec(char *filebuf, long fileptr, int length)

{

int i; /* index to current record */

if(fileptr % 128 == 0) /* display heading if needed */

printf("\n\n 0 1 2 3 4 5 6 7 8 9 A B C D E F");

printf("\n%04lX ",fileptr); /* display file offset */

/* display hex equivalent of

each byte from file */

for(i = 0; i < length; i++)

printf(" %02X", (unsigned char) filebuf[i]);

if(length != 16) /* spaces if partial record */

for (i=0; i<(16-length); i++) printf(" ");

/* display ASCII equivalent of

each byte from file */

printf(" ");

for(i = 0; i < length; i++)

{ if(filebuf[i] < 32 || filebuf[i] > 126) putchar('.');

else putchar(filebuf[i]);

}

}

Figure 8-11. The C version: DUMP.C.

The assembly-language version of the DUMP program contains a number of subroutines that you may find useful in your own programming efforts. These include the following:

Subroutine Action

argc Returns the number of command-line arguments.

argv Returns the address and length of a particular command-line

argument.

w2a Converts a binary word (16 bits) into hex ASCII for output.

b2a Converts a binary byte (8 bits) into hex ASCII for output.

ascii Converts 4 bits into a single hex ASCII character.

It is interesting to compare these two equivalent programs. The C program contains only 77 lines, whereas the assembly-language program has 436 lines. Clearly, the C source code is less complex and easier to maintain. On the other hand, if size and efficiency are important, the DUMP.EXE file generated by the C compiler is 8563 bytes, whereas the assembly-language DUMP.EXE file is only 1294 bytes and runs twice as fast as the C program.